Stefan Holm Olsen

Performance tip: Use good old ContextCache for request-scoped caching

Sometimes we need to load, calculate or resolve a value more than once in a request. Maybe your site needs to fetch a value, by loading the start page, taking a ContentReference value, loading that content and then return a property value from it. Or maybe you need to fetch and show the name of the current customer in more than one place.

Almost all Optimizely content is cached internally and loads fast. But still, if we perform loading and transformation more than once per request, it might be beneficial to cache it briefly (if only for the lifetime of the current request).

Optimizely CMS has a less-known class, called ContextCache (exists in the EPiServer.Framework namespace). This can be injected into any class and contains a dictionary that exists for the lifetime of the current request. When the request is done, the contents are scrapped (although not disposed).

Optimizely CMS uses it internally for things like resolving the current site definition or matching visitor groups. These are examples of values that:

  1. Are heavy to resolve or compute
  2. Can be retrieved many times for a single request
  3. Does not change on repeated calls in the same request

Of course, we can do similar lookup tables in classes on our own. But this one is thread-safe and can be replaced in unit test scenarios.

A practical example

A site may render the customer's name in both header, footer and in a tracking code. A simple implementation could look like this:

using EPiServer.Framework;
using Mediachase.Commerce.Customers;

namespace DemoSite.Customers;

public class CustomerService
{
private const string CustomerNameCacheKey = "CustomerName";
private readonly ContextCache _contextCache;

public CustomerService(ContextCache contextCache) => _contextCache = contextCache;

public string GetCustomerNameDirect() => CustomerContext.Current.CurrentContactName;

public string GetCustomerNameCached()
{
var customerName = _contextCache[CustomerNameCacheKey] as string;
if (customerName == null)
{
customerName = CustomerContext.Current.CurrentContactName;
_contextCache[CustomerNameCacheKey] = customerName;
}

return customerName;
}
}

In the first method, GetCustomerNameDirect, the method always calls into CustomerContext, which calls into the cache and returns the object. Only then can we get the value of the name. If it is called three times, then it makes three calls to CustomerContext.

In the second method, GetCustomerNameCached, the method only calls into CustomerContext the first time. After that, the customer name is only loaded from ContextCache. On page refresh, it starts over.

Another example could be a multi-store Commerce site, where each site uses its own Commerce catalog. Resolving the current catalog from a start page property requires two content load operations to complete. And it may have to be performed several times per request. So, this would be a candidate for ContextCache.

There are many other use-cases for this. But try to cache only simple values in this.

Do you know any other hidden but helpful classes in the Optimizely framework?