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

Well, this blog post is the second part of my latest post.

We have seen that with my last approach we could enforce the caching settings in the code increasing front-office's performance but decreasing back-office responsiveness and performance due to additional database query to get appropriated settings.

In this blog post I will use some of Sitecore pipelines to initiate all caching settings at application startup so that we do not have to query all items if ever we have several sublayouts we would not impact our back-office.

Here, I am assuming that I have several modules (articles, comment, product) and each modules will have its own initializer. This would be the place where I would define my modules' settings. In this article, I will use a generic module initilizer.

Requirements

  1. Sitecore patch
  2. Create our custom classes and modify previous Cache handler.
  3. Our previous LayoutDetails.xml

Sitecore Patch:

In his blog post, @SitecoreJohn already defined all sitecore's most important pipelines.

In my approach, I will use <initialize>. This is the pipeline where Sitecore application is initialized.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor patch:before="processor[@type='Sitecore.Pipelines.Loader.ShowVersion, Sitecore.Kernel']"
          type="SitecoreBlog.Loic.ModuleName.ModuleInitializer, SitecoreBlog.Loic.ModuleName" />
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

Custom Classes

In this section, I am going to write the moduleintializer class and all of its dependencies.

  1. Create the presentation component entity.

    namespace SitecoreBlog.Loic.Shared.Common
    {
        /// <summary>
        /// The Presentation components caching settings.
        /// </summary>
        public sealed class PresentationComponentCaching
        {
            #region Properties
    
            public string Cachable { get; set; }
    
            public string VaryByData { get; set; }
    
            public string VaryByParameters { get; set; }
    
            public string VaryByQueryString { get; set; }
    
            public string VaryByUser { get; set; }
    
            public string VaryByDevice { get; set; }
    
            #endregion
        }
    }
    
  2. Create ModuleRegistry class which will be a singleton since I will only need one instance of this and will be lazy initialize (only initialized when needed) to hold our caching settings per modules.

    Lazy initialization will help us in this case since this ModuleRegistry may need an expensive initialization (more details explained at a later stage of this article)

    namespace SitecoreBlog.Loic.Shared.Common
    {
        using System.Collections;
        using System;
        using System.Collections.Generic;
        using System.Threading;
        using Sitecore.Data;
    
        /// <summary>
        ///     The template box mapper.
        /// </summary>
        public class ModuleRegistry : IModuleRegistry
        {
            #region Static Fields
    
            private static readonly Lazy<ModuleRegistry> Instance = new Lazy<ModuleRegistry>(() => new ModuleRegistry(),
                LazyThreadSafetyMode.ExecutionAndPublication);
    
            #endregion
    
            #region Fields
    
    
            private readonly IDictionary<ID, PresentationComponentCaching> presentationComponentCachingSettings = new Dictionary<ID, PresentationComponentCaching>();
    
    
            #endregion
    
            #region Public Properties
    
            /// <summary>
            ///     Gets the current.
            /// </summary>
            public static ModuleRegistry Current
            {
                get { return Instance.Value; }
            }
    
            /// <summary>
            /// Gets or sets the closed box caching settings.
            /// </summary>
            public IDictionary<ID, PresentationComponentCaching> PresentationComponentCachingSettings
            {
                get { return this.presentationComponentCachingSettings; }
            }
    
    
            #endregion
        }
    
        public interface IModuleRegistry
        {
            IDictionary<ID, PresentationComponentCaching> PresentationComponentCachingSettings {get;}
        }
    }
    
  3. Create ModuleInitializer class.

We can have two ways of fetching caching settings for a particular modules at this stage.

  • The first one would be using the same approach as in my previous article (fetch the sublayout item and read its value).

    The drawback of the first approach is its consumption. Each time we are initializing the module we are fetching an item and reading which may be tricky if we have several modules. Its main advantage is that we will be able to change any caching settings without sending any code deployment. This method is more flexible.

    namespace SitecoreBlog.Loic.MyModule
        {
            using System;
            using System.Linq;
            using Sitecore.Data;
            using Sitecore.Data.Items;
            using Sitecore.Events;
            using Sitecore.Pipelines;
    
            /// <summary>
            ///     The module initializer.
            /// </summary>
            public class ModuleInitializer
            {
                #region Public Methods and Operators
    
                /// <summary>
                ///     The process.
                /// </summary>
                /// <param name="args">
                ///     The args.
                /// </param>
                public void Process(PipelineArgs args)
                {
    
                    var moduleSublayout = Sitecore.Context.Database.GetItem(ItemIDs.ArticleBox);
    
                    if (moduleSublayout == null)
                    {
                        return;
                    }
    
                    ModuleRegistry.Current.ClosedBoxCachingSettings.Add(ItemIDs.ArticleBox , new ClosedBoxesCachingSettings
                    {
                        Cachable = moduleSublayout.Fields["Cacheable"].Value,
                        VaryByData = moduleSublayout.Fields["VaryByData"].Value,
                        VaryByParameters = moduleSublayout.Fields["VaryByParameters"].Value,
                        VaryByQueryString = moduleSublayout.Fields["VaryByQueryString"].Value,
                        VaryByUser = moduleSublayout.Fields["VaryByUser"].Value,
                        VaryByDevice = moduleSublayout.Fields["VaryByDevice"].Value,
                    });
    
    
                }
    
                #endregion
            }
        }
    
  • In the second approach I will directly set the caching settings through the code without any queries. It is less expensive but that would mean that if we want to change it, we would have to re-compile and do a code push.

    namespace SitecoreBlog.Loic.MyModule
            {
                using System;
                using System.Linq;
                using Sitecore.Data;
                using Sitecore.Data.Items;
                using Sitecore.Events;
                using Sitecore.Pipelines;
    
                /// <summary>
                ///     The module initializer.
                /// </summary>
                public class ModuleInitializer
                {
                    #region Public Methods and Operators
    
                    /// <summary>
                    ///     The process.
                    /// </summary>
                    /// <param name="args">
                    ///     The args.
                    /// </param>
                    public void Process(PipelineArgs args)
                    {
    
                        ModuleRegistry.Current.ClosedBoxCachingSettings.Add(ItemIDs.ArticleBox , new ClosedBoxesCachingSettings
                        {
                            Cachable = "1",
                            VaryByData = "1",
                            VaryByParameters = "1",
                            VaryByQueryString = "0",
                            VaryByUser = "0",
                            VaryByDevice = "1"
                        });
    
                    }
    
                    #endregion
                }
            }
    
  • Change our custom RenderCaching class to fetch values from the ModuleRegistry

    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)
                                    {
                                      PresentationComponentCaching render;
                                        cachingParameters.TryGetValue(new ID(rendering.ItemID), out render);
    
                                        if (cachingParameters.TryGetValue(new ID(rendering.ItemID), out render))
                                        {
                                            rendering.Cachable = render.Cachable;
                                            rendering.VaryByData = render.VaryByData;
                                            rendering.VaryByDevice = render.VaryByDevice;
                                            rendering.VaryByParameters = render.VaryByParameters;
                                            rendering.VaryByQueryString = render.VaryByQueryString;
                                            rendering.VaryByUser = render.VaryByUser;
                                        }
    
                                    }
                                }
    
                                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
                }
            }