Adding Business Layer Caching Using Castle Windsor AOP


Aspect Oriented Programming (AOP) is a programming paradigm that allows separation of cross-cutting concerns. But you probably already knew that if you’re reading this. If not, go have a read at the Wikipedia page on AOP or the introduction page on the Castle Project site.

The Problem

If we have an existing application to which we want to add caching, the traditional approach is to include caching calls within the app itself.

We’re going to start off with a simple app to read stock prices and then add caching to it. The service call looks like this:

/// <summary>
/// Gets the stock reading for the specified stock.
/// </summary>
/// <param name="symbol">The stock symbol.</param>
/// <returns>
/// An asynchronous stock reading value.
/// </returns>
public async Task<StockReading> GetStockReading(string symbol)
{
  var client = new HttpClient();
  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/json"));
  client.BaseAddress = new Uri("http://dev.markitondemand.com/Api/");
 
  var query = string.Format("Quote/json?symbol={0}", symbol);
  var response = client.GetAsync(query).Result;
 
  // If it fails - outputs the request string followed by the Http response
  if (response.StatusCode != System.Net.HttpStatusCode.OK)
  {
    var result = string.Format("{0}{1} n{2}", client.BaseAddress, query, response);
    throw new HttpException(result);
  }
 
  // If it gets an OK status Outputs the json/xml received
  var json = await response.Content.ReadAsStringAsync();
  var data = JsonConvert.DeserializeObject<ReaderResultSet>(json);
 
  if (data == null || data.Data == null)
  {
    return null;
  }
 
  return new StockReading
    {
      Name = data.Data.Name,
      Symbol = data.Data.Symbol,
      LastPrice = data.Data.LastPrice,
      TimeStamp = DateTime.Now
    };
}

Simple enough – read some JSON from an API, wrap it up and return it.

If we were going to add caching directly to this method, we’d probably do something like this:

Cache Provider

Rather than using cache directly, we’d be better creating a cache provider interface and injecting it using Dependency Injection.

public interface ICacheProvider
{
  /// <summary>
  /// Gets the specified cache item using the key.
  /// </summary>
  /// <param name="cacheKey">The cache key.</param>
  /// <returns>The object cached with the specified key.</returns>
  object Get(string cacheKey);
 
  /// <summary>
  /// Inserts the specified item into the cache.
  /// </summary>
  /// <param name="cacheKey">The cache key.</param>
  /// <param name="item">The item to cache.</param>
  /// <param name="cacheExpiry">The cache expiry timeout.</param>
  void Insert(string cacheKey, object item, TimeSpan cacheExpiry);
}

Updated Code

private readonly ICacheProvider cacheProvider;
 
/// <summary>
/// Initializes a new instance of the <see cref="StockServices"/> class.
/// </summary>
/// <param name="cacheProvider">The cache provider.</param>
public StockServices(ICacheProvider cacheProvider)
{
  this.cacheProvider = cacheProvider;
}
 
/// <summary>
/// Gets the stock reading for the specified stock.
/// </summary>
/// <param name="symbol">The stock symbol.</param>
/// <returns>
/// An asynchronous stock reading value.
/// </returns>
public async Task<StockReading> GetStockReading(string symbol)
{
  var cache = this.cacheProvider; // injected in the constructor above
  var cacheName = "StockReading-" + symbol;
  var cachedValue = cache.Get(cacheName);
 
  if (cachedValue != null)
  {
    return cachedValue;
  }
 
  var client = new HttpClient();
  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/json"));
  client.BaseAddress = new Uri("http://dev.markitondemand.com/Api/");
 
  var query = string.Format("Quote/json?symbol={0}", symbol);
  var response = client.GetAsync(query).Result;
 
  // If it fails - outputs the request string followed by the Http response
  if (response.StatusCode != System.Net.HttpStatusCode.OK)
  {
    var result = string.Format("{0}{1} n{2}", client.BaseAddress, query, response);
    throw new HttpException(result);
  }
 
  // If it gets an OK status Outputs the json/xml received
  var json = await response.Content.ReadAsStringAsync();
  var data = JsonConvert.DeserializeObject<ReaderResultSet>(json);
 
  if (data == null || data.Data == null)
  {
    return null;
  }
 
  var reading = new StockReading
    {
      Name = data.Data.Name,
      Symbol = data.Data.Symbol,
      LastPrice = data.Data.LastPrice,
      TimeStamp = DateTime.Now
    };
 
  cache.Insert(
    cacheName,
    reading,
    DateTime.Now + TimeSpan.FromMinutes(5));
 
  return reading;
}

This works well enough. The problem is that the caching implementation is sitting right there in the method and will have to be repeated for every method that we want to be cached. Then, if we want to change the way the logic works, that could mean editing many files. So, not ideal.

We’ll write our caching implementation using interception and without altering the original files.

Interception

Dynamic Proxies

Castle DynamicProxy is a library for generating lightweight .NET proxies on the fly at runtime. Proxy objects allow calls to members of an object to be intercepted without modifying the code of the class. http://docs.castleproject.org/Tools.DynamicProxy-Introduction.ashx

As it says in the description, dynamic proxies are a way of creating a lightweight wrapper around an object to intercept and alter its behaviour.

Interceptors

In Windsor, Interceptors take advantage of Dynamic Proxies to alter the functionality of a call. We can use this to wrap calls to our GetStockReading method with caching.

Here’s our Interceptor class in full:

/// <summary>
/// Interceptor for caching.
/// </summary>
public class CacheInterceptor : IInterceptor
{
  /// <summary>
  /// The cache provider implementation.
  /// </summary>
  private readonly ICacheProvider cacheProvider;
 
  /// <summary>
  /// Defines whether caching is enabled.
  /// </summary>
  private readonly bool cachingIsEnabled;
 
  /// <summary>
  /// The cache timeout.
  /// </summary>
  private readonly TimeSpan cacheTimeout;
 
  /// <summary>
  /// Initializes a new instance of the <see cref="CacheInterceptor" /> class.
  /// </summary>
  /// <param name="cacheProvider">The cache provider.</param>
  /// <param name="cachingIsEnabled">if set to <c>true</c> caching is enabled.</param>
  /// <param name="cacheTimeoutSeconds">The cache timeout seconds.</param>
  public CacheInterceptor(ICacheProvider cacheProvider, bool cachingIsEnabled, int cacheTimeoutSeconds)
  {
    this.cacheProvider = cacheProvider;
    this.cachingIsEnabled = cachingIsEnabled;
    this.cacheTimeout = TimeSpan.FromSeconds(cacheTimeoutSeconds);
  }
 
  /// <summary>
  /// Intercepts the specified invocation.
  /// </summary>
  /// <param name="invocation">The invocation.</param>
  public void Intercept(IInvocation invocation)
  {
    // check if the method has a return value
    if (!this.cachingIsEnabled || invocation.Method.ReturnType == typeof(void))
    {
      invocation.Proceed();
      return;
    }
 
    var cacheKey = BuildCacheKeyFrom(invocation);
 
    // try get the return value from the cache provider
    var item = this.cacheProvider.Get(cacheKey);
 
    if (item != null)
    {
      invocation.ReturnValue = item;
      return;
    }
 
    // call the intercepted method
    invocation.Proceed();
 
    if (invocation.ReturnValue != null)
    {
      this.cacheProvider.Insert(cacheKey, invocation.ReturnValue, this.cacheTimeout);
    }
  }
 
  private static string BuildCacheKeyFrom(IInvocation invocation)
  {
    var methodName = invocation.Method.ToString();
 
    var arguments = invocation.Arguments.Select(a => a.ToString()).ToArray();
    var argsString = string.Join(",", arguments);
 
    var cacheKey = methodName + "-" + argsString;
 
    return cacheKey;
  }
}

There’s a fair bit going on here, so let’s take it a bit at a time.

Class Definition

Our interceptor implements the IInterceptor interface which has the single method void Intercept(IInvocation invocation). We’ll get to this below.

public class CacheInterceptor : IInterceptor
{
...
}

Constructor

We’re using dependency injection to pass in instances of the objects and values our Interceptor needs.

We have an implementation of ICacheProvider that we defined above, a Boolean to indicate whether caching is enabled and the cache timeout to use in seconds. Each of these are saved to local variables.

/// <summary>
/// Initializes a new instance of the <see cref="CacheInterceptor" /> class.
/// </summary>
/// <param name="cacheProvider">The cache provider.</param>
/// <param name="cachingIsEnabled">if set to <c>true</c> caching is enabled.</param>
/// <param name="cacheTimeoutSeconds">The cache timeout seconds.</param>
public CacheInterceptor(ICacheProvider cacheProvider, bool cachingIsEnabled, int cacheTimeoutSeconds)
{
  this.cacheProvider = cacheProvider;
  this.cachingIsEnabled = cachingIsEnabled;
  this.cacheTimeout = TimeSpan.FromSeconds(cacheTimeoutSeconds);
}

Intercept

This is the code that actually intercepts and manipulates that call to our method so we’ll break it down further.

/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation.</param>
public void Intercept(IInvocation invocation)
{
...
}

First we check to see if caching is enabled. We also check to see what the return type of the called method is – if it’s void then there’s nothing to cache.

If caching is disabled or the return type is void, we just tell the original call (the invocation) to proceed as normal and exit the interceptor.

// check if the method has a return value
if (!this.cachingIsEnabled || invocation.Method.ReturnType == typeof(void))
{
  invocation.Proceed();
  return;
}

If we get beyond that, we’re going to be interacting with the cache. So we need to construct a unique key for the cache object for lookups.

var cacheKey = BuildCacheKeyFrom(invocation);

The BuildCacheKeyFrom(invocation) just takes the name of the called method and appends the parameters to it. This would need to be refactored and cleaned up for a larger application but it’ll do for our purposes for the moment.

private static string BuildCacheKeyFrom(IInvocation invocation)
{
  var methodName = invocation.Method.ToString();
 
  var arguments = invocation.Arguments.Select(a => a.ToString()).ToArray();
  var argsString = string.Join(",", arguments);
 
  var cacheKey = methodName + "-" + argsString;
 
  return cacheKey;
}

Once we have a key, we check the cache to see if that item already exists. If so, we set the return value of the method to that and exit. This causes the intercepted method to complete and return the value we’ve just set.

// try get the return value from the cache provider
var item = this.cacheProvider.Get(cacheKey);
 
if (item != null)
{
  invocation.ReturnValue = item;
  return;
}

If the cache didn’t contain a value, we need to run the method as normal then cache the result.

// call the intercepted method
invocation.Proceed();
 
if (invocation.ReturnValue != null)
{
  this.cacheProvider.Insert(
    cacheKey,
    invocation.ReturnValue,
    this.cacheTimeout);
}

Hooking All of This Together

The glue for hooking up this Interceptor can be found in the installer for the services. If you’re not familiar with Windsor Installers, have a look at Windsor Tutorial – Part Three – Writing Your First Installer.

Before hooking up anything with the interceptors, our installer looked like this:

public class ServiceInstaller : IWindsorInstaller
{
  /// <summary>
  /// Performs the installation in the <see cref="T:Castle.Windsor.IWindsorContainer"/>.
  /// </summary>
  /// <param name="container">The container.</param>
  ////<param name="store">The configuration store.</param>
  public void Install(IWindsorContainer container, IConfigurationStore store)
  {
    container.Register(
      Component.For<IStockServices>().ImplementedBy<StockServices>().LifestylePerWebRequest());
  }
}

All we’re doing is specifying the implementation for IStockServices.

To that, we need to add our cache provider:

Component.For<ICacheProvider>().ImplementedBy<WebCacheProvider>().LifestyleSingleton()

The implementation of WebCacheProvider is very simple:

public class WebCacheProvider : ICacheProvider
{
  /// <summary>
  /// Gets the specified cache item using the key.
  /// </summary>
  /// <param name="cacheKey">The cache key.</param>
  /// <returns>
  /// The object cached with the specified key.
  /// </returns>
  public object Get(string cacheKey)
  {
    return HttpRuntime.Cache[cacheKey];
  }
 
  /// <summary>
  /// Inserts the specified item into the cache.
  /// </summary>
  /// <param name="cacheKey">The cache key.</param>
  /// <param name="item">The item to cache.</param>
  /// <param name="cacheExpiry">The cache expiry timeout.</param>
  public void Insert(string cacheKey, object item, TimeSpan cacheExpiry)
  {
    HttpRuntime.Cache.Insert(
      cacheKey,
      item,
      null,
      DateTime.Now + cacheExpiry,
      TimeSpan.Zero);
  }
}

We also need to add a registration for our Interceptor:

Component.For<CacheInterceptor>().LifestyleTransient().DependsOn(
  Dependency.OnAppSettingsValue("cacheTimeoutSeconds"),
  Dependency.OnAppSettingsValue("cachingIsEnabled"))

If you remember, our constructor for CacheInterceptor, it looked like this:

public CacheInterceptor(ICacheProvider cacheProvider, bool cachingIsEnabled, int cacheTimeoutSeconds)
{
  this.cacheProvider = cacheProvider;
  this.cachingIsEnabled = cachingIsEnabled;
  this.cacheTimeout = TimeSpan.FromSeconds(cacheTimeoutSeconds);
}

The values for cachingIsEnabled and cacheTimeoutSeconds are read in from web.config using the Dependency.OnAppSettingsValue(string) in the installer. These values are just set up like so:

<appSettings>
  <add key="cachingIsEnabled" value="true"/>
  <add key="cacheTimeoutSeconds" value="60"/>
  ...
</appSettings>

The last step is to tell Windsor to use the Interceptor for IStockServices. So our full installer code becomes:

public void Install(IWindsorContainer container, IConfigurationStore store)
{
  container.Register(
    Component.For<CacheInterceptor>().LifestyleTransient().DependsOn(Dependency.OnAppSettingsValue("cacheTimeoutSeconds"), Dependency.OnAppSettingsValue("cachingIsEnabled")),
    Component.For<ICacheProvider>().ImplementedBy<WebCacheProvider>().LifestyleSingleton(),
    Component.For<IStockServices>().ImplementedBy<StockServices>().LifestylePerWebRequest().Interceptors<CacheInterceptor>());
}

And that’s it. Any calls to a method of IStockServices will be wrapped in the interceptor.

Sample Code

Sample code using this approach is available on GitHub.