Take the community feedback survey now.

Andreas J
Dec 2, 2022
  55
(0 votes)

How to use CacheTagHelper with content areas in Optimizely CMS 12

I might be going out on a limb here - if you have a better solution, feel very free to share it! 

Upgrading your Optimizely web application from .NET Framework to .NET Core should provide a huge performance win. However, the ContentOutputCacheAttribute did not make the cut and was not migrated by Optimizely. If your web application was reliant on this, chances are you had some bottlenecks that will remain even after the upgrade. Not being able to use the output cache attribute will make those bottlenecks come alive once more.

PS. Don't have any bottlenecks? Continue reading anyway - learning how to use CacheTagHelper can dramatically improve your performance.

To once again (shame on you!) hide those bottlenecks, we can pretty easily make use of the CacheTagHelper that .NET Core provides. An unrealistically simple example of this would be:

<cache>
    @Html.PropertyFor(m => m.ContentArea)
</cache>

You've now "solved" one problem (the bottleneck), but gained one new - cache invalidation.

In order to re-evaluate the code block within the cache node - you need to add vary-by-* conditions. Since we're caching a content area, we need to make sure the cache is invalidated whenever visitor groups are applied or when content is published.

Let's first use vary-by to depend on visitor group setup of the content area.

public static string GetContentAreaCacheDiscriminator(this IHtmlHelper htmlHelper, ContentArea contentArea)
{
    var httpContext = htmlHelper.ViewContext.HttpContext;

    if (contentArea == null)
    {
        return httpContext.Request.GetDisplayUrl();
    }

    var requiredRoles = string.Join(",", contentArea.Items.Select(x => x.ContentGroup));

    return $"{httpContext.Request.GetDisplayUrl()}-{requiredRoles}";
}

... and in the Razor view ...

<cache vary-by="@Html.GetContentAreaCacheDiscriminator(Model.ContentArea)">
    @Html.PropertyFor(m => m.ContentArea)
</cache>

The ContentGroup property of every ContentAreaItem in the content area seems to keeps track of what users can see the content.

So, now the cache should be invalidated if visitor groups are applied to the content area.

The ability to re-evaluate the content area whenever some nested block is published is more tricky. The only simple-ish way I can come up with is listening to the publish event, increment some counter and include that counter in the cache discriminator method.

It would look something like this:

public static class AtomicState
{
    public static int Counter = 0;

    public static void Increment() => Interlocked.Increment(ref Counter);
}

[InitializableModule, ModuleDependency(typeof(FrameworkInitialization))]
public class ContentEventsInitializableModule : IInitializableModule
{
    private IContentEvents _contentEvents;

    public void Initialize(InitializationEngine context)
    {
        _contentEvents = context.Locate.Advanced.GetRequiredService<IContentEvents>();

        _contentEvents.PublishedContent += OnPublished;
    }

    public void Uninitialize(InitializationEngine context)
    {
        _contentEvents.PublishedContent -= OnPublished;
    }

    private static void OnPublished(object sender, ContentEventArgs e)
    {
        AtomicState.Increment();
    }
}

Now we must make use of AtomicState.Counter in our cache discriminator method, so just add it to the return statement:

return $"{httpContext.Request.GetDisplayUrl()}-{AtomicState.Counter}-{requiredRoles}";

Since AtomicState.Counter will be read zero or more times per request, and can be updated at any point, we must make it thread safe - which in this case is achieved with Interlocked.Increment(...).

If you wan't more granular control over what cache to invalidate, you need to check the published event for what content type was published and you need another counter for the specific case.

That's it. Thank you for your time! 😏

I originally wrote this post on my personal blog:
https://andreas.jilvero.se/blog/how-to-use-cachetaghelper-with-content-areas-in-optimizely/

Dec 02, 2022

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - Opticon London 2025

This installment of a day in the life of an Optimizely OMVP gives an in-depth coverage of my trip down to London to attend Opticon London 2025 held...

Graham Carr | Oct 2, 2025

Optimizely Web Experimentation Using Real-Time Segments: A Step-by-Step Guide

  Introduction Personalization has become de facto standard for any digital channel to improve the user's engagement KPI’s.  Personalization uses...

Ratish | Oct 1, 2025 |

Trigger DXP Warmup Locally to Catch Bugs & Performance Issues Early

Here’s our documentation on warmup in DXP : 🔗 https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites What I didn...

dada | Sep 29, 2025

Creating Opal Tools for Stott Robots Handler

This summer, the Netcel Development team and I took part in Optimizely’s Opal Hackathon. The challenge from Optimizely was to extend Opal’s abiliti...

Mark Stott | Sep 28, 2025

Integrating Commerce Search v3 (Vertex AI) with Optimizely Configured Commerce

Introduction This blog provides a technical guide for integrating Commerce Search v3, which leverages Google Cloud's Vertex AI Search, into an...

Vaibhav | Sep 27, 2025

A day in the life of an Optimizely MVP - Opti Graph Extensions add-on v1.0.0 released

I am pleased to announce that the official v1.0.0 of the Opti Graph Extensions add-on has now been released and is generally available. Refer to my...

Graham Carr | Sep 25, 2025