Unity – Part 3: Aspect Oriented Programming

AOP

This is my third post on Unity. See the first here for an introduction and the second here for how to apply dependency injection.

Aspect Oriented Programming (AOP) is a technique for applying cross-cutting concerns to existing implementations, without modifying them. Some examples of it are:

  • Wrapping method calls that go to the database in transactions automatically;
  • Logging all calls to some method, including the input parameters and return value;
  • Catching exceptions thrown in a method automatically and doing something with them.

AOP is supported in the Enterprise Library (of which Unity is part) by the Policy Injection application block, and it can be integrated with Unity. You must install this application block, perhaps by using NuGet:

image

We need to add the interception behavior – which is the one that actually applies aspects – to Unity, either by code:

   1: unity.AddNewExtension<Interception>();

Or by XML configuration:

   1: <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
   2:     <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
   3: </unity>

Having said that, the first concept we need to know is that of an interceptor. An interceptor in the Policy Injection block is an implementation of Microsoft.Practices.Unity.InterceptionExtension.IInterceptor interface, and there are three implementations:

It is required that, when you are going to apply an aspect to a registration, you choose an interceptor suitable for that registration, based on what type we are registering.

An aspect itself is an implementation of Microsoft.Practices.Unity.InterceptionExtension.ICallHandler, Unity includes five out of the box such handlers:

The ICallHandler interface only defines a single method, Invoke, which wraps a method’s arguments and allows having code run before, after or even instead of the target method, and an Order property, for specifying the order by which the aspect should be applied, in case there are many.

A simple call handler, for outputting some string before or after a method call, might be:

   1: public class OutputCallHandler : ICallHandler
   2: {
   3:     public Boolean Before
   4:     {
   5:         get;
   6:         set;
   7:     }
   8:  
   9:     public Boolean After
  10:     {
  11:         get;
  12:         set;
  13:     }
  14:  
  15:     public String Message
  16:     {
  17:         get;
  18:         set;
  19:     }
  20:  
  21:     Int32 ICallHandler.Order
  22:     {
  23:         get;
  24:         set;
  25:     }
  26:  
  27:     IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
  28:     {
  29:         if (this.Before == true)
  30:         {
  31:             Console.WriteLine(this.Message);
  32:         }
  33:  
  34:         IMethodReturn result = getNext()(input, getNext);
  35:  
  36:         if (result.Exception != null)
  37:         {
  38:             Console.Error.WriteLine(result.Exception.Message);
  39:         }
  40:         else
  41:         {
  42:             if (this.After == true)
  43:             {
  44:                 Console.WriteLine(this.Message);
  45:             }
  46:         }
  47:  
  48:         return (result);
  49:     }
  50: }

Another option is to have interception for all methods of the target registration, which can be achieved by implementing Microsoft.Practices.Unity.InterceptionExtension.IInterceptionBehavior in a concrete class, such as this:

   1: public class MyInterceptionBehavior : IInterceptionBehavior
   2: {
   3:     IEnumerable<Type> IInterceptionBehavior.GetRequiredInterfaces()
   4:     {
   5:         return (Type.EmptyTypes);
   6:     }
   7:  
   8:     IMethodReturn IInterceptionBehavior.Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
   9:     {
  10:         //before target method call
  11:  
  12:         if (input.MethodBase == typeof(MyService).GetMethod("DoSomething"))
  13:         {
  14:             //do something
  15:         }
  16:  
  17:         IMethodReturn methodReturn = getNext().Invoke(input, getNext);
  18:  
  19:         //after target method call
  20:  
  21:         return (methodReturn);
  22:     }
  23:  
  24:     Boolean IInterceptionBehavior.WillExecute
  25:     {
  26:         get
  27:         {
  28:             return (true);
  29:         }
  30:     }
  31: }

If we want to cancel the default method call, if it is non void, we must return an appropriate value:

   1: IMethodReturn methodReturn = input.CreateMethodReturn(someValue, input.Arguments);

Or if we want to return an exception:

   1: IMethodReturn methodReturn = input.CreateExceptionMethodReturn(new SomeException());

There are three ways by which we can apply an aspect to a registration:

  • By applying an attribute to a method on the declaring or target type;
  • By code configuration;
  • By XML configuration.

Interception By Attributes

We need to create an attribute that derives from Microsoft.Practices.Unity.InterceptionExtension.HandlerAttribute and which instantiates our call handler:

   1: [Serializable]
   2: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
   3: public sealed class OutputCallHandlerAttribute : HandlerAttribute
   4: {
   5:     public Boolean Before
   6:     {
   7:         get;
   8:         set;
   9:     }
  10:  
  11:     public Boolean After
  12:     {
  13:         get;
  14:         set;
  15:     }
  16:  
  17:     public String Message
  18:     {
  19:         get;
  20:         private set;
  21:     }
  22:  
  23:     public OutputCallHandlerAttribute(String message)
  24:     {
  25:         this.Message = message;
  26:     }
  27:  
  28:     public override ICallHandler CreateHandler(IUnityContainer container)
  29:     {
  30:         return (new OutputCallHandler() { After = this.After, Before = this.Before, Message = this.Message });
  31:     }        
  32: }

And we apply it to any method declaration:

   1: public interface IMyService
   2: {
   3:     [OutputCallHandler("Before", Before = true)]
   4:     [OutputCallHandler("After", After = true)]
   5:     void DoSomething();
   6: }

But before this works, we need to tell Unity to use interface interception for our type:

   1: unity.Configure<Interception>().SetDefaultInterceptorFor<IMyService>(new InterfaceInterceptor());

Interception By Code

For intercepting by code, whenever we register something with Unity, we also tell it to use interface interception and to include a behavior instance – it is not possible to specify a call handler for a specific method:

   1: unity.RegisterType<IMyService, MyService>(new ContainerControlledLifetimeManager(), new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<OutputInterceptionBehavior>());

Interception By Configuration

When applying interception by configuration we also cannot target a specific method, but instead specify an interception behavior, which will apply to all method – of course, inside of it we can do our own filtering, by looking at the IMethodInvocation.MethodBase property:

   1: <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
   2:     <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>
   3:     <container>
   4:         <extension type="Microsoft.Practices.Unity.InterceptionExtension.Interception, Microsoft.Practices.Unity.Interception"/>
   5:         <interceptors>
   6:             <interceptor type="InterfaceInterceptor">
   7:                 <default type="MyNamespace.IMyService, MyAssembly"/>
   8:             </interceptor>
   9:         </interceptors>
  10:         <register type="MyNamespace.IMyService, MyAssembly" mapTo="MyNamespace.MyService, MyAssembly">
  11:             <lifetime type="singleton"/>
  12:             <interceptionBehavior type="MyNamespace.OutputInterceptionBehavior, MyAssembly"/>
  13:         </register>
  14:         </register>
  15:     </container>
  16: </unity>

Executing

You must Unity to retrieve an instance, which will be properly wrapped in a proxy, and from there all of your configured interceptors will be called:

   1: IMyService svc = ServiceLocator.Current.GetInstance<IMyService>();
   2: svc.DoSomething();

Next in line: extending Unity.

                             

No Comments