Using ASP .NET cache tag helper in Umbraco 9

30/01/2022 umbraco dotnet asp .net umbraco 9 caching

In this post, I will explore using .Net cache tag helpers in Umbraco 9. First let’s get to know what the cache tag helpers are.

In the words of the .Net documentation:

“The Cache Tag Helper provides the ability to improve the performance of your ASP.NET Core app by caching its content to the internal ASP.NET Core cache provider.”

In it’s simplest form, we have the <cache> tag. We can surround a segment of the razor file with the tag and it will be cached the next time the razor file is called. For example:

<cache>

<h1>@Model.Title</h1>

</cache>

If no timescale or expiry condition is given, it is a 20 minute default. We will look at some of these expiry conditions later in the post.

Setting up our Umbraco solution

Let’s start off by creating a new Umbraco 9 solution and going through the install process in browser. For my caching examples, I will use the Umbraco starter kit. If using CLI, we can install using this command:

dotnet add package Umbraco.TheStarterKit

Otherwise, we can install in nuget in Visual Studio. 

Now after we build and run the solution again, we have an Umbraco site with a basic node structure in CMS and some view files in the project.

Caching in Umbraco 9

Let’s explore the master.cshtml file added by installing the starter kit, this is the file that contains the layout of each page that uses it. It has the navigation & footer aswell as the base HTML elements we need to bring in our CSS and scripts. You’ll notice the navigation is added to the page by a partial view using:

@await Html.PartialAsync("~/Views/Partials/Navigation/TopNavigation.cshtml")

If you put a breakpoint the TopNavigation.cshtml and reload the page in browser, you will see when this is called. You will notice it’s called twice due to how the starter kit code is written, for ease of testing, I am going to remove the instance of it wrapped in “mobile-nav” class.

Now, let’s wrap the remaining call to TopNavigation.cshtml in a cache tag and see what happens!

       <cache>

            <nav class="nav-bar top-nav">

                @await Html.PartialAsync("~/Views/Partials/Navigation/TopNavigation.cshtml")

            </nav>

        </cache>

If you run with debug now, it will go into the TopNavigation breakpoint for the first render of the above HTML, then not again (well, for 20 minutes!).

This code is called for every user that loads the page and isn’t personalised per user (this is an important consideration when planning caching in your application), so it makes sense to cache to improve performance.

Say we want this to be cached for a shorter amount of time so it refreshes content more often. We can limit the cache expiry to a custom amount using a "expires-after" parameter on the cache helper tag, for example 60 seconds:

<cache expires-after="@TimeSpan.FromSeconds(60)">

            <nav class="nav-bar top-nav">

                @await Html.PartialAsync("~/Views/Partials/Navigation/TopNavigation.cshtml")

            </nav>

</cache>

Test the debugging again, you will see it hits the breakpoint first time we load the page then not again on any follow up page loads until a minute has passed. In a CMS when content is published, this could be useful to make sure the page content is kept up to date. However, there is another way we can ensure this…

Expiring cache on content publish

The next example will be for the blog pages provided in the starter kit. We’ll add a cache tag in the Blogpost.cshtml file, let’s look at the “vary-by” cache expiry parameter. We can make the cache expire on the updated date value on the page model, which will update when published in CMS:

<cache vary-by="@Model.UpdateDate">

    @Html.Partial("~/Views/Partials/SectionHeader.cshtml")

    <section class="section">

        <div class="container">

            <article>

                <div class="blogpost-meta">

                    <small class="blogpost-date">@Model.CreateDate.ToShortDateString()</small>

                    <span class="blogpost-cat">

                        @Html.Partial("~/Views/Partials/CategoryLinks.cshtml", Model.Categories)

                    </span>

                </div>

                <h3>@Model.Excerpt</h3>

                @Html.GetGridHtml(Model, "bodyText", "bootstrap3-fluid")

            </article>

        </div>

    </section>

</cache>

Now, load one of the blog pages and refresh in browser, it’s cached! It will only call this razor code inside the tag when the updated date is changed. You can test this by updating some content and publishing in Umbraco. Now it will be updated in the cached page as the updated date “vary-by” value has changed, so the cache knows it’s no longer valid.

Now, let’s load the other blog pages on the starter kit website. You may notice that both pages although they have different content in the CMS, show the same content in browser. This is because they have the same updated date based on when they were published. For example in the screenshot below, you will notice the URL of one page but the content of another! 

Of course, this is a bug we'll need to resolve. We need to make the vary-by parameter unique to the content we are publishing to remove this caching issue. I have made the vary-by a composite value of the node id + the updated date:

<cache vary-by="@Model.Id + @Model.UpdateDate">

Now it will only cache that specific blog post content the first time it’s loaded then refresh that page each time from cache. Separate pages will generate their own cache on first page load and expire when the node is published.

There are more expiry conditions you can set on the <cache> tag, check them out here.