World is now on Opti ID! Learn more

Andreas J
Dec 2, 2022
  28
(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
Make Global Assets Site- and Language-Aware at Indexing Time

I had a support case the other day with a question around search on global assets on a multisite. This is the result of that investigation. This co...

dada | Jun 26, 2025

The remote server returned an error: (400) Bad Request – when configuring Azure Storage for an older Optimizely CMS site

How to fix a strange issue that occurred when I moved editor-uploaded files for some old Optimizely CMS 11 solutions to Azure Storage.

Tomas Hensrud Gulla | Jun 26, 2025 |

Enable Opal AI for your Optimizely products

Learn how to enable Opal AI, and meet your infinite workforce.

Tomas Hensrud Gulla | Jun 25, 2025 |

Deploying to Optimizely Frontend Hosting: A Practical Guide

Optimizely Frontend Hosting is a cloud-based solution for deploying headless frontend applications - currently supporting only Next.js projects. It...

Szymon Uryga | Jun 25, 2025

World on Opti ID

We're excited to announce that world.optimizely.com is now integrated with Opti ID! What does this mean for you? New Users:  You can now log in wit...

Patrick Lam | Jun 22, 2025

Avoid Scandinavian Letters in File Names in Optimizely CMS

Discover how Scandinavian letters in file names can break media in Optimizely CMS—and learn a simple code fix to automatically sanitize uploads for...

Henning Sjørbotten | Jun 19, 2025 |