Cache indirect output of Sitecore controls

CONTEXT

In my previous post I’ve described solution that allow you to inject scripts and styles in a specific placeholders in a layout. Scripts are registered in views, but they will be rendered by global filters only when all output of a page is ready. However, when it comes to caching Sitecore will be able to record HTML generated by your component just after it is rendered.
So none of the actions that will be required in other parts of the site will be triggered with cache enabled.

APPROACH

We need to simulate similar behavior that Sitecore has in pipeline, but instead of HTML output we need to save and restore required actions.
We will need 3 processors:

  • Enter Context
  • Save Actions
  • Restore From Cache

Context & Cache key

When we are talking about MVC components they all have its own execution context, as you would need to provide different rendering items to each component. Sitecore will create it and push context to a stack, when you enter it, and remove it from stack and return to parent context, when a lifetime of args object for current pipeline will come to an end. This would ensure that during the whole execution of the pipeline you will be able to get current rendering info.

Also if Sitecore finds out that current component configured to be cached it would generate CacheKey. The Key will be used to identify HTML output of a component in a site cache.

In current solution context will store information about current caching scope and the key will be used to identify scripts in cache.

public class EnterScriptCachingContext : RenderRenderingProcessor
{
    public override void Process(RenderRenderingArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        if (args.Rendered || string.IsNullOrEmpty(args.CacheKey) 
            || RequireHelper.GetInstance().IsScriptCachingContextEntered) // prevent saving in caching context of nested controls
        {
            return;
        }
        this.EnterContext(args);
    }

    protected virtual void EnterContext(RenderRenderingArgs args)
    {
        var disposable = RequireHelper.GetInstance().EnterScriptCachingContext(args.CacheKey);
        args.Disposables.Add(disposable);
    }
}

In Line 7, we checking that if caching context was entered already. We should be able to get all scripts for current and child controls, when current control will be retrieved from cache.

Line 16, create a context with current CacheKey (It is important to add it to an args.Disposables in order to exit context properly). Context creation will use Sitecore ContextService to push ScriptCachingService object there.

public IDisposable EnterScriptCachingContext(string cacheKey)
{
    return ContextService.Get().Push(new ScriptCachingContext(cacheKey));
}

Writing to CACHE

Writing to cache would be similar to renderings cache implementation, we only need to generate peace of content that we would like to save.

protected virtual void UpdateCache(string cacheKey, RenderRenderingArgs args)
{
    var helper = RequireHelper.GetInstance();

    foreach (var reg in helper.Registrars)
    {
        // Get all script elements from cache

        var scripts = reg.FindByCacheKey(cacheKey);
        var index = 0;
        foreach (var script in scripts)
        {
            this.AddHtmlToCache(reg.UniqueId + cacheKey + index++, script, args);
        }
    }
}

In case of scripts registration code will iterate over registrars and find scripts snippets marked with current CacheKey with method FindByCacheKey, which will return rendered script section. Before saving key would be enriched with registrar id and index of this script, so that we will be able to put scripts in correct place in layout and remove duplication.

Restore scripts

Last piece of this puzzle is to restore elements from cache. If component has cache options enabled and site already has something in cache than processor will look for element with current key, registrar id and index.
When script snippets retrieved they are registered similar to registration from view described in the initial post.

protected virtual void Render(string cacheKey, RenderRenderingArgs args)
{
    var htmlCache = Context.Site.ValueOrDefault(CacheManager.GetHtmlCache);
    if (htmlCache == null)
    {
        return;
    }

    var helper = RequireHelper.GetInstance();
    foreach (var itemRegistrar in helper.Registrars)
    {
        var index = 0;
        var html = htmlCache.GetHtml(itemRegistrar.UniqueId + cacheKey + index++);
        while (html != null)
        {
            itemRegistrar.Add(html);
            html = htmlCache.GetHtml(itemRegistrar.UniqueId + cacheKey + index++);
        }
    }
}

USAGE

Finally, we need to register our pipelines in mvc.renderRendering pipeline in the order you find below.

<mvc.renderRendering>
	<processor patch:after="*[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.RenderFromCache, Sitecore.Mvc']"
		type="Example.Pipelines.Require.RequireScriptsFromCache, Example" />

	<processor patch:after="*[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.EnterRenderingContext, Sitecore.Mvc']"
		type="Example.Pipelines.Require.EnterRequireScriptsContext, Example" />

	<processor patch:after="*[@type='Sitecore.Mvc.Pipelines.Response.RenderRendering.AddRecordedHtmlToCache, Sitecore.Mvc']"
		type="Example.Pipelines.Require.AddRecordedScriptsToCache, Example" />
</mvc.renderRendering>

That’s it. We could cache components that inject scripts and styles via global filters.


Share if you like the post. Follow me on twitter @true_shoorik

Cache indirect output of Sitecore controls

One thought on “Cache indirect output of Sitecore controls

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s