Performance optimisations - exclude custom widgets from cache

Read time: 10 min

Before proceeding to the solution, a quick introduction explains what is a cache and how Sitefinity caches data.

What is a Cache?

This prominent definition by Wikipedia answers directly to the question:

"In computing, a cache (/ˈkæʃ/ kash,[1] or /ˈkeɪʃ/ kaysh in AuE[2]) is a component that stores data so future requests for that data can be served faster"

What types of cache mechanisms exist with Sitefinity?

Sitefinity is using several types of cache mechanisms to optimize the performance of a website:

  • Output Cache (cache is stored on the server)

When a page is requested for the first time, right before the response is sent to the client - it gets stored in memory. This process is called cache substitution. When the same page(URL) is requested next time, instead of going through the same process, the response is retrieved from the cache and directly sent to the client. You can remove the cache from memory (called cache invalidation) by either recycle the application or via Sitefinity cache settings.

  • Client Cache (cache is stored on the client/browser)

Sitefinity caches media content (images, documents, videos) on the client browser. In addition static resources such as JavaScript and CSS files are also being cached by the browser for every subsequent request.

  • Database Cache

Telerik Data Access has a caching mechanism, the 2nd Level Cache, which reduces the calls made to the relational server, by conserving data already loaded from the database. Database access is therefore, necessary only when the retrieving data is currently not available in the cache. For more information, see Second Level Cache

What happens if data changes after the pages are cached?

There are scenarios when you would like to display the most recent data from a custom widget instead of relying on the cached version of a page. For example if you have a custom blogs widget that displays list of blog posts, when you create a new blog post - you would like to display this blog post in the list of posts. However if the page is already cached, the users will not see the recently created blog post. In that case, custom cache dependency technique should be used to exclude parts of the page from being substituted in the Output cache.

Implement custom cache dependency technique

When developing your own user or custom widget, to invalidate the cache, you need to implement the appropriate cache dependencies in order. This way, when you create or modify a content item, the widget cache is invalidated in order to reflect the latest changes in the content.

Sitefinity has an interface called IHasCacheDepenency with single method GetCacheDependencyObjects that serves as a contract for retrieving cached items for control views. Follow the steps below:

1.Create a new custom class named CacheDependencyInitializer and implement the IHasDependency interface:

public class CacheDependencyInitializer : IHasCacheDependency
{
} 

2.Create new public property that will store the custom widget that you would like to exclude from caching:

 public SimpleView ControlWidget { get; set; }

3.Add two constructors - one without parameters (default) and another that will set the ControlWidget property:

    public CacheDependencyInitializer()
    { }

    public CacheDependencyInitializer(SimpleView controlWidget)
    {
        this.ControlWidget = controlWidget;
    }

4.Implement the GetCacheDependencyObjects method from the IHasCacheDepenency interface:

 public IList<CacheDependencyKey> GetCacheDependencyObjects()
    {
        var cacheDependencyNotifiedObjects = new List<CacheDependencyKey>();
        {
            var itemTypeString = "your type name goes here"; 
            //*e.g: Telerik.Sitefinity.Blogs.Model.BlogPost*
            Type type = TypeResolutionService.ResolveType(itemTypeString);
            if (type.FullName.Contains("DynamicTypes"))
            {
                var contentType = typeof(DynamicContent);
                this.AddCachedItem(cacheDependencyNotifiedObjects, type.FullName, contentType);
            }
            else
            {
                cacheDependencyNotifiedObjects.Add(new CacheDependencyKey() { Type = type });
            }
        }
        return cacheDependencyNotifiedObjects;
    }

private void AddCachedItem(List<CacheDependencyKey> cacheDependencyNotifiedObjects, string key, Type type)
    {
        if (!cacheDependencyNotifiedObjects.Any(itm => itm.Key == key && itm.Type == type))
        {
            cacheDependencyNotifiedObjects.Add(new CacheDependencyKey() { Key = key, Type = type });
        }
    }

In the code above if the page is requested from the frontend, cache dependency keys are added to the PageData items of the HTTP context. As a result, if there are any changes in the content items, the widget invalidates the cache for the page where you have dropped it.

5.Subscribe the collection of the cache dependency notified objects to the cache dependency via a public method named SubscribeCacheDependency:

public void SubscribeCacheDependency()
    {
        if (!this.ControlWidget.IsBackend())
        {
            var cacheDependencyNotifiedObjects = this.GetCacheDependencyObjects();

            if (!SystemManager.CurrentHttpContext.Items.Contains(PageCacheDependencyKeys.PageData))
            {
                SystemManager.CurrentHttpContext.Items.Add(PageCacheDependencyKeys.PageData, new List<CacheDependencyKey>());
            }

            ((List<CacheDependencyKey>)SystemManager.CurrentHttpContext.Items[PageCacheDependencyKeys.PageData])
                .AddRange(cacheDependencyNotifiedObjects);
        }
    }

6.Finally, you can use an instance of the custom CacheDependencyInitializer class in InitializeControls method of any custom widget the following way:

 protected override void InitializeControls(GenericContainer container)
 {
  var cacheInitializer = new CacheDependencyInitializer(this);
  cacheInitializer.SubscribeCacheDependency();
 }

The following code will ensure that the items of the custom widget will be subscribed to the Sitefinity cache dependency, thus preventing them from being substituted in the cache.

Veronica Milcheva

About Veronica Milcheva

I am a passionate Sitefinity blogger, developer and consultant. In my spare time I enjoy running and listening to music. My personal quote: There's no tough problem, just not enough coffee :)

View Comments

comments powered by Disqus