Tags: Optimizely/Episerver

Quick and dirty output cache in Optimizely CMS12

In .NET Framework, the concept of output caching existed in both WebForms and MVC. Output caching made it easy to cache the entire markup of a page, and it could really speed things up.

In Optimizely/Episerver we could simply add the Optimizely-specific attribute to our controllers, and the output was cached for the specified number of seconds.

[ContentOutputCache(Duration = 60)]

This attribute would only add output caching for unauthenticated users, and the cache would be invalidated when editors published any changes.

The concept of output cache has not yet made it into .NET5/6, but Mads Kristensen has created WebEssentials.AspNetCore.OutputCaching and we can use that in Optimizely too!

Step 1 - Install the NuGet

Install the NuGet package and configure it as described in the readme file.

We do not want the markup cached for authenticated users. If we did:

To prevent this from happening, we'll inherit the OutputCache-attribute, and make some changes.

Step 2 - create our own attribute

public class ContentOutputCacheAttribute : OutputCacheAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.HttpContext.User.Identity.IsAuthenticated)
        {
            context.HttpContext.EnableOutputCaching(
                TimeSpan.FromSeconds(Duration),
                VaryByHeader,
                VaryByParam,
                VaryByCustom,
                UseAbsoluteExpiration);
        }
    }
}

This will prevent the cache from being populated with markup from authenticated users. Then put the attribute we created on your controllers:

[ContentOutputCache(Duration = 86400)]

Duration = 86400 will cache the page for 24 hours.

Note that the above code will prevent authenticated users from populating the cache. Authenticated users will still see the cached version of the page if the page has been visited by non-authenticated users for the last 24 hours (if Duration is set to 86400). This mean that the quick navigation menu will not be visible. Use the URL /episerver/cms to access edit mode.

Step 3 - clear cache on changes

If the editor updates any pages, the easiest solution is probably to clear the entire cache. Attach to the publishing event i Startup.cs.

public void Configure(
    IApplicationBuilder app, 
    IWebHostEnvironment env, 
    IContentEvents contentEvents)
{
    ...
    contentEvents.PublishedContent += OnPublishedContent;
    ...
}

private void OnPublishedContent(object sender, ContentEventArgs e)
{
    var outputCachingService = ServiceLocator.Current.GetInstance<IOutputCachingService>();
    outputCachingService.Clear();
}

Step 4 - manual purging of the cache

Another quick and dirty solution, if you want to be able to purge the cache manually. Add this controller

[Route("SuperSecretCacheClearer")]
public class ClearOutoutCacheController
{
    private IOutputCachingService _outputCachingService { get; set; }

    public ClearOutoutCacheController(IOutputCachingService outputCachingService)
    {
        _outputCachingService = outputCachingService;
    }

    public JsonResult Index(string secret)
    {
        if (secret == "42")
        {
            _outputCachingService.Clear();
            return new JsonResult(true);
        }
        return new JsonResult(false);
    }
}

And clear the cache by accessing the URL: https://www.mypage.com/SuperSecretCacheClearer?secret=42

Performance improvements

I tested with K6 load testing on a Linux WebApp with the minimum configuration and saw great improvements with output caching activated. The slowest page (with all my blog posts) went from 941 ms to 36 ms on average with 20-50 simultaneous virtual users! The site could also handle 3,7 times as many requests per second with output caching enabled.

Without cache

A screenshot from K6 without output cache enabled.
(Click for larger image)

With cache

A screenshot from K6 with output cache enabled.
(Click for larger image)

That's it - maybe not a perfect solution, but I think It'll do while we wait for an official release!

Update: If you are using personalization (visitor groups) – have a look at this follow-up blog post on how to make it play nice with output cache.