Share

LinkedIn

Sitecore Caching

Sitecore has several layers of caching. However, it is sometimes necessary to add your own caching as well.

For the most part, this would only be necessary if you are either retrieving additional content from an external source or doing some processing of Sitecore content that you want to avoid repeating. This post will show a couple of options for implementing caching that is invalidated by a publishing operation.

CustomCache Class
The Sitecore API actually includes a decent starting point for your caching needs. The abstract class Sitecore.Caching.CustomCache provides most of the caching functionality that you will need. Max at Image0 blogged about this approach a couple of years ago. My version of this has mostly subtle differences, but there is one exception. I have added event handling to clear the cache after a publish job. I was surprised to find that this is not provided by the CustomCache class already, but it is simple enough to add. Since I can’t think of any reason why someone wouldn’t want this cache to be cleared on publish, I opted for registering the event handler in the constructor as opposed to the configuration file.

namespace Aws.BlogPosts.SitecoreCaching
{
    public class ExpensiveContentCache : Sitecore.Caching.CustomCache
    {
        public ExpensiveContentCache()
            : base(
                "ExpensiveContentCache",
                Sitecore.StringUtil.ParseSizeString(
                    Sitecore.Configuration.Settings.GetSetting("Caching.JobSearchCacheSize")))
        {
            Sitecore.Events.Event.Subscribe("publish:end", this.OnPublishEnd);
            Sitecore.Events.Event.Subscribe("publish:end:remote", this.OnPublishEnd);
        }
  
        protected virtual void OnPublishEnd(object sender, System.EventArgs eventArgs)
        {
            this.Clear();
        }
  
        public ExpensiveContentModel GetExpensiveContent(string key)
        {
            return this.GetObject(key) as ExpensiveContentModel;
        }
  
        public void AddExpensiveContent(string key, ExpensiveContentModel content)
        {
            this.SetObject(key, content, this.EstimateSizeOfExpensiveContent(content));
        }
  
        private long EstimateSizeOfExpensiveContent(ExpensiveContentModel content)
        {
            var size = Sitecore.Reflection.TypeUtil.SizeOfObject();
  
            // add more for data stored in object fields
  
            return size;
        }
    }
}

Note that when using the SetObject method that you have to provide an estimated size of the object you are adding to the cache. The Sitecore.Reflection.TypeUtil class can help you build your estimate. You will need to add up the estimated sizes of all data stored in the fields of your object. It is better to estimate high since on 32-bit machines, you can get out of memory exceptions by estimating too low. On a 64-bit machine with cache size limits disabled, this is just wasted effort.

PublishCacheDependency
The CustomCache class works well, but it requires a fair amount of code. Also estimating the size of the objects that you are putting into the cache is an unnecessary pain. It would be nice if we could use the normal ASP.NET cache but have it cleared after publishing. This is surprisingly easy to achieve by creating a class that inherits from System.Web.Caching.CacheDependency. The PublishCacheDependency class below simply registers itself for the publish:end and publish:end:remote events. The handler for these events just calls the NotifyDependencyChanged event that is inherited from the base class. It also overrides the DependencyDispose method so it can unsubscribe when the job is done.

namespace Aws.BlogPosts.SitecoreCaching
{
    public class PublishCacheDependency : System.Web.Caching.CacheDependency
    {
        public PublishCacheDependency()
        {
            Sitecore.Events.Event.Subscribe("publish:end", this.PublishCacheDependency_OnPublishEnd);
            Sitecore.Events.Event.Subscribe("publish:end:remote", this.PublishCacheDependency_OnPublishEnd);
        }
  
        public void PublishCacheDependency_OnPublishEnd(object sender, System.EventArgs eventArgs)
        {
            this.NotifyDependencyChanged(sender, eventArgs);
        }
  
        protected override void DependencyDispose()
        {
            Sitecore.Events.Event.Unsubscribe("publish:end", this.PublishCacheDependency_OnPublishEnd);
            Sitecore.Events.Event.Unsubscribe("publish:end:remote", this.PublishCacheDependency_OnPublishEnd);
        }
    }
}

Note that the name of event handler method includes the name of the class. This is to ensure that the method has a unique name. As I was writing this post, I discovered that the Event.Unsubscribe method contains a bug that causes any event handlers that share a name to be unsubscribed simultaneously. That is, if I have several classes that register an event handler with the name OnPublishEnd and then unsubscribe one of them, they will all be unsubscribed.

Using it
Rather than having a dedicated CacheManager class, I prefer to have a manager class for the content. This class generally knows how to get the content, process it, and cache it. The caching is encapsulated so that the consumer doesn’t need to know if or how it is implemented.

namespace Aws.BlogPosts.SitecoreCaching
{
    using System;
  
    public class ExpensiveContentManager
    {
        private static readonly ExpensiveContentCache Cache = new ExpensiveContentCache();
  
        public ExpensiveContentModel GetSomeExpensiveContent(string someParameter)
        {
            var key = this.GetKey(someParameter);
            var content = Cache.GetExpensiveContent(key);
            if (content == null)
            {
                content = this.GetAndProcessContent();
                Cache.AddExpensiveContent(key, content);
            }
  
            return content;
        }
  
        public ExpensiveContentModel GetSomeExpensiveContent2(string someParameter)
        {
            var key = this.GetKey(someParameter);
            var cache = System.Web.HttpRuntime.Cache;
            var content = cache[key] as ExpensiveContentModel;
            if (content == null)
            {
                content = this.GetAndProcessContent();
                cache.Insert(key, content, new PublishCacheDependency());
            }
  
            return content;
        }
  
        private ExpensiveContentModel GetAndProcessContent()
        {
            throw new NotImplementedException();
        }
  
        private string GetKey(string parameter)
        {
            throw new NotImplementedException();
        }
    }
}

You can see that both options are very similar from the perspective of the consumer, but the CustomCache class requires quite a bit more code. The PublishCacheDependency class can even be added to your Sitecore library and reused all over the place.

Why then would you use the CustomCache approach? I can only think of one reason. When you instantiate a CustomCache class, it is registered with Sitecore’s CacheManager. This would allow you to have your cache cleared when the CacheManager.ClearAllCaches method is called. However, that method does not get called very often. It should almost never get called on a CD server.

If the content that you are caching is used only for a front-end website (as opposed to an application within the Sitecore client), I think the PublishCacheDependency is the way to go.

Sitecore development, Sitecore custom code, Sitecore caching

Comments

Add a Comment

*
*

Please confirm you are human by typing the text you see in this image:

Jan Sommer said: 1/13/2015 at 2:19 PM

Excellent post. Thanks for sharing.

Andy Burns said: 1/28/2016 at 3:33 AM

Thanks, that's a very helpful post; I like the idea of using a CacheDependency if caching objects, as I'd rather not have to estimate size.