A Type-Safe Cache with Delegates for ASP.NET (also MVC)

Caching is great. It’s what allows all of our favorite websites to run lightning-fast. ASP.NET has a robust caching system built in which can hold value types or serializable reference types. It can also perform caching of output from an ActionResult or PartialViewResult rendered from a controller action method.

However, the existing ASP.NET caching has some fairly severe limitations, especially if you are trying to use the Cache API directly and not the handy-dandy [OutputCache] attribute. The two patterns I find myself repeating over and over are:

  • Checking if an item exists in the cache, then writing it if not
  • Casting an item once I receive it back from the cache

I have previously used a class called CacheHelper from Ben Griswold which works great for the casting problem. However, it does not address the pattern of check, add, repeat. I’ve recently been getting more comfortable with Delegates and Expressions in C#, and this seemed like a great opportunity to put that knowledge to use.

My solution uses an Expression<Func<T>> to fetch results. The main implementation method is as follows:

        private static int DefaultCacheTime { get { return Properties.Settings.Default.DefaultCacheMinutes; } }
        private static Cache HttpCache { get { return HttpContext.Current == null ? null : HttpContext.Current.Cache; } }

        public T Get(string key, int expirationMinutes, Expression> expr)
        {
            // Get the delegate to evaluate
            var func = expr.Compile();

            // If we don't have a cache, just return
            if (HttpCache == null) return func();

            // Default the cache time if we didn't receive it
            if (expirationMinutes == 0) expirationMinutes = DefaultCacheTime;

            var item = Get(key);
            if (item == null || item.Equals(default(T)))
            {
                item = func();
                HttpCache.Insert(
                    key,
                    item,
                    null,
                    DateTime.Now.AddMinutes(expirationMinutes),
                    Cache.NoSlidingExpiration
                );
            }
            return item;
        }

This works great, but it leaves unsolved another issue, which I despise: having to create a string key for each item you want to retrieve from the cache. Luckily, we can use the Expression to generate a string key for us! Specifically, we inspect the body of the expression and just use that as the key. There are also a couple of overloads for convenience.

        public T Get(Expression> expr)
        {
            return Get(DefaultCacheTime, expr);
        }

        public T Get(string key, Expression> expr)
        {
            return Get(key, DefaultCacheTime, expr);
        }

        public T Get(int expirationMinutes, Expression> expr)
        {
            var key = GetKey(expr);
            return Get(key, expirationMinutes, expr);
        }

Our key function generates a string that should be unique per expression, but may turn out to collide if the text of two expressions are identical but used in different contexts. I haven’t found a solution to that issue yet – if you have one, please offer it!

        private static string GetKey(Expression> expr)
        {
            var body = expr.Body.ToString();
            var bodyBytes = Encoding.UTF8.GetBytes(body);
            var CryptoProvider = new SHA1CryptoServiceProvider();
            string bodyHash = BitConverter.ToString(CryptoProvider.ComputeHash(bodyBytes));
            var typeName = typeof (T).Name;
            return String.Concat(typeName, "-", bodyHash);
        }

Below is the complete interface for the caching service – the remaining methods should be easily implementable for those who need them.

    public interface ICacheService
    {
        T Get(Expression> expr);
        T Get(string key);
        T Get(string key, Expression> expr);
        T Get(int expirationMinutes, Expression> expr);
        T Get(string key, int expirationMinutes, Expression> expr);
        void Add(object item, string key);
        void Delete(String key);
        void Delete(Expression> expr);
    }
Posted Wednesday, February 22nd, 2012 under ASP.NET MVC.

Leave a Reply