left-icon

Microsoft Unity Succinctly®
by Ricardo Peres

Previous
Chapter

of
A
A
A

CHAPTER 4

Aspect-Oriented Programming (AOP)

Aspect-Oriented Programming (AOP)


Introduction

Aspect-Oriented Programming (AOP) is about the ability to inject cross-cutting behavior (aspects) in existing code so as to do useful yet somewhat lateral operations transparently. This is called, in Unity terms, interception. Some examples include:

  • Logging all method invocations, including parameters.
  • Controlling access to a method based on the identity of the current user.
  • Catching exceptions and doing something with them.
  • Caching the results of a “heavy” operation so that in subsequent executions results come from the cache.

Other examples exist, of course. Aspects are applied to target code in a totally transparent way; that is, you just call your methods in the usual way and interception occurs magically. For now, let’s understand how Unity helps us applying aspects.

Interception Kinds and Techniques

Interception in .NET can be implemented in one of the following five ways:

  • Virtual method interception: Dynamically generates a subclass that overrides the virtual methods of the base class so as to add new behavior or suppress the existing one.
  • Interface interception: Generates a proxy that will sit behind an interface and its concrete implementation to which all of the interface’s operations will be diverged.
  • Transparent proxy interception: Generates a transparent or Remoting proxy to an interface or to an instance of a MarshalByRefObject; this is what Windows Communication Foundation (WCF) proxies use.
  • Context bound interception: Leverages the ContextBoundObject class and its associated framework. Since this is very specific and not very usual (because it requires using a base class of type ContextBoundObject), it is not supported by Unity and will not be covered.
  • Code weaving: This is probably the most powerful mechanism but it is also a complex one since it involves changing the code on an assembly—in memory as well as in the compiled output file. Unity also does not support it.

Some of these techniques can be applied to existing instances (instance interception) or to Unity-created ones (type interception). In any case, they only apply to instance, not static methods. The following table lists just that:

Table 2: Interception kinds and targets

Interception

Kind

Target

What Can Be Intercepted

Virtual method

Type

Non-sealed classes with virtual methods.

All virtual methods, properties, and event add and remove handlers.

Interface

Instance

Existing classes implementing an interface.

All interface methods, properties, and event add and remove handlers.

Transparent proxy

Instance

Existing classes implementing an interface or inheriting from MarshalByRefObject.

All interface methods, properties, and event add and remove handlers (in the case of interfaces) or all virtual methods, properties, and event add and remove handlers (for MarshalByRefObject-derived classes).

Interception, if enabled, is applied automatically. But before we can use it, we need to configure what interceptor to use per registration. Unity includes the following interceptors, which cover the most typical scenarios:

The base interfaces that specify the interception kinds, IInstanceInterceptor and ITypeInterceptor, expose a small number of methods. Worthy of note are two methods that both interfaces inherit from IInterceptor:

  • CanIntercept: Checks if the interceptor can intercept a given type, passed as parameter.
  • GetInterceptableMethods: Returns the list of interceptable methods for a given key-component pair of types, which may include property getters and setters, and event add and remove handlers.

Depending on the registration key, we need to set the proper interceptor; this is the one that will build our interception proxy for the target instance or type. Before we look at how to configure it, let’s understand how interception is implemented. But, even before that, let’s get EnterpriseLibrary.PolicyInjection, a required package from NuGet. This is where all interception-related classes are defined and is a prerequisite for this chapter.

Installing Policy Injection as a NuGet package

Figure 9: Installing Policy Injection as a NuGet package

Adding Interfaces

We can also add one or more interfaces to the generated proxy. In this case, we will need to implement—either in a behavior or in a call handler (see the next sections)—all of its methods and properties. We shall see an example later on.

Call Handlers

Interception means that all calls to a method, property, event add, or remove handler are diverged to custom code. In it, we decide what to do before or after (or instead of) the call to the original code.

In Unity, the atomic unit by which we can perform interception is the ICallHandler interface. Its interface is very simple:

public interface ICallHandler

{

    IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext);

    int Order { getset; }

}

The Invoke method is the one to where the action actually goes. It receives the execution context—the instance where the method invocation was made and any method parameters—as well as a delegate for the next handler.

The Order property tells Unity how to sort all call handlers that are applied to the same target.

Let’s see an example that measures the time that it took a method to execute:

public class MeasureCallHandler : ICallHandler

{

    public IMethodReturn Invoke(IMethodInvocation input, 

GetNextHandlerDelegate getNext)

    {

        var watch = Stopwatch.StartNew();

        var result = getNext()(input, getNext);

        var time = watch.ElapsedMilliseconds;    

        var logger = ServiceLocator.Current.GetInstance<ILogger>();

        logger.Log(String.Format(

"Method {0} took {1} milliseconds to complete", input.MethodBase, time));

        return (result);

    }

    public int Order { get; set; }

 }

The call to getNext()(input, getNext) is normally always present in a call handler, and it tells Unity to execute the target method or property that the call handler is intercepting. Its result is a value suitable for returning from Invoke and contains the actual returned value from the intercepted method if it is not void.

The MethodBase property contains a reference to the intercepted method. If you want to see if this method is a property getter or setter, you can use:

if (input.MethodBase.IsSpecialName && ( input.MethodBase.Name.StartsWith("get_") || input.MethodBase.Name.StartsWith("set_"))) { }

Or an event handler add or remove method:

if (input.MethodBase.IsSpecialName && ( input.MethodBase.Name.StartsWith("add_") || input.MethodBase.Name .StartsWith("remove_"))) { }

Target is the targeted object instance that contains the intercepted method.

Arguments contains a collection of all method parameters, if any.

InvocationContext is a general-purpose dictionary indexed by string and storing objects, where call handlers targeting the same method or property can share data.

After calling the base implementation, we get a result from which we can obtain the actual returned value, if the method was not void (ReturnValue) and did not return an exception or the exception that was thrown (Exception).

If we want to return a value without actually executing the intercepted method, we can use the CreateMethodReturn method. In its simplest form, it just takes a value to return for non-void methods which, of course, must match the method’s expected return type:

public class NullCallHandler : ICallHandler

{

    public IMethodReturn Invoke(IMethodInvocation input, 

GetNextHandlerDelegate getNext)

    {

        return (input.CreateMethodReturn(null));

    }

    public int Order { get; set; }

 }

And, if we want to return an exception instead, we call CreateExceptionMethodReturn:

public class ExceptionCallHandler : ICallHandler

{

    public IMethodReturn Invoke(IMethodInvocation input, 

GetNextHandlerDelegate getNext)

    {

        return (input.CreateExceptionMethodReturn(new NotImplementedException()));

    }

    public int Order { get; set; }

 }

If we have two call handlers configured for the same method, we can pass values from the first to the second:

public class MessagingCallHandler : ICallHandler

{

    public IMethodReturn Invoke(IMethodInvocation input, 

GetNextHandlerDelegate getNext)

    {

        if (input.InvocationContext.ContainsKey("message") == true)

        {

            //someone got here first...

        }

        else

        {

            input.InvocationContext["message"] = "I was here first";

        }

        return (getNext()(input, getNext));

    }

    public int Order { get; set; }

 }

Interception Behaviors

A call handler only applies to a single interception target (a method or property). Unity offers another construction for intercepting all calls to all interceptable members of a class, called an interception behavior.

An interception behavior is a class that implements IInterceptionBehavior. Again, it is a simple interface although slightly more complex than ICallHandler. Its contract is this:

public interface IInterceptionBehavior

{

    IEnumerable<Type> GetRequiredInterfaces();

    IMethodReturn Invoke(IMethodInvocation input, 

GetNextInterceptionBehaviorDelegate getNext);    

    bool WillExecute { get; }

}

Method GetRequiredInterfaces is a way developers can use to force the behavior to only run if the target class implements one or more specific interfaces. In most cases, we just return an empty collection.

Invoke is exactly the same as in ICallHandler so there’s no point in discussing it again.

Property WillExecute is used to tell Unity if the behavior should be applied or not. If it returns false, method Invoke will not run.

Note: Since WillExecute receives no context, it is hard to use. Most of the time we just return true.

Note: The difference between call handlers and behaviors is that behaviors intercept all interceptable methods in a class whereas call handlers intercept just one.

A simple interception behavior:

public class MyInterceptionBehavior : IInterceptionBehavior

{

    IEnumerable<TypeIInterceptionBehavior.GetRequiredInterfaces()

    {

        return (Type.EmptyTypes);

    }

 

    IMethodReturn IInterceptionBehavior.Invoke(IMethodInvocation input, 

GetNextInterceptionBehaviorDelegate getNext)

    {

        //before base call

        Console.WriteLine("About to call method {0}", input.MethodBase);

        var result = getNext().Invoke(input, getNext);

        if (result.Exception != null)

        {

            Console.WriteLine("Method {0} returned {1}", input.MethodBase, result.ReturnValue);

        }

        else

        {

            Console.WriteLine("Method {0} threw {1}", input.MethodBase, result.Exception);

        }

        //after base call

        return (result);

    }

 

    bool IInterceptionBehavior.WillExecute { get { return (true); } }

}

Configuring Interception by Attributes

We configure interception on an interceptable property or method by applying a HandlerAttribute that returns an instance of an ICallHandler. For example, imagine you have an implementation of ICallHandler called CachingHandler, you could write a host attribute for it as this:

[Serializable]                   

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]

public class CachingHandlerAttribute : HandlerAttribute

{

    public CachingHandlerAttribute(int hours, int minutes, int seconds)

    {

        this.Duration = new TimeSpan(hours, minutes, seconds);

    }

    public TimeSpan Duration { get; public set; }

    public override ICallHandler CreateHandler()

    {

        return (new CachingHandler(this.Duration) { Order = this.Order });

    }

}

After we have our attribute, it’s just a matter of applying it to a method. When Unity resolves the containing class, it will recognize it:

[CachingHandler(1, 0, 0)]        //1 hour, 0 minutes, 0 seconds

void DoLongOperation(int someParameter, int anotherParameter);

See chapter Chapter 5  Extending Unity for a full implementation.

Note: Interception attributes can be applied to either the key or the component type.

Configuring Interception by Code

Applying Interception

First, we need to tell Unity to use the Interception extension:

unity.AddNewExtension<Interception>().Configure<Interception>();

We have two basic options:

  • Register an injection behavior
  • Register a call handler

Interception Behaviors

Interception behaviors are injected at registration time using the RegisterType method, like in the following example in which we add a MyInterceptionBehavior to the FileLogger:

unity.RegisterType<ILoggerFileLogger>(

    new Interceptor<InterfaceInterceptor>(),

    new InterceptionBehavior<MyInterceptionBehavior>());

Note: You can add several interception behaviors; all will be applied in sequence.

Notice how we define the interceptor to use (InterfaceInterceptor); it is also possible to configure it as the default for a type:

unity.Configure<Interception>().Configure<Interception>()

    .SetDefaultInterceptorFor<ILogger>(new InterfaceInterceptor());

And this is it. Every time you ask for a FileLogger instance, you will get a proxy that includes the MyInterceptionBehavior.

Call Handlers

Call handlers are a bit trickier to apply because we need to define to which member or members they will apply. For that, we add call handler types (AddCallHandler or AddCallHandler<T>) or instances (AddCallHandler) to a policy (AddPolicy) and a set of rules (AddMatchingRule):

unity.Configure<Interception>().Configure<Interception>()

    .AddPolicy("Add MeasureCallHandler to FileLogger.Log")

    .AddMatchingRule(new TypeMatchingRule(typeof(FileLogger)))

    .AddMatchingRule(new MemberNameMatchingRule("Log"))

    .AddCallHandler<MeasureCallHandler>();

Tip: You should add a call handler instance instead of its type if the call handler requires special configuration (like setting its execution order or if it doesn’t have a public, parameterless constructor).

This example has two rules:

Tip: The call handler is only applied if all rules match.

Tip: You can add any number of call handlers; they will be processed and sorted by their Order property.

A rule must implement IMatchingRule, which exposes a very simple interface:

public interface IMatchingRule

{

    bool Matches(MethodBase member);

}

There are several rules included out of the box:

You can easily implement your own rules; see an example in Policy Rules.

Note: All rules that take names accept regular expressions.

Adding Interfaces to Interception Proxies

When we register a type for interception, we can add additional interfaces that Unity will add to the generated proxy. The injection member classes used for this purpose are AdditionalInterface and AdditionalInterface<T>. Here is one example of its usage:

unity.RegisterType<ILoggerFileLogger>(

    new Interceptor<InterfaceInterceptor>(),

    new InterceptionBehavior<DisposableBehavior>(),

    new AdditionalInterface<IDisposable>()

);

This tells Unity to add interface IDisposable to all proxies to FileLogger and to apply a DisposableBehavior. The result of calling Resolve will then implement the IDisposable interface and the Dispose method is available for invocation. Of course, because this method was not implemented by the FileLogger class, it is up to the DisposableBehavior to make sure that, if the method is called, something happens.

If you add some interface to a class, you have to implement all of the interface’s methods and properties. A common example is adding support for INotifyPropertyChanged, which is a very useful interface that is somewhat cumbersome to implement when there are many properties. Let’s see how we can do it automatically with an interception behavior.

First, the registration; this is where we add the INotifyPropertyChanged, set the interceptor (VirtualMethodInterceptor in this case), and set the interceptor class:

unity.RegisterType<MyComponentMyComponent>(

    new Interceptor<VirtualMethodInterceptor>(),

    new InterceptionBehavior<NotifyPropertyChangedInterceptionBehavior>(),

    new AdditionalInterface<INotifyPropertyChanged>()

);

The NotifyPropertyChangedInterceptionBehavior class:

public class NotifyPropertyChangedInterceptionBehavior : IInterceptionBehavior

{

    private event PropertyChangedEventHandler handlers = delegate { };

    public IEnumerable<Type> GetRequiredInterfaces()

    {

        yield return (typeof(INotifyPropertyChanged));

    }

    public IMethodReturn Invoke(IMethodInvocation input, 

GetNextInterceptionBehaviorDelegate getNext)

    {

        var result = null as IMethodReturn;

        if (input.MethodBase.IsSpecialName == true)

        {

            if (input.MethodBase.Name.StartsWith("set_") == true)

            {

                result = getNext()(input, getNext);

                this.handlers(input.Target, new PropertyChangedEventArgs

(input.MethodBase.Name.Substring(4)));

            }

            else if (input.MethodBase.Name == "add_PropertyChanged")

            {

                this.handlers += (input.Arguments[0] as PropertyChangedEventHandler);

                result = input.CreateMethodReturn(null, input.Arguments);

            }

            else if (input.MethodBase.Name == "remove_PropertyChanged")

            {

                this.handlers -= (input.Arguments[0] as PropertyChangedEventHandler);

                result = input.CreateMethodReturn(null, input.Arguments);

            }

        }

        return (result);

    }

    public Boolean WillExecute { get { return(true); } }

}

And a sample usage:

var component = unity.Resolve<Component>();

var npc = component as INotifyPropertyChanged;

npc.PropertyChanged += delegate(Object source, PropertyChangedEventArgs args)

{

    //raised

};

component.Property = "Some Value"//raise PropertyChanged event

Configuring Interception by XML

Applying Interception

When using XML configuration, we usually register the Unity extension (Interception) for applying the interception functionality and a section extension (InterceptionConfigurationExtension) that allows the configuration of it in XML:

<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">

    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension

.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />

        <container>

            <extension type="Microsoft.Practices.Unity.InterceptionExtension

.Interception, Microsoft.Practices.Unity.Interception" />

        </container>

</unity>

Interception Behaviors

Behaviors are configured inside the register declaration, in an interceptionBehavior element:

<register type="Succinctly.ILogger, Succinctly" mapTo="Succinctly.

ConsoleLogger, Succinctly">      

    <interceptor type="InterfaceInterceptor"/>

    <interceptionBehavior type="Succinctly.MyBehavior, Succinctly"/>

</register>

The interceptionBehavior allows three attributes:

  • name (optional): A name that, if supplied, causes Unity to resolve the behavior instead of just creating a new instance.
  • type (required): The behavior type (a class that implements IInterceptionBehavior).
  • isDefaultForType (optional): If true, the behavior will be applied to all registrations of this type; the default is false.

Call Handlers

Call handlers are, as you know, slightly more complicated because of the matching rules:

<container>

    <register type="Succinctly.ILogger, Succinctly" mapTo="Succinctly.FileLogger, 

Succinctly">    

        <interceptor type="InterfaceInterceptor"/>

    </register>

    <interception>

        <policy name="Add MeasureCallHandler to FileLogger.Log">

            <matchingRule name="type" type="TypeMatchingRule">

                <constructor>

                    <param name="typeName" value="Succinctly.FileLogger" />

                </constructor>

            </matchingRule>

            <matchingRule name="type" type="MemberNameMatchingRule">

                <constructor>

                    <param name="nameToMatch" value="Log" />

                </constructor>

            </matchingRule>

            <callHandler name="MeasureCallHandler" type="Succinctly

.MeasureCallHandler, Succinctly" />

        </policy>

    </interception>

</container>

I believe that the new sections interceptors, default, policy, matchingRule, and callHandler are self-explanatory so there’s really nothing to add here. Do notice the interceptor declaration inside register; this is required so that Unity knows what interceptor to use.

Adding Interfaces to Interception Proxies

This is all it takes to add a new element, addInterface, to the register declaration:

<register type="Succinctly.ILogger, Succinctly" mapTo="Succinctly.

ConsoleLogger, Succinctly">      

    <interceptor type="InterfaceInterceptor"/>

    <addInterface type="System.IDisposable, mscorlib"/>

</register>

Applying Interception in an Existing Instance

An alternative approach to resolving an intercepted component from Unity is to generate a runtime proxy through an interface to an existing instance. In the process, we can add a number of interception behaviors and even interfaces. We do that by calling one of the ThroughProxy overloads:

var logger = new FileLogger("output.log");

var loggerProxy = Intercept.ThroughProxy<ILogger>(logger, 

    new InterfaceInterceptor(),

    new AddInterface<IDisposable>(),

    new IInterceptionBehavior[] { new MyInterceptionBehavior() });

var disposableLogger = loggerProxy as IDisposable;

Do note that this only works through interfaces and that the existing instance is left untouched.

Included Call Handlers

The Enterprise Library—of which Unity is a part—includes a number of call handlers that you can use in your code:

  1. Table 3: Included call handlers

Call Handler

Purpose

Logging Handler

Logging Method Invocation and Property Access

LogCallHandler Microsoft.Practices.EnterpriseLibrary.Logging.dll

Exception Handling

Handling Exceptions in a Structured Manner

ExceptionCallHandler Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll

Validation Handler

Validating Parameter Values

ValidationCallHandler Microsoft.Practices.EnterpriseLibrary.Validation.dll

Authorization Handler

Authorizing Method and Property Requests

AuthorizationCallHandler Microsoft.Practices.EnterpriseLibrary.Security.dll

Performance Counter Handler

Measuring Target Method Performance

PerformanceCounterCallHandler Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll

Note: Some of these call handlers require a specific installation procedure. Don’t forget to read their documentation.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.