Enforce automatic caching on Presentation Components to improve performance - Part 1

This blog post is about how I think we can improve our Sitecore's CMS experience by enforcing Sitecore's sublayout caching in order to improve website's performance.

Sitecore CMS comes with a pack of caching mechanism and among of them we have Sitecore's HTML Cache.

The HTML cache - also referred to as the web cache - caches the actual HTML generated from your sublayouts and renderings.

In this blog post we can find all Sitecore CMS caching definitions.

When we create a new sublayout for our content we have a set of fields which are available to us which handle its caching settings (cacheable, Clear on Index Update, Vary By Device, Vary By Data, etc...) directly on our sublayout.

Caching defined at Sublayout level

But when I added the concerned sublayout in my presentation detail, I noticed that the caching parameters were not set on my actual item. Despite this it still applied on the presentation component.

What is discussed on this post is how we can enforce this through the code (even if we can do so within Sitecore itself).

Caching results at presentation detail

One way to overcome this is to make Sitecore reads these values from the code and apply them to the items's presentation detail dynamically. For that, we would need 3 things:

  1. Code change
  2. Custom xml file (LayoutDetails.xml)

Custom Cache Handler Implementation

1. Code Change - Create our custom cache handler

Create the code for to read and apply the caching settings. I will call it RenderCaching.cs. It will inherits from the Sitecore.Shell.Applications.ContentManager.Dialogs.LayoutDetails.LayoutDetailsForm. I will basically override Sitecore's "onOk" method and apply our custom code in there. In this part of the solution, I will read my sublayout's caching settings and add it to my component when I click on OK.

    namespace SitecoreBlog.Loic.Extensibility.Caching
    {
        using System;
        using System.Collections;
        using Sitecore.Data;
        using Sitecore.Data.Fields;
        using Sitecore.Data.Items;
        using Sitecore.Globalization;
        using Sitecore.Layouts;
        using Sitecore.SecurityModel;
        using Sitecore.Shell.Applications.ContentManager.Dialogs.LayoutDetails;
        using Sitecore.Web;
        using Sitecore.Web.UI.Sheer;

        /// <summary>
        /// The rendering cacheable.
        /// </summary>
        public class RenderCaching : LayoutDetailsForm
        {

            #region Methods


            protected override void OnOK(object sender, EventArgs args)
            {
                //this.SiteContext = IoCFactory.Current.GetContainer().Resolve<ISiteContext>();

                using (new SecurityDisabler())
                {
                    var currentItem = this.GetCurrentItem();

                    var layout = this.Layout;

                    var layoutField = new LayoutField(currentItem.Fields[Sitecore.FieldIDs.LayoutField]);

                    var layoutDefinition = LayoutDefinition.Parse(layout);

                    var sublayout = Sitecore.Context.Database.GetItem("{36F5C791-15B9-4BB1-9C65-2CDB2557DEAD}");

                    if (!string.IsNullOrWhiteSpace(layout) && sublayout != null)
                    {
                        layoutDefinition.LoadXml(layout);

                        foreach (DeviceDefinition device in layoutDefinition.Devices)
                        {
                            var currentRenderings = this.GetRenderingList(device.ID, layoutDefinition);

                            foreach (RenderingDefinition rendering in currentRenderings)
                            {
                              //  ClosedBoxesCachingSettings render;

                                //Get the sublayout by passing its id or path.

                                //Checking if we have the sublayout on the presentation detail
                                if (rendering.ItemID.Equals("{36F5C791-15B9-4BB1-9C65-2CDB2557DEAD}"))
                                {
                                    //set all caching parameters

                                    rendering.Cachable = sublayout.Fields["Cacheable"].Value;
                                    rendering.VaryByData = sublayout.Fields["VaryByData"].Value;
                                    rendering.VaryByDevice = sublayout.Fields["VaryByDevice"].Value;
                                    rendering.VaryByParameters = sublayout.Fields["VaryByParameters"].Value;
                                    rendering.VaryByQueryString = sublayout.Fields["VaryByQueryString"].Value;
                                    rendering.VaryByUser = sublayout.Fields["VaryByUser"].Value;
                                }

                            }
                        }

                        currentItem.Editing.BeginEdit();
                        {
                            layoutField.Value = layoutDefinition.ToXml();
                        }

                        currentItem.Editing.EndEdit();

                    }

                    SheerResponse.CloseWindow();

                }
            }

            private Item GetCurrentItem()
            {
                var id = WebUtil.GetQueryString("id");

                var language = Language.Parse(WebUtil.GetQueryString("la"));

                var version = Sitecore.Data.Version.Parse(WebUtil.GetQueryString("vs"));
                var database = Sitecore.Data.Database.GetDatabase("master");

                return database.GetItem(id, language, version);
            }

            private ArrayList GetRenderingList(string deviceId, LayoutDefinition layoutDefinition)
            {
                return layoutDefinition.GetDevice(deviceId).Renderings;
            }

            #endregion
        }
    }

2. Create our XML file to use our custom code

I will create the corresponding XML file (LayoutDetails.xml) which will override in-built Sitecore functionality and I will make a reference to my custom cache handler.

This file should be located under \Website\sitecore\shell\Override\Applications\Content Manager\Dialogs

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense" xmlns:shell="http://www.sitecore.net/shell">
  <LayoutDetails>
    <FormDialog Icon="Applications/32x32/window_view.png" Header="Layout Details" Text="The details of the assigned layouts, controls and placeholders.">
      <Stylesheet>
        #LayoutPanel {
        padding: 4px;
        }

        .ff #LayoutPanel {
        padding: 0;
        }

        .scContentControlLayoutDevice, .scContentControlLayoutDevice_Active {
        padding-bottom: 6px;
        }

        .ff .scDeviceCommands span:hover {
        text-decoration: underline;
        cursor: pointer;
        }


        .scConditionContainer{
        background-color: #446693;
        color: White;
        float: left;
        left: 10px;
        position: absolute;
        text-align: center;
        top: -2px;
        padding: 0 2px;
        }

        .ie .scConditionContainer
        {
        left: 9px;
        }

        .scLongConditionContainer
        {
        left:7px;
        padding: 0 1px;
        }
      </Stylesheet>

      <CodeBeside Type="SitecoreBlog.Loic.Extensibility.Caching.RenderCaching,PSitecoreBlog.Loic"/>

      <Scrollbox ID="LayoutPanel" Class="scScrollbox scFixSize scFixSize4" Padding="0px" Height="100%" GridPanel.Height="100%"/>

    </FormDialog>
  </LayoutDetails>
</control>

And voilà! we're all set up. But you will notice that this approach is a not really good in terms of back office performance because when we click on the "Ok" button when we are doing some additional database request to get the item to use its caching settings. These could be very messy if we have a lot of presentation components. As this is my first "draft" of this implementation, I am already working on a solution which will not impact Sitecore's back-office performance as well and will be published on the part 2 of this article.

Useful resources: