Sitecore Blog: @sitecorejohn blog

Custom Caching Criteria with the Sitecore ASP.NET CMS

By John West, May 16, 2011 | Rating:  | Comments (18)

The Sitecore Caching for SSL + non-SSL sublayout thread on the Sitecore Developer Network (SDN) forums prompted me to write this blog post about extending the Sitecore ASP.NET layout engine with additional VaryBy caching options.

The Presentation Component Reference manual on SDN provides information about the VaryBy options that Sitecore uses to cache the output of presentation components. This blog post describes how to add an additional VaryBy option (VaryByScheme, for HTTP or HTTPS), but could be informative to anyone attempting to override Sitecore's logic for embedding presentation controls.

You may have wondered why to implement a Sitecore web control you inherit from Sitecore.Web.UI.WebControl and override DoRender() instead of inheriting from System.Web.UI.Control and overriding the Render() method as you might do on projects that don't involve Sitecore. For one thing, using Sitecore's WebControl class gives you the ability to bind your control to a Sitecore placeholder. But more importantly in the context of this post, the Sitecore base class exposes all of the VaryBy properties, and the Render() method in Sitecore.Web.UI.WebControl manages caching - if an entry is in the cache, the Render() method retrieves that HTML, otherwise the Render() method invokes DoRender().

The HTML cache for the output of presentation components works much like a big hashtable. Each time the layout engine invokes a control, if caching is enabled, Sitecore builds a hash key by calling its GetCacheKey() method. This method calls the GetCachingID() method, which returns an identifier for the presentation component, such as the path to the .ascx or .xslt file. The GetCacheKey() method then adds tokens to this value based on the VaryBy caching options specified for the presentation component.

Because Sitecore's Sublayout and XslFile controls each inherit from WebControl, they benefit from this functionality automatically. When you use a sublayout or an XSL file, Sitecore doesn't just run the .aspx or .xslt, but invokes a web control (Sitecore.Web.UI.WebControls.Sublayout or Sitecore.Web.UI.WebControls.XslFile) that renders the file. The Sublayout contrul uses path to the .aspx file as the caching ID; the XslFile control uses the path to the .xslt file.

Sublayouts and XSL files automatically use the file path as the caching ID, but you should override the GetCachingID() method in all of your custom web controls. The base class for web controls included with this solution provides an override that returns the namespace and name of the class as the caching ID.

Some solutions could benefit from custom VaryBy options, such as when using the same presentation components in both HTTP and an HTTPS sites. You can implement a solution based on this prototype, which adds a custom VaryByScheme attribute to the caching keys for all presentation components..

Sitecore uses something like the factory pattern when binding presentation controls to placeholders. For more information about the configuration factory, see my blog post The Sitecore ASPNET CMS Configuration Factory. It looks to me like the general factory instantiates separate factories for building each type of presentation control. To override the types returned by the factory, override the factories.

To use this solution:

  1. Compile the code into your project.
  2. Update your web controls to use the Sitecore.Sharedsource.Web.UI.WebControl base class.
  3. Enable the /App_Config/Include/CustomVaryBy.config web.config include file.

This solution applies automatically when binding dynamically using placeholders. If you bind sublayouts or XSL renderings statically using the <sc:sublayout> and <sc:xslfile> controls, to get the described functionality, you need to update those references to use the sublayout and XSL controls included in this solution, typically by adding the following element to the /configuration/system.web/pages/controls element in the web.config file, and then updating the namespaces for sublayout and xslfile controls in .aspx and .ascx files from sc to ss (<sc:sublayout> becomes <ss:sublayout>):

<add tagPrefix="ss" namespace="Sitecore.Sharedsource.Web.UI.WebControls" assembly="assembly"/>
    

I implemented factories only for sublayouts and XSL controls, but not for method renderings, URL renderings, web controls, or XML controls. You can create these according to your requirements where appropriate. I provided a base class for web controls to return the namespace and class; your controls should inherit from this or override the GetCachingID() method to call the corresponding method to return a unique identifier for the class.

Extending the CMS user interfaces with a checkbox to control the custom VaryBy option would require additional work. You can disable the caching option by setting the VaryByScheme property to false on individual presentation components.

Note the logic in the VaryByScheme property of the WebControl, Sublayout, and XslFile classes (I realize it's debatable whether logic should be allowed in a property, and these classes demonstrate my side of that argument). The goal here is that you coud add a data template with a VaryByScheme checkbox to the base templates for the sublayout and rendering definition items, and use that to determine whether to vary by scheme. Because there is no UI to enable the caching option when binding the control to a placeholder, this code assumes that caching should vary by scheme by default, and only excludes this option if the checkbox in the data template for the presentation control is not selected. It's a little unusual to inspect the rendering definition item when binding the rendering to a placeholder, but this was the only approach I could think of without extending the UI with additional caching options, which would have been more for something that wouldn't necessarily have added much value.

I couldn't find a way to achieve my goal without to add the same logic to three classes: WebControl, Sublayout and XslFile (I wrote a helper class, but it didn't feel right so I removed it). The root of the problem is that I can't change the base class for XslFile or Sublayout to my WebControl implementation without copying entire classes from Sitecore, which I thought would be worse than the duplication (any time you upgrade Sitecore, you would have to check those classes for changes from Sitecore).

I used the CMS 6.5 Technology Preview for this analysis. For more information about caching, see the Cache Configuration Reference on SDN. For even more information about caching with Sitecore, see my blog post Caching Options in the Sitecore ASP.NET CMS.

If you have any additional information about caching individual presentation components with Sitecore, please comment on this blog post.

Tags: API, Architecture, Infrastructure

Comments

  • Hi John,

    An extra improvement is to have the system cache by Timespan, which it already has but requires coding to work with. We can do the following on your sublayout.cs, xslfile.css etc.

    /// <summary>
    /// Sets the Cache Timespan, and needs to be in the format;
    /// The Format must be 00:00:00 representing hours, minutes and seconds. A fourth
    // optional part may be added, representing milliseconds (ie. 00:00:00:00)
    /// </summary>
    public string SetCacheTimespan
    {
    set
    {
    if (value.Contains(":"))
    {
    // get the base timeout as our default
    TimeSpan time = base.CacheTimeout;

    // set the cache time span
    base.CacheTimeout = Sitecore.DateUtil.ParseTimeSpan(value, time);
    }
    }
    }

    - Christopher Giddings
    May 16, 2011 at 9:03 PM

  • Hello,

    I responded on the forum thread:

    http://sdn.sitecore.net/SDN5/Forum/ShowPost.aspx?PageIndex=2&PostID=35652

    Depending on the outcome, I may write another blog post that references this one.

    Regards,

    -John

    - John West
    May 17, 2011 at 10:06 AM

  • Nice work and good timing, John!
    I was searching since the beginning of this week for a solution like this. I implemented it in our solution and need to change it to the desired VaryBy option and start testing next week :)

    - Martijn van der Put
    May 19, 2011 at 3:19 PM

  • Hello John,

    in you blog you reference to "Note the logic in the VaryByScheme property of the WebControl, Sublayout, and XslFile classes" but in the download you probably removed it? Cause there is not logic at all in the property...

    - Martijn van der Put
    May 20, 2011 at 7:56 AM

  • @Martijn:

    Thanks for your comment. I hate to admit that somehow an old version of the .zip was out there. I have replaced it; please refresh the page and download again.

    - John West
    May 20, 2011 at 9:14 AM

  • Hello John,

    I've implemented your new version but I see some strange behavior when getting the renderingID in het getter of VaryByScheme. In my case this.RenderingID is "(control: id=pushboxoverlay_1)" and I would expect an ID here?

    - Martijn van der Put
    May 26, 2011 at 2:11 PM

  • Sorry, to be honest, I never tested that - I just assumed Sitecore sets RenderingID to the ID of the sublayout definition item. Are you binding a sublayout to a placeholder? In case it matters, what version of Sitecore? I used the 6.5 technology preview.

    - John West
    May 26, 2011 at 7:31 PM

  • @John, I'm using Sitecore 6.4a nd the RenderingID seems to get the .NET ID of the control. The sublayout is binded to a placeholder. What I'm trying to achieve here is to get the rendering (sublayout Item) where this sublayout is assigned to. Which seems hard to accomplish. Any ideas if this could be possible?

    - Martijn van der Put
    May 26, 2011 at 11:57 PM

  • @Martijn,

    I think at worst you could do a query under /sitecore/layout/sublayouts for any items with a Path field equal to the Path property of the sublayout (maybe case insensitive), and if there is only one, use that. Especially if you have lots of sublayouts, this might not perform as well as using a property that contains the ID, if we can find one that Sitecore sets (or maybe setting RenderingID to the control ID is a defect, or maybe the code just checks the wrong property). I will try to make some time to test. Really sorry for posting the bad code in the first place, but I will post a solution one way or another. I still think the rest of this approach works.

    - John West
    May 27, 2011 at 6:43 AM

  • @John I agree that the rest of this approach should work.
    I tried to get the renderingID from the sublayout but that did not work. The only usefull part I could get was the physical path of the rendering.
    I was thinking about getting the current item and then loop through the renderings to find the one that matches the physical rendering path. I did not try it yet but it sounds as a a long way around to get it.
    I will try my new approach soon, but keep me posted if you have found any resolutions.

    - Martijn van der Put
    May 27, 2011 at 7:05 AM

  • @Martijn: Sure enough, Sitecore doesn't set RenderingID to the ID of the sublayout definition item as I assumed. In fact, it doesn't seem to set any property of the sublayout control to that value. I posted updated code on the forum - I actually stepped through this and it seemed to work. It uses a query to match Path instead of iterating the sublayouts. If it works for you, I will update the download associated with this blog post. Thanks!

    http://sdn.sitecore.net/SDN5/Forum/ShowPost.aspx?PageIndex=2&PostID=36134

    - John West
    May 27, 2011 at 9:20 AM

  • @John I stepped through the new code and this is doing what I expect :)
    So, go ahead and update the associated download.
    Thanks!

    - Martijn van der Put
    May 27, 2011 at 1:06 PM

  • Updated. Thanks for the testing!

    - John West
    May 27, 2011 at 1:16 PM

  • Mark Ursino makes a great suggestion here that provides a simple alternative:

    http://stackoverflow.com/questions/6297180/sitecore-html-cache-and-external-data-sources

    "You can cache the sublayout and vary by params, where you define the custom params."

    - John West
    June 14, 2011 at 12:12 PM

  • Great topic John!

    Will have to make little code adjustsments to Martijns work tommorrow, so both this topic and comments are great for perception...

    gr,

    Robbert Hock
    Sitecore MVP

    - Robbert Hock
    June 21, 2011 at 1:04 PM

  • http://sitecoreblog.alexshyba.com/2011/08/sitecore-output-caching-kick-it-up.html

    - John West
    August 22, 2011 at 12:04 PM

  • Thanks John, very useful info, this would definitely be worth it for the additional performance since we would need it throughout the project.

    How do you currently disable the caching for the controls? I presume from the code sample you've added a VaryByScheme field to the templates for the sublayout items? Any thoughts of how much work is involved in adding the checkbox in to the VaryBy options without causing issues for future upgrades :)

    The VaryByParams and adding custom params based on current user role may also be a good option.

    - Kamruz Jaman
    November 16, 2011 at 4:26 AM

  • @Kamruz:

    Getting the checkbox into the UI is the hardest part - probably not worthwhile. I thought someone (Nick?) mentioned doing this on the forum thread linked from the introduction to this blog post, but I'm not sure they got it working and now I can't find the comment (maybe review that thread). Adding a checkbox to the UI is the easy part; overriding the UI to store that value as a custom XML attribute in layout details is harder, and I think if you can store the value, it would be easy to apply it. If you can avoid it, I would.

    Is this a sublayout, web control, or XSL rendering? The approach may differ depending on the type of control. Easiest in regards to caching would be a web control. Another option might be to bind your presentation component statically to a sublayout, and statically pass the parameter from the sublayout to your control.

    Another possibility might be conditional rendering, although I'm not sure you can apply caching conditions with the rules engine.

    > How do you currently disable the caching for the controls?

    The VaryByScheme property contains logic to determine whether to vary by the scheme, for example in WebControl.cs (a base class for web controls). If you don't explicitly set the VaryByScheme property, it defaults to true. If a rendering definition item references the control, and that item contains a checkbox field named VaryByScheme that is not checked, then VaryByScheme is false. Logic in the GetCacheKey() method adds the scheme to the cache key if the VaryByScheme property is true.

    - John West
    November 16, 2011 at 2:33 PM

*
*
*

Learn More with Sitecore

Newsletter
*

Cabot Heritage | Read Case Study >

With Sitecore we have a site that caters to our needs—members of our editorial and marketing teams can add content, freeing IT to keep the site running smoothly.

- Jon Heller, IT Director, Cabot Heritage Corporation