Caching when you have duplicate container components

In theory, the magic of Dynamic Placeholders lets us have a container component placed onto your page more than once. That didn’t work in the old world of “static” placeholders, because the rendering engine didn’t like two placeholders with the same name. But despite it’s benefits, the dynamic implementation has an annoying edge case – you may not be able to enable caching for your container component. I had a client bump into this issue recently, so I spent some time considering approaches that might help them address this issue.

The issue

Imagine you have a container component. A very simple one will do fine – all it really needs to contain is a Dynamic Placeholder:

@using Sitecore.Mvc
<div style="border:2px solid red; margin-top:10px; padding:10px;">
    <div>Dynamic Container</div>
    @Html.Sitecore().DynamicPlaceholder("container")
</div>

If you build up a page using two instances of that component, with some child components inside each of the placeholders it will look fine in Experience Editor:

But if the container components have caching enabled, it won’t look right on the public site:

That’s because the code renders the first container and caches the result. Then it starts to render the second one, but gets a cache hit, and so returns the cached data for the first one. In the cache you see:

There’s no cache variant that works for “this component varies by its children” – so only the first instance of the container ever gets rendered and cached.

My interest in this came about because I got handed a bug report on a client site that said “our page is wrong!” and it turned out to be because of this issue. I think there are three main ways that the issue could be resolved to make the client happy:

Solution one: Just disable caching

If you turn the cache settings off for the container component, all will look right.

That’s probably fine if your container has few children, or it’s not used very much. The performance difference in that scenario is likely to be small – especially if you are able to enable caching for the child components inside your container.

That’s a fix developers can roll out easily (it’s just undoing the cache settings on the rendering item) and it doesn’t affect editors at all.

Solution two: Use a fake data source

You could configure the container component with the “vary by datasource” cache setting instead of turning caching off. If the component doesn’t really need a data source, the code will just ignore what ever item the datasource setting points to, other than for caching. And that can allow the normal caching framework to work here.

No deployment is required for this, which is a bonus. But it might be confusing for editors to need to add a data source where it’s not really needed. Plus this doesn’t work if your container does need a datasource, and you have two on a page that need the same datasource but different children.

So this might not be a great answer.

Solution three: Vary the cache by rendering id

Finally, it turns out it’s not actually that hard to extend Sitecore’s caching model to allow for the idea of “cache this specific rendering instance”. You could write some code that looked at the actual children of a component and generated a cache key based on that. Maybe by hashing some data from the Rendering XML? But it struck me that it’s faster and easier (and probably much the same outcome) to just make use of the unique ID that every instance of a rendering gets by default…

To do this, you need to create a new data template to store your “cache by rendering’s unique id” flag:

And then add that to the inheritance tree of the standard caching flags item:

And that lets you set a caching flag on your container rendering:

To act on that new flag, you need to deploy a simple bit of code to extend the rendering pipeline:

public class VaryByUniqueIdCacheKey : Sitecore.Mvc.Pipelines.Response.RenderRendering.GenerateCacheKey
{
    protected override string GenerateKey(Rendering rendering, RenderRenderingArgs args)
    {
        var cacheKey = base.GenerateKey(rendering, args);

        var cacheField = (CheckboxField)rendering.RenderingItem.InnerItem.Fields["VaryByUniqueId"];
            
        if (cacheField.Checked)
        {
            cacheKey += "_#uniqueId:" + rendering.UniqueId.ToString();
        }

        return cacheKey;
    }
}

That looks for the extra cache variant flag on the item being rendered, and if it exists the cache key is extended with the unique ID for this rendering instance.

And you can patch that into your config via:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <mvc.renderRendering>
        <processor patch:instead="processor[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.GenerateCacheKey, Sitecore.Mvc']" type="Caching.VaryByUniqueIdCacheKey, Caching" />
      </mvc.renderRendering>
    </pipelines>
  </sitecore>
</configuration>

Once that’s done, you’ll get a separate cache entry for each instance of your container component:

And the published version of the site will look correct:

Whether that solution is “better” for you than just disabling caching probably depends on the effort involved in rendering each of your containers. If they have one or two items in them, chances are it’s not worth it. But if your container had a larger number of items, the “memory usage vs processing time” trade-off might be worth it.

It’s an option for you…

2 thoughts on “Caching when you have duplicate container components

    • Thanks Matt. There’s always a cache viewer in my debugging toolbox – being able to see what the caches are up to has helped me diagnose many issues over the years.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.