Skip to main content

Caching as a Cross-Cutting Concern using Castle Windsor

 

This post assumes knowledge of Dependency Injection and AOP.

AOP or Aspect-Oriented Programming is a very powerful way to add Cross-Cutting concerns to a system without impacting the core code base. Cross-Cutting concerns can cover non-functional requirements and also functional requirements.

There are generally three approaches when implementing AOP.

1. Generate Dynamic Proxies at runtime to intercept method calls

2. Post-Compilation Assembly Transformation

3. Attributes

  • Just using Plain Old Attributes

I really really don’t like annotating classes & members with Attributes so for me Dynamic Proxies is the only option and the performance hit is worth the productivity gains.

The very common example of AOP is Logging. Logging is by all accounts a Cross-Cutting concern and so lends itself very well to the example.

However I tend to think of Caching as a Cross-Cutting concern and really hate to see code like the below.

       public Product GetProductById(int id)
       {
           var cacheKey = string.Format("Product-", id);

           var cache = HttpRuntime.Cache;

           var product = cache.Get(cacheKey) as Product; 

           if (product != null)
           {
               return product; 
           }

           product = productRepository.Get(id);

           cache.Add(cacheKey, product, null, 
               System.Web.Caching.Cache.NoAbsoluteExpiration, 
               System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.High, null);

           return product; 

       }

IMO the above code block only adds noise to your code base and makes it harder to read. It’s also prone to logic errors if it’s being implemented in many places and so is a prime candidate for AOP.

In this example I will be using Castle Windsor just because I am most familiar with it, but any good Dependency Injection framework should support AOP. 

In Castle Windsor there is a concept of an Interceptor which as the name implies intercepts any method calls on a class. Behind the scenes this is implemented using DyanmicProxy.

Let’s first have a look at a fairly contrived example of a Service that we want to add Caching too.

    public interface ICatalogQueryService
    {
        Product GetProductById(int id); 
    }


Now in order to configure the Service to use our Interceptor we need a way to identify which class we want to apply the interceptor too. You can use Attributes or explicitly set the interceptor when configuring the Container however I prefer is to use an Interface to explicitly mark which Interface should be intercepted. This allows you to easily configure a Facility but that’s a topic for another post.

    public interface IMustBeCached
    {
    }

So after that I change the ICatalogQueryService to implement the IMustBeCached interface.

    public interface ICatalogQueryService : IMustBeCached 
    {
        Product GetProductById(int id); 
    }

The next and most important part is creating the Interceptor, this has to implement the Castle.Core.Interceptor.IInterceptor interface.

So here it is, in  just a few lines of code we have implemented a reusable Cache policy that caches all return values for one minute and our ICatalogQueryService implementation is none the wiser.

 

   public class CacheInterceptor : IInterceptor
   {
       private readonly ICacheProvider cacheProvider;

       private const int CacheExpiryMinutes = 1; 

       public CacheInterceptor(ICacheProvider cacheProvider)
       {
           this.cacheProvider = cacheProvider; 
       }

       #region IInterceptor Members

       public void Intercept(IInvocation invocation)
       {
           //check if the method has a return value
           if (invocation.Method.ReturnType == typeof(Void))
           {
               invocation.Proceed();
               return; 
           }

           var cacheKey = BuildCacheKeyFrom(invocation); 
          
           //try get the return value from the cache provider
           var item = cacheProvider.Get(cacheKey); 

           if (item != null)
           {
               invocation.ReturnValue = item;
               return;
           }

           //call the intercepted method
           invocation.Proceed();

           if (invocation.ReturnValue != null)
           {
               cacheProvider.Put(cacheKey, CacheExpiryMinutes, invocation.ReturnValue); 
           }

           return;
       }

       #endregion

       private static string BuildCacheKeyFrom(IInvocation invocation)
       {
           var methodName = invocation.Method.Name;

           var arguments = (from a in invocation.Arguments select a.ToString()).ToArray();
           var argsString = string.Join(",", arguments);

           var cacheKey = methodName + "-" + argsString;

           return cacheKey; 

       }
   }

 

Now all that’s left to do is tie it to configure the Windsor Container to use the Interceptor.

     public class Bootstrapper
     {
        private WindsorContainer container; 
        public WindsorContainer Container
        {
            get { return container;  }
        }

        public void Configure()
        {
            container = new WindsorContainer();

            container.Register(
                Component.For<CacheInterceptor>(),
                Component.For<ICacheProvider>()
                    .ImplementedBy<WebCacheProvider>().LifeStyle.Singleton,
                Component.For<ICatalogQueryService>()
                    .ImplementedBy<CatalogQueryService>()
                    .LifeStyle.Transient
                    .Interceptors(new InterceptorReference(typeof(CacheInterceptor))).Anywhere);

        }
     }

 

The other great thing about this is that you can turn caching on or off just by configuring the Container. 
Usually I will only enable Interceptors when in the Staging & Production environments and so keep any Cache out of the Debug environment as it can be problematic.

That’s all there is to it. Feel free to comment if you have any questions.

Till next time.

Comments

  1. Excellent post. Often I only need to cache certain repository calls. How would you achieve this in an elegant way? Also some locations of my application needs caching for certain calls (eg. GetAllLanguages) while other need the fresh version. Currently im using GetAllLanguagesCached() to illustrate specifically when a cached call is made vs non-cached. Thanks.

    ReplyDelete
  2. One way I've approached it before is to have 2 implementations of the same interface. e.g. ILanguageRepository with method GetAllLanguages() then you have 1 implementation which hits the Datastore and a Cache implementation which depends on the first implementation. The cache implementation would check the cache first if no results load from the non-cache implementation and add to the cache. Your layers above would then depend only on the cache implementation and you're free to wire up using IoC and not changing the calling code to switch between DB and Cache implementations. Make sense?

    ReplyDelete
  3. It is in a way, except using an Interceptor in the way I've described in this post would apply to all methods in that class whereas by having a specific implementation you can choose which methods you want to cache. In your case if you want the caller (Controller) to control whether or not they get from the Cache or not then you're not going to get that with an Interceptor or 2nd implementation. This is putting a responsibility on the Controller which could & arguably should be managed via the infrastructure. I can't think of a case when the Controller needs to know whether the data came from Cache or not.

    ReplyDelete
  4. I have added a LanguageService (Facade) that takes a ILanguageRep, and a ILanguageCached injection. This facade then has a GetAll() and GetAllCached() method respectively. Then my controller can choose to use the cached or not. Using Ninject the same ILanguageRep is injected into the Facade and the Cached implementation.

    ReplyDelete

Post a Comment

Popular posts from this blog

Freeing Disk Space on C:\ Windows Server 2008

I just spent the last little while trying to clear space on our servers in order to install .NET 4.5. Decided to post so my future self can find the information when I next have to do this. I performed all the usual tasks: Deleting any files/folders from C:\windows\temp and C:\Users\%UserName%\AppData\Local\TempDelete all EventViewer logs Save to another Disk if you want to keep themRemove any unused programs, e.g. FirefoxRemove anything in C:\inetpub\logsRemove any file/folders C:\Windows\System32\LogFilesRemove any file/folders from C:\Users\%UserName%\DownloadsRemove any file/folders able to be removed from C:\Users\%UserName%\DesktopRemove any file/folders able to be removed from C:\Users\%UserName%\My DocumentsStop Windows Update service and remove all files/folders from C:\Windows\SoftwareDistributionDeleting an Event Logs Run COMPCLN.exe Move the Virtual Memory file to another disk However this wasn’t enough & I found the most space was cleared by using the Disk Cleanup to…

CPF Contribution Rates for new Singapore Permanent Residents (SPR’s)

Recently my wife and I applied and got approved for Singapore Permanent Residency. After completing the formalities the most significant immediate change is the contribution to CPF which is Singapore’s mandatory social security savings scheme requiring contributions from employers and employees. CPF contributions start from the date you obtain SPR status, which is the date of the entry permit.   Being a relentless budgeter I needed to know exactly how much I and my employer would have to contribute so that I could adjust my budget accordingly as the employee contributions get deducted from the monthly salary. After doing some research I discovered that there is a “graduated” approach to CPF contributions for new SPR’s where the contributions gradually increase in the first and second year and then upon reaching the third year are at the full amount. Note: There is an option for employers to contribute the full amount for year 1 and year 2 and the employee can use the graduated rate, b…

Consuming the SSRS ReportExecutionService from a .NET Client

I’ve just finished writing a nice wrapper which internally calls the SSRS ReportExecutionService to generate reports.
Whilst it was fairly simple to implement there has been some major changes between 2005 and 2008 and the majority of online and documentation is based on the 2005 implementation. The most important change is that the Report Server and Report Manager are no longer hosted in IIS which will be a welcomed change to Sys Admins but makes the security model and hosting model vastly different. So far I’ve yet to figure out how to allow Anonymous Access, if anyone knows how to do this leave a comment and it will be most appreciated. Getting StartedTo get started you’ll want to add a service reference to http://localhost/ReportServer_SQL2008/ReportExecution2005.asmx where ReportServer_SQL2008 is the name you configure in the Reporting Services Configuration Manager. The Web Application files are located in C:\Program Files\Microsoft SQL Server\MSRS10.SQL2008\Reporting Servic…