Aspect Oriented Programming with Castle Windsor

Aspect Oriented Programming (AOP) is a way of addressing the ‘cross cutting concerns’ of a system – e.g. logging, security, transactions, caching etc.  Using aspects you can apply these concerns without hand writing it into each place you need it.  This helps with creating clean solutions and with testability.  There’s a good summary of this in Clean Code – A handbook to agile Software Craftsmanship, so for more have a read of that (chapter called Systems)…

The system I’m building at the moment uses AOP to apply logging and caching.  It’s remarkably simple to apply AOP using Castle Windsor (this is one of the reasons why I picked Castle Windsor as my container – it’s one of the most mature containers so a lot of features exist and have been well used).  This post from Ayende was my starting point.  The reason I didn’t use the code ‘as is’ is because I don’t want to configure my aspects in XML.

Aspects are applied using interceptors.  Interceptors are given to us by Windsor and pretty much do what’s described by the name; they intercept method calls.  Windsor achieves this by dynamically generating a proxy.  Fortunately, all of this happens for us and we don’t really have to care (other than be aware of the implications a proxy has).

We can only intercept method calls we know about – that is all calls made on methods which implement those defined on the service interface.  These services / implementations must be registered within our container.

Using a ‘facility’ we can get involved in the registration process and add our interceptors as required (by hooking up to the kernel ‘ComponentRegistered’ event).  The facility creates an interceptor if the component being registered requires an aspect.

public class AspectFacility : AbstractFacility
    {
        private readonly IAspectSelector[] aspectSelectors;
 
        public AspectFacility(params IAspectSelector[] aspectSelectors)
        {
            this.aspectSelectors = aspectSelectors;
        }
 
        protected override void Init()
        {
            Kernel.AddComponent("aspectInterceptor", typeof(AspectInterceptor), LifestyleType.Transient);
            Kernel.AddComponentInstance<IAspectFactory>(new KernalAspectFactory(Kernel));
            RegisterAspectsInKernal();
            Kernel.ComponentRegistered += ComponentRegistered;
        }
 
        private void RegisterAspectsInKernal()
        {
            foreach (var aspectSelector in aspectSelectors)
            {
                var requiresConfiguration = aspectSelector as IAspectRequiresConfiguration;
                if (requiresConfiguration != null)
                {
                    requiresConfiguration.Configure(Kernel);
                }
                Kernel.AddComponent(aspectSelector.GetType().Name, aspectSelector.AspectType, LifestyleType.Singleton);
            }
        }
 
        void ComponentRegistered(string key, IHandler handler)
        {
            if (!(handler.ComponentModel.Implementation.IsPublic || handler.ComponentModel.Implementation.IsNestedPublic))
                return;
 
            var matchedAspects = new List<IAspectSelector>();
            foreach (var aspect in aspectSelectors)
            {
                if (aspect.Enabled &&
                    aspect.IsMatch(handler.ComponentModel.Service))
                {
                    if (matchedAspects.Count == 0)
                        handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AspectInterceptor)));
                    matchedAspects.Add(aspect);
                }
            }
            
            if (matchedAspects.Count > 0)
            {
                if (!handler.ComponentModel.ExtendedProperties.Contains(AspectInterceptor.AspectSelectorsExtendedPropertyName))
                {
                    handler.ComponentModel.ExtendedProperties.Add(
                        AspectInterceptor.AspectSelectorsExtendedPropertyName, matchedAspects);
                }
            }
        }
    }

The interceptor which gets added runs all the aspects that are registered for that component.  An aspect can decide to invoke the implementation or not depending on its vote options.

public class AspectInterceptor : IInterceptor, IOnBehalfAware
   {
       private readonly IAspectFactory aspectFactory;
       public const string AspectSelectorsExtendedPropertyName = "aspects";
 
       private IEnumerable<IAspectSelector> aspectSelectors;
       private readonly List<IAspect> aspectInstances = new List<IAspect>();
       private Type serviceType;
       private readonly object initLock = new object();
 
       public AspectInterceptor(IAspectFactory aspectFactory)
       {
           if (aspectFactory == null) throw new ArgumentNullException("aspectFactory");
           this.aspectFactory = aspectFactory;
       }
 
       public void Intercept(IInvocation invocation)
       {
           var context = new MethodInvocationContext(serviceType, invocation);
           CreateAspects();
 
           var resp = PreCall(context);
           
           if (resp == MethodVoteOptions.Halt)
               return;
 
           try
           {
               context.MethodInvokeStartTime = DateTime.Now;
               invocation.Proceed();
           }
           catch (Exception ex)
           {
               OnException(context, ex);
               throw;
           }
 
           PostCall(context);
       }
 
       private MethodVoteOptions PreCall(MethodInvocationContext context)
       {
           var resp = MethodVoteOptions.Continue;
           var relevantAspects = GetRelevantAspectsForInvocation(context);
           foreach (var aspect in relevantAspects)
           {
               resp = aspect.PreCall(context);
               if (resp == MethodVoteOptions.Halt)
                   break;
           }
 
           return resp;
       }
 
       private void PostCall(MethodInvocationContext context)
       {
           foreach (var aspect in GetRelevantAspectsForInvocation(context))
           {
               aspect.PostCall(context);
           }
       }
 
       private void OnException(MethodInvocationContext context, Exception exception)
       {
           foreach (var aspect in GetRelevantAspectsForInvocation(context))
           {
               aspect.OnException(context, exception);
           }
       }
 
       private IEnumerable<IAspect> GetRelevantAspectsForInvocation(MethodInvocationContext context)
       {
           return aspectInstances.Where(a => a.HandleForMethodCall(context));
       }
 
       private void CreateAspects()
       {
           if (aspectInstances.Count > 0)
               return;
           
           lock (initLock)
           {
               if (aspectInstances.Count > 0)
                   return;
 
               foreach (var aspectSelector in aspectSelectors)
               {
                   aspectInstances.Add(aspectFactory.Create(aspectSelector.AspectType));
               }
           }
       }
 
       public void SetInterceptedComponentModel(ComponentModel target)
       {
           aspectSelectors = (IEnumerable<IAspectSelector>)target.ExtendedProperties[AspectSelectorsExtendedPropertyName];
           serviceType = target.Service;
       }
   }

You can register aspects by calling the WithAspects extension method.  This must be done before any components are registered with the container (at least any components which you would like your aspects applied to).

public static IWindsorContainer WithAspects(this IWindsorContainer container, params IAspectSelector[] aspects)
{
    container.AddFacility("aspectFacility", new AspectFacility(aspects));
    return container;
}

That’s pretty much it…  The aspects I’ve written so far are Logging and Caching, I will blog about them shortly.  Once I’ve done all the posts I’ll make the code available somewhere.

Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

One Response to Aspect Oriented Programming with Castle Windsor

  1. Pingback: Logging Aspect | pep => lowdown

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s