AOP With Windsor: Adding Caching to IRepository<T> based on T's attributes

time to read 6 min | 1121 words


Okay, this is a fairly wacky scenario, I admit.
I am using IRepository<T> for my data access, and I wanted to be able to specify attributes on the T (that is, on the entity, not the repository) and have the behavior of the class change. For instance, if I have IRepository<OrderStatus>, that is something that changes very rarely, so I can safely cache that. But I want to put [Cacheable] on OrderStatus, not on OrderStatusRepository.  To make matter worse, I never explicitly register IRepository<OrderStatus>, I only register IRepository<T> and let Windsor figure the rest of it out.
I thought that it would be a major hurdle, but it turn out to be fairly easy.

Windsor has the concept of Component Model, basically everything that Windsor knows about a component, and you can plug your own contributers, which can add additional knowledge to Windsor.

/// <summary>
///
Inspect the model and add a caching interceptor if appropriate
/// </summary>
public class CachingInterceptorContributer : IContributeComponentModelConstruction
{
    /// <summary>
    /// Inspect the model and add a caching interceptor if appropriate
    /// </summary>
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        bool isRepository = model.Service.IsGenericType &&
            model.Service.GetGenericTypeDefinition() == typeof(IRepository<>);
        if (isRepository == false)
            return;
        Type entityType = model.Service.GetGenericArguments()[0];
        bool cacheable = entityType
              .GetCustomAttributes(typeof(CacheableAttribute),true).Length != 0;

        if(cacheable==false)
            return;
        model.Interceptors.Add(new InterceptorReference(typeof(CachingInterceptor)));
    }

}

This checks that the component is an IRepository<T>, and that the T has [Cacheable] attribute. The real nice thing here is that it will be called when Windsor decides that it needs to create an IRepository<OrderStatus>, thereby giving me the chance to add the interceptor to this (and only this) repository.
The interceptor is very simple, and builds on  already existing code:

/// <summary>
///
Add query caching capabilities
/// </summary>
public class CachingInterceptor : IInterceptor, IOnBehalfAware
{
    private string typeName;
 

    /// <summary>
    /// Intercepts the specified invocation and adds query caching capabilities
    /// </summary>
    /// <param name="invocation">The invocation.</param>
    public void Intercept(IInvocation invocation)
    {
        using(With.QueryCache(typeName))
        {
            invocation.Proceed();
        }
    }
 

    /// <summary>
    /// Sets the intercepted component model.
    /// </summary>
    /// <param name="target">The target.</param>
    public void SetInterceptedComponentModel(ComponentModel target)
    {
        typeName = target.Service.GetGenericArguments()[0].FullName;
    }

}


I am using this just to tell NHibernate that it should cache the query, since NHibernate would already be caching the entities in its second level cache (I might show later how I handled that). Now to register the contributer:


/// <summary>
///
Registers various add-ons to the container
/// </summary>
public class AdditionalFunctionalityFacility : AbstractFacility
{

      /// <summary>
      /// The actual addition of resolvers
      /// </summary>
      protected override void Init()
      {
        Kernel.AddComponent("caching.interceptor", typeof(CachingInterceptor));
        Kernel.ComponentModelBuilder.AddContributor(new CachingInterceptorContributer()); 
      }

}

And that is it. I register a facility, which takes care of configuring the interceptor and the contributer, and I am set. I can now simply put [Cacheable] on an entity, and it will cache that in all levels.
This technique is a powerful one, and I do believe that I will use it more in the future.