Simple local caching CommerceRuntime service (LocalCacheService)




Note: This can be implemented with any Dynamics 365 or older CommerceRuntime versions. Sample code can be downloaded at the end of this blog.

In some extensibility scenarios it may be useful to cache data in CRT memory. It could be data that may need to be frequently reused and does not change often, especially, if it may be expensive to calculate the data or fetch the data from the database. There are multiple examples for cases when we may need such a service.

One example could be pieces of secure information from HQ (AX) that we do not want to store in the channel database (i.e. SecureAppSettings sample).

Another example may be the fetching of product attributes in multiple extension dlls. In some places we have the attribute values already available, in others we do not, so it would have to be re-queried (i.e. SimpleProduct).  One could off course extend the CRT further to pass the queried product attribute down to where we need it later (via extension properties), but I have found it may be sometimes more trouble than it is worth. Even further, if we have multiple RetailServer requests involved, extension properties are not automatically persisted, so we would have to find another place to save this state.

In these and other examples, a simple cache makes the coding simpler.  Just make sure the data that get cached does not change very frequently. One thing to note is that this cache lives in the memory of the host of the CRT (RetailServer or MPOSOffline), so in a production environment there will be multiple cache instances, one for each host process. In many cases, this may not really matter much. In other cases, it may be better to use a distributed cache instead (topic for a future blog).

This simple local cache solution is based on the .NET MemoryCache object.  No SQL is needed. A client can use a single line to fetch (try to fetch) the data with this code:

var getFromCacheRequest = new GetFromLocalCacheRequest(cacheKeyExternalProductTaxId);
object cachedValueExternalProductTaxId = context.Runtime.Execute(getFromCacheRequest, context).Value;
if (cachedValueExternalProductTaxId != null)
{
   // 
}

and can push a new item onto the cache with this code:

//cache productId to externalProductTaxId mapping for 30 min
var saveToCacheRequest = new SaveToLocalCacheRequest(cacheKeyExternalProductTaxId, externalProductTaxId, 30 * 60);
context.Runtime.Execute(saveToCacheRequest, context);

The LocalCacheService implements as usual for CRT services the IREquestHandler. Below is some of the crucial code for reference. In case you want to look at all the code (or reuse it in some of your projects), download the zipped up version below.

public Response Execute(Request request)
{
    Type requestType = request.GetType();
    Response response;
    if (requestType == typeof(GetFromLocalCacheRequest))
    {
        var cacheKey = ((GetFromLocalCacheRequest)request).Key;
        var cacheValue = cache.Get(cacheKey);
        string logMessage = string.Empty;
        if (cacheValue != null)
        {
            logMessage = "GetFromLocalCacheRequest successfully fetched item from cache for key '{0}'.";
        }
        else
        {
            logMessage = "GetFromLocalCacheRequest could not find item in cache for key '{0}'.";
        }

        RetailLogger.Log.ExtendedInformationalEvent(logMessage, cacheKey);
        response = new GetFromLocalCacheResponse(cacheValue);
    }
    else if (requestType == typeof(SaveToLocalCacheRequest))
    {
        var cacheKey = ((SaveToLocalCacheRequest)request).Key;
        var cacheValue = ((SaveToLocalCacheRequest)request).Value;
        var cacheLifeInSeconds = ((SaveToLocalCacheRequest)request).CacheLifeInSeconds;
        cache.Set(cacheKey, cacheValue, DateTimeOffset.Now.AddSeconds(cacheLifeInSeconds));
        RetailLogger.Log.ExtendedInformationalEvent("SaveToLocalCacheRequest saved item for key '{0}'", cacheKey);
        response = new NullResponse();
    }
    else
    {
        throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Request '{0}' is not supported.", request.GetType()));
    }

    return response;
}

Extensions.MyLocalCacheServiceBlog