In this post, I'm going to cover an Episerver donut caching package that I have created and that you can use in your projects to quickly implement donut hole caching. This is the fifth post in my series covering output caching in Episerver. As the number of posts implies, caching in Episerver can be complicated. In the previous guides, we've talked about when and why you may not use 'full page' caching and why you might want to implement donut caching

Installing the Donut Cache

As I mentioned, I have created a donut hole project you can use to implement donut hole caching, but if I needed to write everything from scratch, it would have taken me far too long. Instead, I cheated somewhat by using an existing donut caching Nuget package to get us 80% there. DevTrends have created a really good open source donut caching package that is a great starting place to create an Episerver version from. The first thing we need to do is add the Donut cache framework into your solution. episerver_donut_caching

Open up Nuget type 'donut' and you should see the DevTrends MvcDonutCaching package.

Understanding The Donut Cache

One of the big limitations of the dev trends version in terms of implementing it within Episerver, is that it was based on MVC routing. The dev trends version comes with an HTML partial helper that will create donut holes based on controllers and actions. The dev trends version then creates unique cache keys based on the controller/action combo. To get donut caching to work in Episerver we need an HTML helper that creates holes based on IContent and ContentAreas and have the unique cache keys based on serialized content references. Luckily, for you, I have done just that and the code is available from Nuget from here.

How It Works

The EpiserverDonutCache comes with 'EpiserverDonutCacheAttribute' this is the attribute you will need to decorate your controllers with to process donut holes. Donut holes are created in views by using the provided HTML helpers. The helpers work with ContentAreas or IContents so you have some flexibility. When the page is being processed and a hole is created, a donut comment is added to the HTML. The HTML comment has a content reference associated with it. After the page is rendered, this version with donut comments is added to the output cache.

The next time a page request is made, the HTML that has been cached against the page (including the donut comments) is retrieved from the donut cache and then a replace is done via a regular expression on all the donut comments on the page. This replace directly calls the page, or, block controller for the item being donut cached. The benefit of directly calling page and block controllers means we can add an additional level of caching in pages. Depending on how you set up your controllers, these holes can either be additionally cached with their own expiry time, or, we can configure the controllers to just get the HTML for the block live each time.

I admit this can be hard to get your head around, so if you are struggling with this a bit, I would recommend checking out my Github example, if you're getting confused. By having this level of caching configuration is quite powerful. For example. you may cache more out the page for 24 hours for example as the content doesn't change very often, but, have the donut holes only cache for a maximum of 5 minutes.

By having this different cache times should reduce a lot of load off of your server as the CPU only has to re-build small parts of your page every now and again rather than the whole page every 5 minutes.


Pages On all your top level pages you need to add the EpiserverDonutCacheAttribute and the duration you want to cache the page (the duration is measured in seconds)> My start page would look like this:

        [EpiserverDonutCache(Duration = 24 * 3600)]
        public ActionResult Index(StartPage currentPage)
            return View("Index", new StartPageViewModel(currentPage, EpiserverDependencies));

Blocks and Partials

To make a block or partial work with the donut cache, you will need to decorate it with the 'ChildActionOnly' attribute. If you forget to add this, then the donut whole will not render correctly. If you never want the block to cache and to be rendered directly each time, this is all you need to do. If, on the other hand, you want to cache your block for a period of time, you need to use the EpiserverDonutCache attribute again. To cache a block for 60 seconds you would decorate it like this:

        [ChildActionOnly, EpiserverDonutCache(Duration = 60)]

Holes Within Holes

In some instances you may only want to create a donut hole within a child action. Say you have a carousel, you may want the carousel slides to be donut cached. If you want to have nested donut within your code, you need to use the OutputCacheOptions.ReplaceDonutsInChildActions attribute. Without this, the donut holes will not work correctly.

        [ChildActionOnly, EpiserverDonutCache(Duration = 60, Options = OutputCacheOptions.ReplaceDonutsInChildActions)]
        public override ActionResult Index(DonutExampleBlock currentBlock)
            var displayTag = GetDisplayOptionTag();
            return PartialView("Index",
                               new DonutExampleBlockViewModel(currentBlock,

HTML Helpers

Now you know how to configure the controllers so MVC can correctly parse the donuts, we should probably create a few to test it out. In your view, you can either create a hole around a single IContent or a content area. The IContent is probably easier so we'll cover that first.


<p class="DonutHole">
    Donut One cached at: @Model.TheTime (expires every 60 seconds)

    @foreach(var content in Model.Content)

Content Area


Like normal content areas, I have also created an overload that will take the standard additional parameters, like so:

       Html.DonutForContent(Model.CurrentBlock.MainContentArea, new
            CustomTag = "ul",
            CssClass = "ul-class",
            ChildrenCustomTagName = "li",
            ChildrenCssClass = "li-class",
            Tag = "wide"

Example Project

To hopefully make your life a little easier, I have created a fully working demo website with everything you need to get you up and running. episerver_donut_project

You can download this sample site from my Github account here: JonDJones.Com.EpiserverDonutCaching

So, there you have my Episerver donut caching package, hopefully explained, so you can get going with donut caching in your project. The key to implementing donut caching successfully is decorating your controllers correctly. On all your top level pages you need to use the 'EpiserverDonutCache' attribute. On all child block controllers, you need to use the 'ChildActionOnly'. If you want to created nested donuts you need to use the 'OutputCacheOptions.ReplaceDonutsInChildActions' option. The duration is measured in seconds (not minutes) so be aware!