Cache manager easing cache handling for EPiServer 7.5 with ISynchronizedObjectInstanceCache

We had this splendid little cache manager in my previous project that simplified handling caching for us. It was written by a former collegue of mine, Patrik Akselsson, and last week I found a need for a similar thing at my new client’s. Looking back at what I already had, I set about making a similar CacheManager that would work with the new EPiServer 7.5 environment.

Using the EPiServer 7.5 ISynchronizedObjectInstanceCache creating a simple CacheManager

The first thing needing to be created is an interface exposing methods and allowing for different implementations of the CacheManager; both for helping with mocking when writing unit tests as well as the real concrete manager class.

ICache.cs

public interface ICache<T>
{
  T this[string key] { get; set; }
  T Get(string key, Func<T> factory);
  void Remove(string key);
  void InvalidateCachedObjects();
}

The Get method is what will mainy be used when working with this cache manager. It takes a key value, which is a string, as well as a Func defining how to generate the data to be cached in the event that the key does not already exist. Using it is rather straight forward.

public void SomeMethod()
{
  var cache = ServiceLocator.Current .GetInstance<ICache<SomeObject>>();
  var someData = cache.Get("SomeKey", GetSomeData);
}

private SomeObject GetSomeData()
{
  // Advanced logic giving data needing to be cached
  return new SomeObject();
}

The GetSomeData method is what creates the actual data that needs to be cached, and the CacheManager’s Get method will use it as a factory to populate the cache should the key not already exist. In which case, the cached data will be returned instead.

CacheManager.se

public class CacheManager<T> : ICache<T> where T : class
{
  private readonly TimeSpan relativeExpiration;
  private readonly string[] cacheDependencyKeys;
  private readonly ISynchronizedObjectInstanceCache objectInstanceCache;

  public CacheManager() : this(Settings.Instance.HttpCacheExpiration) { }

  public CacheManager(TimeSpan relativeExpiration)
  {
    objectInstanceCache = ServiceLocator.Current .GetInstance<ISynchronizedObjectInstanceCache>();
    cacheDependencyKeys = new[]
      {
        DataFactoryCache.VersionKey,
        InstanceKey,
      };

    InitializeCacheDependency();
    this.relativeExpiration = relativeExpiration;
  }

The first part of the CacheManager implementation specifies cache keys, sets the cache expiration time and initializes the cache dependency. Normally I would likely inject EPiServer’s ISynchronizedObjectInstanceCache in a constructor argument rather than using the ServiceLocator, but this way it’s probably more clear (see this previous article if you’re interested in setting up your own StructureMap dependency resolver). The cache expiration used will be EPiServer’s own Settings.InstanceHttpCacheExpiration; which is specified as an attribute called httpCacheExpiration on applicationSettings in the episerver.config configuration file; please see the applicationSettings section of Configuring EPiServer for more information. As you can see in the code above, EPiServer’s database version key is used as a cache dependency, which will result in the cache being cleared when editors publish new content.

  public T this[string key]
  {
    get { return objectInstanceCache.Get(GetCacheKey(key)) as T; }
    set
    {
      var evictionPolicy = new CacheEvictionPolicy(cacheDependencyKeys, relativeExpiration, CacheTimeoutType.Absolute);
      objectInstanceCache.Insert(GetCacheKey(key), value, evictionPolicy);
    }
  }

There is not much special about the property above; it simply uses EPiServer’s own ISynchronizedObjectInstanceCache to get and insert cache values. A new thing compared to previous versions of EPiServer is the CacheEvictionPolicy. It takes a set of cache dependency keys, a relative expiration TimeSpan as well as a value saying if the timeout should be absolute or sliding. If you choose absolute, EPiServer’s cache manager will add your time span to the current time and use the result as the expiration.

  public T Get(string key, Func<T> factory)
  {
    var result = this[key];
    if (result == null)
    {
      result = factory();
      this[key] = result;
    }
    return result;
  }

The Get method contains what most people probably would do when implementing this sort of cache; a cache retrieval followed by a simple null check to determine if they need to generate a new object for the cache, or if just return the cached data.

  public void Remove(string key)
  {
    objectInstanceCache.Remove(GetCacheKey(key));
  }

  public void InvalidateCachedObjects()
  {
    InitializeCacheDependency();
  }

  private string InstanceKey
  {
    get { return typeof(T).FullName; }
  }

  private void InitializeCacheDependency()
  {
    objectInstanceCache.Insert(InstanceKey, DateTime.Now.Ticks, null);
  }

  private static string GetCacheKey(string key)
  {
    return typeof(T).FullName + "_" + key;
  }
}

The rest of the methods are pretty self explaining; Remove removes an object with a given key from the cache, InvalidateCachedObjects clears all the objects of the current type T and GetCacheKey simply adds the type name to the cache key making it exclusive to the current type. The InstanceKey and InitializeCacheDependency are used to help with invalidating cache.

Getting all this to work, you would probably also need to tell StructureMap a thing or two of what to do when faced with our ICache interface.

For(typeof(ICache<>)).Singleton().Use(typeof(CacheManager<>)) .Child(typeof(TimeSpan)) .Is(Settings.Instance.HttpCacheExpiration);