Reusable HttpContext Cache Maintainer

      No Comments on Reusable HttpContext Cache Maintainer

I found this in some C# website code I inherited:

        protected List<Item> ImportantList
        {
            get
            {
                List<Item> value = HttpContext.Current.Cache.Get("ImportantList") as List<Item>;

                if (value == null)
                {
                    List<Item> list = SomeClass.SomeExpensiveStaticFunction();

                    HttpContext.Current.Cache.Add("ImportantList", list, null, DateTime.Now.AddMinutes(30), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);
                }

                return HttpContext.Current.Cache.Get("ImportantList") as List<Item>;
            }
        }

This isn’t the end of the world and I’ve probably written blocks like this myself. There are some superficial changes like eliminating the second “list” variable or not retrieving from the cache the second time.  But it gets really messy when you see three or more of these kinds of blocks all in a row.  The repeated elements just cry out to be standardized.  So you could try to do something like this:

        private List<Item> CacheCheck(string cacheKey, Func<List<Item>> func)
        {
            List<Item> result = HttpContext.Current.Cache.Get(cacheKey) as List<Item>;

            if (result == null)
            {
                result = func.Invoke();

                HttpContext.Current.Cache.Add(cacheKey, result, null, DateTime.Now.AddMinutes(30), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);
            }

            return result;
        }
        
        protected List<Item> ImportantList
        {
            get
            {
                return CacheCheck("ImportantList", () => SomeClass.SomeExpensiveStaticFunction());
            }
        }

This is working a little better.  If we need to maintain a few more instances of things like “ImportantList”, it’s just a case of defining another property that wraps a call to CacheCheck with an anonymous function or lambda expression.

But it still feels like we could make something more reusable.  Plus, the use of an anonymous function, while handy, feels kind of like cheating.  We should have something a bit more universal so the same logic could be applied to other languages.  This current solution is also bound to this one particular class and we likely could use this in so many other places.  It wouldn’t be enough to drop it down into a base class because that limits it to only certain aspects of the application.

Let’s break out the core logic into a class and an interface:

    public interface IValueProvider<out T>
    {
        T GetValue();
    }

    public class CachedValueMaintainer<TValue, TProvider>
        where TValue : class
        where TProvider : IValueProvider<TValue>, new()
    {
        public static TValue GetValue(string cacheKey, TProvider provider)
        {
            TValue result = HttpContext.Current.Cache.Get(cacheKey) as TValue;
            if (result != null)
            {
                return result;
            }

            result = provider.GetValue();
            HttpContext.Current.Cache.Add(cacheKey, result, null, DateTime.Now.AddMinutes(30), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);

            return result;
        }

        public static TValue GetValue(string cacheKey)
        {
            return GetValue(cacheKey, new TProvider());
        }
    }

So now if we want an implementation of this mechanism, we simply define a “provider” class:

    public class ImportantListProvider : IValueProvider<List<Item>>
    {
        public List<Item> GetValue()
        {
            return SomeExpensiveTask();
        }
    }

And when we need the value in question, just call the maintainer, with or without an instance of our provider class:

        protected List<Item> ImportantList
        {
            get
            {
                return CachedValueMaintainer<List<Item>, ImportantListProvider>.GetValue("ImportantList");
            }
        }

Now our solution is more testable, reusable, interchangeable and readable. We have spread the logic for our original class to several others but many would advocate that’s the cost of a more robust solution.

Leave a Reply