Wednesday 27 April 2011

AOP - caching with postsharp

Aspect oriented programming is a nice way of keeping your code clean.  Essentially what it allows us to do,  is place various attributes on a method of class declaration which can perform various actions for us without cluttering up our logic.

The example below has a cache aspect declared meaning that the data returned can be cached.

  [Cache(CacheType.Absolute, 120)]  
     public Dictionary<string, string> GetData(string rawUrl)  
     {  

Postsharp extends msbuild and as a result it does extend a compile time a little bit, but it is not obviously noticeable. Using postsharp, we can  hook into the events that happen before and after a method is called. In this case we are intercepting before a method is called.

  [Serializable]  
   public sealed class CacheAttribute : MethodInterceptionAspect  
   {  
     private readonly CacheType m_cacheType;  
     private readonly int m_expiry;  
     public CacheAttribute(CacheType cacheType, int expiry)  
     {  
       m_cacheType = cacheType;  
       m_expiry = expiry;  
     }  
     public override void OnInvoke(MethodInterceptionArgs context)  
     {  
       object value;  
       var key = GenerateKey(context);  
       if (!CacheHelper.Get(key, out value))  
       {  
         // Do lookup based on caller's logic.   
         context.Proceed();  
         value = context.ReturnValue;  
         CacheHelper.Add(value, key, m_cacheType, m_expiry);  
       }  
       context.ReturnValue = value;  
     }  
     private static string GenerateKey(MethodInterceptionArgs context)  
     {  
       var keyBuilder = new StringBuilder();  
       keyBuilder.Append(context.Method.GetHashCode());  
       foreach (var arg in context.Arguments.ToArray())  
       {  
         keyBuilder.Append(arg.GetHashCode());  
       }  
       return keyBuilder.ToString();  
     }  
   }  

Here is the CacheHelper class referenced above:

 public enum CacheType  
   {  
     Absolute,  
     Sliding  
   }  
   public static class CacheHelper  
   {  
     private static CacheItemPolicy GetCachePolicy(CacheType type, int expiry)  
     {  
       var policy = new CacheItemPolicy();  
       switch (type)  
       {  
         case (CacheType.Absolute):  
           policy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(expiry);  
           break;  
         case (CacheType.Sliding):  
           policy.SlidingExpiration = new TimeSpan(0, 0, 0, expiry);  
           break;  
       }  
       return policy;  
     }  
     /// <summary>  
     /// Insert value into the cache using  
     /// appropriate name/value pairs  
     /// </summary>  
     /// <typeparam name="T">Type of cached item</typeparam>  
     /// <param name="o">Item to be cached</param>  
     /// <param name="key">Name of item</param>  
     /// <param name="cacheType">Cache Expiration type</param>  
     /// <param name="expiry">Expiry time in seconds</param>  
     public static void Add<T>(T o, string key, CacheType cacheType, int expiry)  
     {  
       var cacheItem = new CacheItem(key, o);  
       MemoryCache.Default.Add(cacheItem, GetCachePolicy(cacheType, expiry));  
     }  
     /// <summary>  
     /// Remove item from cache  
     /// </summary>  
     /// <param name="key">Name of cached item</param>  
     public static void Clear(string key)  
     {  
       MemoryCache.Default.Remove(key);  
     }  
     /// <summary>  
     /// Check for item in cache  
     /// </summary>  
     /// <param name="key">Name of cached item</param>  
     /// <returns></returns>  
     public static bool Exists(string key)  
     {  
       return MemoryCache.Default.Contains(key);  
     }  
     /// <summary>  
     /// Retrieve cached item  
     /// </summary>  
     /// <typeparam name="T">Type of cached item</typeparam>  
     /// <param name="key">Name of cached item</param>  
     /// <param name="value">Cached value. Default(T) if item doesn't exist.</param>  
     /// <returns>Cached item as type</returns>  
     public static bool Get<T>(string key, out T value)  
     {  
       try  
       {  
         if (!Exists(key))  
         {  
           value = default(T);  
           return false;  
         }  
         value = (T)MemoryCache.Default.Get(key);  
       }  
       catch  
       {  
         value = default(T);  
         return false;  
       }  
       return true;  
     }  
   }  

So anytime I want to add caching to a repository now, I just add this aspect (attribute) to my method. The same can be applied for concepts just as easily for logging and transaction handling.

(Neil Duncan wrote this code originally btw!)

No comments:

Post a Comment