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!)