C# Cache Helper Class

Do you need a quick and cache wrapper class? Here’s a static class which I included in my more recent C# web application.  You’ll notice the class uses generics to allow for some, umm, generic functionality. 

public static class CacheHelper
{
    /// <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>
    public static void Add<T>(T o, string key) where T : class
    {
        // NOTE: Apply expiration parameters as you see fit.
        // In this example, I want an absolute 
        // timeout so changes will always be reflected 
        // at that time. Hence, the NoSlidingExpiration.  
        HttpContext.Current.Cache.Insert(
            key, 
            o, 
            null,
            DateTime.Now.AddMinutes(
                ConfigurationHelper.CacheExpirationMinutes),
            System.Web.Caching.Cache.NoSlidingExpiration);
    }

    /// <summary>
    /// Remove item from cache 
    /// </summary>
    /// <param name="key">Name of cached item</param>
    public static void Clear(string key)
    {
        HttpContext.Current.Cache.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 HttpContext.Current.Cache[key] != null;
    }

    /// <summary>
    /// Retrieve cached item
    /// </summary>
    /// <typeparam name="T">Type of cached item</typeparam>
    /// <param name="key">Name of cached item</param>
    /// <returns>Cached item as type</returns>
    public static T Get<T>(string key) where T : class
    {
        try
        {
            return (T) HttpContext.Current.Cache[key];
        }
        catch
        {
            return null;
        }
    }
}

And here is a relatively standard sample usage of the library. 

public override List<Employee> GetEmployeeList()
{
    string key = ConfigurationHelper.CacheKeyEmployeeList;

    List<Employee> employees = CacheHelper.Get<List<Employee>>(key);

    if (employees == null)
    {
        employees = instance.GetEmployeeList();
        CacheHelper.Add(employees, key);
    }

    return employees;
}

Notice how I’m grabbing the cached value, storing it in a local variable and then checking if it is equal to null rather than using the CacheHelper.Exists() method.  If I used the CacheHelper.Exists() method, the cached object could expire between the time I check its existence and the time I get its value through the CacheHelper.Get() method.  Therefore, the above approach is the safest strategy to use when retrieving cached values.  CacheHelper.Exists() should really only be used for quick existence checks which are unrelated to the fetch.

But if you want to use the code CORRECTLY there’s a catch.  Did you notice the “class” constraint on the CacheHelper.Get() and CacheHelper.Add() methods?   I did this because you can’t always return null from a generic method.  If the return type were always a reference type it would be fine, but comparing a non-nullable value type to null would throw a runtime exception or would always evaluate to false.  Therefore, I’ve constrainted CacheHelper which limits its functionality but unsure safe use of the cache.  If you’re feeling dangerous, you’re welcome to remove the constraints.

I’ll update the library once I come up with a good work around.  Speaking of, any suggestions?

Let me know if you have any questions and/or this type of post is helpful.  Thanks.

UPDATE 12/10:

After further review, I’ve removed the “class” constraint from the CacheHelper.  The Get() method now follows the common Try(x, out y) pattern where x is what you wish to operate on, y is the resulting value and the method return success or failure.  Here’s a sample call:

string key = "EmployeeList";
List<Employee> employees;

if (!CacheHelper.Get(key, out employees))
{
    employees = DataAccess.GetEmployeeList();
    CacheHelper.Add(employees, key);
    Message.Text =
        "Employees not found but retrieved and added to cache for next lookup.";
}
else
{
    Message.Text = "Employees pulled from cache.";
}

And here’s the updated class.  Note, I’ve also provided a sample project for download.  Thanks for your comments!

using System;
using System.Web;

public static class CacheHelper
{
    /// <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>
    public static void Add<T>(T o, string key) 
    {
        // NOTE: Apply expiration parameters as you see fit.
        // I typically pull from configuration file.

        // In this example, I want an absolute
        // timeout so changes will always be reflected
        // at that time. Hence, the NoSlidingExpiration.
        HttpContext.Current.Cache.Insert(
            key,
            o,
            null,
            DateTime.Now.AddMinutes(1440),
            System.Web.Caching.Cache.NoSlidingExpiration);
    }

    /// <summary>
    /// Remove item from cache
    /// </summary>
    /// <param name="key">Name of cached item</param>
    public static void Clear(string key)
    {
        HttpContext.Current.Cache.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 HttpContext.Current.Cache[key] != null;
    }

    /// <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) HttpContext.Current.Cache[key];
        }
        catch
        {
            value = default(T);
            return false;
        }

        return true;
    }
}

Download CacheHelper Sample Project:CacheHelperClass.zip

 

Comments

  1. How about a bool TryGetValue(string key, out TValue value) method? As it follows the tryget pattern it would simplify retrieving values and running some code conditionally only if the value exists and is valid.

  2. Thanks for the suggestion, Waldek. I updated the class using this approach last night and it is much cleaner. Thanks for validating my second attempt. I’ll post the updated code shortly.

  3. Thanks for the great post. Is it possible to add the OnRemove even to this class?
    I want to call a function and repopulate the cache when it is removed.

  4. @Shuaib – Neat idea but this class is entirely static and it merely wraps the HttpContent cache object. An OnRemove event wouldn’t really work without some serious re-architecture. Thanks for the question.

  5. H! can i determine the size of each item in the cache?
    I followed the linked but i can’t understand, what’s happening there..I mean where i can run the command..

    Thanks..

  6. I think how your caching here is wrong, your using generics in the wrong way.

    This method appears to be strongly typed to the caller, but it’s using a Cast underneath.

    public static bool Get(string key, out T value)
    {
    //…

    value = (T) HttpContext.Current.Cache[key];

    //…
    }

  7. @Vince, Thanks for the comment, but I’m not sure there’s a way around this due to the way data is stored in Cache, Session, Profile, etc. How would you implement the generic Get() without casting the value when pulled from Cache?

  8. Have you ever thought about publishing an ebook or guest authoring on other websites? I have a blog based upon on the same information you discuss and would really like to have you share some stories/information. I know my visitors would appreciate your work. If you’re even remotely interested, feel free to send me an email.

closed