Kentico 12: Design Patterns Part 17 - Centralized Cache Management through Decoration
Tags: kentico, mvc, aspnet, csharp
Published: 2019-11-04 13:44:05

---
title: Kentico 12: Design Patterns Part 17 - Centralized Cache Management through Decoration
published: true
description: Learn how to combine SOLID principles to ensure valid, centralized cache dependency key creation for both query and output caching.
cover_image: https://thepracticaldev.s3.amazonaws.com/i/j3bs5o6sm4280gm2v6ri.jpg
tags: kentico, mvc, aspnet, csharp
series: Kentico 12 - Design Patterns
---

<figcaption>Photo by <a href="https://unsplash.com/@alisaanton">Alisa Anton
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

## Caching and Querying: A Primer

We want our Kentico 12 MVC applications to be fast 🏎!

Database access, especially when there is a lot of it, can be slow 🦥... and it also doesn't scale well as traffic to a site increases.

This is why we rely on caching as a means of keeping data closer to the application, or closer to the visitor of the site, where it's faster to access.

With our Kentico sites there is typically 2 places we can control cache within the application - data caching and output caching.

I've previously written about both data caching (query caching in particular) and output caching 🧐:

{% link seangwright/kentico-12-design-patterns-part-12-database-query-caching-patterns-43hc %}

{% link seangwright/kentico-12-design-patterns-part-15-output-caching-and-user-context-4bpe %}

So far, however, I've treated these topics as separate.

If we were to implement both query caching and output caching, as described by the posts above, we'd end up generating cache dependency keys twice - once for the data being queried from the database and once for the rendered views in the output cache.

This is not ideal, as it implies:

- ❌ We have 2 sets of cache keys being generated for the same data
- ❌ We potentially have 2 implementations for generating those keys
- ❌ The MVC layer, somehow, needs to know exactly which data a page depends on, which feels like a leaky abstraction

But what can we do about it? These two caching layers are, well, two separate layers! ... or are they 😮?

To put this in concrete terms lets look at an example using some of the strategies outlined in the above posts.

---

## Query & Output: Separate Caching Strategies

### IQuery and IQueryHandler

In my previous post, Database Query Caching Patterns, we ended up with a `IQuery<TResponse>`/`IQueryHandler<TQuery, TResponse>` pattern that is shown below:

```csharp
public interface IQuery<TResult> { }

public interface IQueryHandler<TQuery, TResult> 
    where TQuery : IQuery<TResult>
{
    TResult Exceute(TQuery query);
}
```

We implemented these interfaces with a feature based on retrieving `Article` pages from the database, using some parametrization.

First we created an `IQuery<TResponse>` implementation which represented the operation we wanted to execute - querying for `Article`s based on the current `SiteName`, `Culture`, and `Count` of items we wanted to retrieve:

```csharp
public ArticlesQuery : IQuery<IEnumerable<Article>>
{
    public int Count { get; set; }
    public string SiteName { get; set; }
    public string Culture { get; set; }
}
```

Then we created an implementation of the operation, using `IQueryHandler<TQuery, TResponse>`, which wrapped a call to our `ArticleProvider`, using the `ArticlesQuery` parameters 🤓:

```csharp
public ArticlesQueryHandler : IQueryHandler<ArticlesQuery, IEnumerable<Article>>
{
    public IEnumerable<Article> Execute(ArticlesQuery query)
    {
        return ArticleProvider.GetArticles()
            .OnSite(query.SiteName)
            .Culture(query.Culture)
            .TopN(query.Count)
            .OrderByDescending("DocumentPublishFrom")
            .TypedResult;
    }
}
```

The code could be used as follows:

> I'm doing this in a `Controller` class for demonstration only. `Controller` classes should be thin ❗❗ - delegate this kind of work to other parts of your application.

```csharp
public class ArticleController : Controller
{
    private readonly IQueryHandler<ArticlesQuery, IEnumerable<Article>> handler;
    private readonly ISiteContext siteContext;
    private readonly ICultureContext cultureContext;

    public ArticleController(
        IQueryHandler<ArticlesQuery, IEnumerable<Article>> handler,
        ISiteContext siteContext,
        ICultureContext cultureContext)
    {
        this.handler = handler;
        this.siteContext = siteContext;
        this.cultureContext = cultureContext;
    }

    public ActionResult Index(int count)
    {
        var query = new ArticlesQuery
        {
            Count = count,
            Culture = cultureContext.cultureName,
            SiteName = siteContext.siteName
        };

        var articles = handler.Execute(query);

        return View(articles);
    }
}
```

Now we have our core data access patterns defined, so let's move on to the caching part 👍.

---

### Query Caching

The reason we picked this `IQuery`/`IQueryHandler` pattern was due to its adherence to [SOLID design principles](https://en.wikipedia.org/wiki/SOLID).

Specifically:

- ✅ **Single Responsibility**: `IQuery` defines data, `IQueryHandler` defines implementation
- ✅ **Open/Closed**: Easy to apply [Aspect Oriented Programming through Decoration](https://simpleinjector.readthedocs.io/en/latest/aop.html)
- ✅ **Liskov Substitution**: We can supply any implementation of our `IQueryHandler` to the above `ArticleController`
- ✅ **Interface Segregation**: `IQueryHandler` has 1 method: `Execute()`
- ✅ **Dependency Inversion**: Interfaces, like `IQueryHandler` allow for query implementations to be supplied by the application at runtime - no concrete dependencies in our business logic

We created an additional type, `IQueryCacheKeysCreator<TQuery, TResponse>`, that would generate the cache keys, and cache item name parts, for a given query:

```csharp
public interface IQueryCacheKeysCreator<TQuery, TResult> 
    where TQuery : IQuery<TResult>
{
    string[] DependencyKeys(TQuery query, TResult result);
    object[] ItemNameParts(TQuery query);
}
```

This infrastructure helps us avoid the messy use of Attributes for string building, since that normally requires tokenization of strings to mark spots for replacement, ex:

```csharp
[CacheKeys("nodes|##SITE_NAME##|Sandbox.Article|all")]
public class ArticlesQuery
{
    // ...
}
```

> Ensuring that `##SITE_NAME##` is in the correct place in the above string, doesn't have any typos, and works with all of the other tokens we might need to replace... sounds daunting and really error prone 😱.

> Just look at all the variable cache key parts in [Kentico's documentation](https://docs.kentico.com/k12sp/configuring-kentico/configuring-caching/setting-cache-dependencies) to get an idea of how complex this can get.

Our implementation of `IQueryCacheKeysCreator` for the `ArticlesQuery` is pretty simple, can take it's own constructor dependencies if needed, and can scale to more complex queries pretty easily 👍:

> We could be injecting `ISiteContext` and `ICultureContext` as dependencies instead of passing that data through the `ArticlesQuery`, which is the approach I normally take 😎.

```csharp
public class ArticlesQueryCacheKeysCreator :
    IQueryCacheKeysCreator<ArticlesQuery, IEnumerable<Article>>
{
    public string[] DependencyKeys(
        ArticlesQuery query, 
        IEnumerable<Article> result) =>

        new object[]
        {
            $"nodes|{query.SiteName}|{Article.CLASS_NAME}|all" 
        };

    public object[] ItemNameParts(ArticlesQuery query) =>
        new [] 
        { 
            "myapp|data|articles", query.SiteName, 
            query.Culture, query.Count.ToString() 
        };
}
```

> If you find yourself generating a lot of cache dependency keys by hand, check out my [FluentCacheKeys](https://github.com/wiredviews/kentico-fluent-cache-keys) NuGet package ⚡:

> {% link wiredviews/kentico-cms-quick-tip-fluentcachekeys-consistent-cache-dependency-key-generation-9ek %}

The end-goal of all this architecture is to create a central point through which all queries and query responses will pass. In this case, it's the `IQueryHandler` interface that all implementations must fulfill.

So, let's use Decoration as a means of intercepting access to (or applying an Aspect on) our `IQueryHandler` implementations. This is where we do our caching:

```csharp
public class QueryHandlerCacheDecorator<TQuery, TResponse> 
    : IQueryHandler<TQuery, TResponse> 
    where TQuery : IQuery<TResponse>
{
    private readonly ICacheHelper;
    private readonly IQueryHandler<TQuery, TResponse> handler;
    private readonly IQueryCacheKeysCreator<TQuery, TResponse> cacheKeysCreator;

    public QueryHandlerCacheDecorator(
        ICacheHelper cacheHelper,
        IQueryHandler<TQuery, TResponse> handler,
        IQueryCacheKeysCreator<TQuery, TResponse> cacheKeysCreator)
    {
        this.cacheHelper = cacheHelper;
        this.handler = handler;
        this.cacheKeysCreator = cacheKeysCreator;
    }

    public TResponse Execute(TQuery query) =>
        cacheHelper.Cache(
            (cacheSettings) => 
            {
                TResponse result = handler.Execute(query);

                if (cacheSettings.Cached)
                {
                    cacheSettings.GetCacheDependency = () =>
                        cacheHelper.GetCacheDependency(
                            cacheKeysCreator.DependencyKeys(query, result));
                }

                return result;
            },
            new CacheSettings(
               cacheMinutes: 10,
               useSlidingExpiration: true,
               cacheItemNameParts: cacheKeysCreator.ItemNameParts(query)));
}
```

Thanks to C# generics we get wonderful, strong-type support, and thanks to our Inversion of Control (IoC) container, we get to lay this `QueryHandlerCacheDecorator` in front of every `IQueryHandler` implementation with a simple configuration call 🤯.

Using [Autofac](), the call would look like this:

```csharp
var builder = new ContainerBuilder();

builder.RegisterGenericDecorator(
    typeof(QueryHandlerCacheDecorator<,>),
    typeof(IQueryHandler<,>));
```
---

### Output Caching

Output caching, on the surface, is pretty simple 🤨.

ASP.NET MVC gives us the `[OutputCache]` attribute that we can apply to any `Controller` class or action method.

It has a handful of configuration options, like cache duration, what cache configuration from App Settings in the `web.config` should be used.

> You can read more about how to configure output caching in [Kentico's documentation](https://docs.kentico.com/k12sp/configuring-kentico/configuring-caching/caching-on-mvc-sites) for caching in MVC applications, or [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs) on enabling output caching.

The question we didn't look at in my previous post was this: When content in the CMS is changed, how is the output cache cleared correctly 🤔?

Kentico's documentation shows the APIs that must be used to ensure we tell ASP.NET what the cache dependency keys are for a given output-cached page:

```csharp
string dependencyCacheKey = String.Format(
    "nodes|mvcsite|{0}|all", 
    Article.ClassName.ToLowerInvariant());

CacheHelper.EnsureDummyKey(dependencyCacheKey);
HttpContext.Response.AddCacheItemDependency(dependencyCacheKey);
```

We _could_ make these calls in all of our `Controller` classes...

We would need to ensure that each piece of data from the CMS, which a given action is dependent on, is represented as a `string dependencyCacheKey` and passed to `HttpContext.Response.AddCacheItemDependency()`.

That's not very SOLID 😒, as it violates the following:

- ❌ **Single Reponsibility**: Our `Controller` now does route <-> View gluing, and cache management
- ❌ **Open/Closed**: We can't modify how cache dependency key generation is done for Output Caching without modifying the `Controller` class
- ❌ **Dependency Inversion**: Our `Controller` now has a dependency on `HttpContext` and `CacheHelper`, and these are going to be difficult, it not impossible, to test.

---

### SOLID-ifying Our Approach

Kentico's Dancing Goat sample site handles this problem somewhat...

It defines a `IOutputCacheDependencies` type that abstracts away the details of output cache management. Good 👏!

But it still injects this interface into every Controller. Bad 💣!

When we use a type (interface or class) in the same part of our architecture across the entire application, and that type doesn't supply data that is needed to make business decisions, it's very likely that we have a [Cross-Cutting Concern](https://en.wikipedia.org/wiki/Cross-cutting_concern) on our hands 🧐!

`IOutputCacheDependencies` exists to help with caching, and caching, like logging, is most definitely a cross-cutting concern.

There are several ways to attack these scenarios:

1. **Ambient Context**: Examples like `HttpContext` and `static` logging classes
1. **Inject dependencies everywhere**: This is how the Dancing Goat site handles `IOutputCacheDependencies`
1. **Aspect Oriented Programming**: Use Decoration across interfaces to centralize the operation

Option 1 results in a lot of repetition, so Don't Repeat Yourself (DRY) is missing and we have a [Code Smell](https://en.wikipedia.org/wiki/Code_smell). It also violates all the SOLID principles we just listed (Single Responsibility, Open/Close, Dependency Inversion) 😟.

Option 2 fixes the Dependency Inversion violation, but it doesn't solve Single Responsibility and Open/Closed... it's also not DRY 😑.

The third option (my favorite 🥰), adheres to Single Responsibility, Open/Closed, Dependency Inversion, and it's as DRY as The Sahara 🐫🐫🐫.

As we will see, it also solves our duplicated management of cache dependency keys between our output caching and query caching 🎉🥳.

---

## The Solution: Combining Query and Output Cache Management

### IOutputCacheDependencies

First, I like the idea of the `IOutputCacheDependencies` type used in the Dancing Goat code base. Let's use it, but also simplify it:

```csharp
public interface IOutputCacheDependencies
{
    void AddDependencyOnKeys(params string[] cacheKeys);
}
```

And here's an example implementation:

```csharp
public class OutputCacheDependencies : IOutputCacheDependencies
{
    private readonly IHttpContextBaseAccessor httpContextAccessor;
    private readonly ICacheHelper cacheHelper;
    private readonly HashSet<string> dependencyCacheKeys;

    public OutputCacheDependencies(
        IHttpContextBaseAccessor httpContextAccessor,
        ICacheHelper cacheHelper)
    {
        this.httpContextAccessor = httpContextAccessor;
        this.cacheHelper = cacheHelper;

        dependencyCacheKeys = new HashSet<string>();
    }

    public void AddDependencyOnKeys(params string[] cacheKeys)
    {
        foreach (string key in cacheKeys)
        {
            string lowerKey = key.ToLowerInvariant();

            if (dependencyCacheKeys.Contains(lowerKey))
            {
                return;
            }

            dependencyCacheKeys.Add(lowerKey);

            cacheHelper.EnsureDummyKey(lowerKey);

            httpContextAccessor
                .HttpContextBase
                .Response
                .AddCacheItemDependency(lowerKey);
        }
    }
}
```

> You might that I've also created interfaces for `HttpContext` (`IHttpContextBaseAccessor`) and `CacheHelper` (`ICacheHelper`). I like pushing the un-testable dependencies as far to the outside of my application as possible.

> This follows the [Onion Architecture pattern](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) and allows more of our business logic to be easily testable 😄.

So, now we have a class that lets us define the cache dependency keys of a specific page in our output cache.

If any of these keys are touched due to changes to that content in the CMS, our output cache will be cleared for the pages depending on those keys.

---

### QueryHandlerCacheDecorator

Maybe you already see what's coming... 😏

We already have a central point where all caching is taking place - `QueryHandlerCacheDecorator`. It's also the spot where all cache dependency keys for a request to our application are being generated 😮.

Those keys are what we want to pass to our `IOutputCacheDependencies`, so let's wire it all up by passing the `IOutputCacheDependencies` to our `QueryHandlerCacheDecorator` as a dependency.

First, we update the constructor parameters:

```csharp
public class QueryHandlerCacheDecorator<TQuery, TResponse> 
    : IQueryHandler<TQuery, TResponse> 
    where TQuery : IQuery<TResponse>
{
    private readonly ICacheHelper cacheHelper;
    private readonly IQueryHandler<TQuery, TResponse> handler;
    private readonly IQueryCacheKeysCreator<TQuery, TResponse> cacheKeysCreator;
    private readonly IOutputCacheDependencies outputCache;

    public QueryHandlerCacheDecorator(
        ICacheHelper cacheHelper,
        IQueryHandler<TQuery, TResponse> handler,
        IQueryCacheKeysCreator<TQuery, TResponse> cacheKeysCreator,
        IOutputCacheDependencies outputCache)
    {
        this.cacheHelper = cacheHelper;
        this.handler = handler;
        this.cacheKeysCreator = cacheKeysCreator;
        this.outputCache = outputCache;
    }
```

Then we modify the `Execute()` method to pass the generated `cacheKeys`, for the given query, to both `outputCache.AddDependencyOnKeys()` and `cacheHelper.GetCacheDependency()`:

```csharp
    public TResponse Execute(TQuery query) =>
        cacheHelper.Cache(
            (cacheSettings) => 
            {
                TResponse result = handler.Execute(query);

                if (!cs.Cached)
                {
                     return result;
                }

                cs.GetCacheDependency = () =>
                {
                     string[] cacheKeys = cacheKeysCreator
                         .DependencyKeys(query, result);

                     outputCache.AddDependencyOnKeys(cacheKeys);

                     return cacheHelper.GetCacheDependency(cacheKeys);
                };

                return result;
            },
            new CacheSettings(
               cacheMinutes: 10,
               useSlidingExpiration: true,
               cacheItemNameParts: cacheKeysCreator.ItemNameParts(query)));
}
```

Bingo, banjo 🪕!

We now are guaranteed that any cache dependency keys for query caching will also be associated with the output cache of the page.

It's easy to assume that all Kentico data will come from an `IQueryHandler` implementation, which means that when we wire up all the pieces, we are safe from missing, typo'd, or mis-configured keys in only one of our caching layers 😅.

When we mess up our cache keys, at least they'll be consistent 🤣!

And, if we're really worried about these keys, then unit tests on our `IQueryCacheKeysCreator` implementations or integration tests on our whole caching layer will validate the quality of our code 😎.

---

## Wrap Up

Caching has always been an important part of Kentico EMS Portal Engine applications, and it's no less important in Kentico 12 MVC.

We typically have 2 caching layers - one for data, and one for HTML output.

Most, if not all, of the data we work with in Kentico will come from the database shared with the CMS, but even if it doesn't, by coming up with a common, segregated interface, like `IQueryHandler`, we can use AOP and Decoration to ensure our data caching is always being leveraged ⚡.

Since Output Caching occurs at a different layer (MVC), it might seem like a difficult task to leverage the cross-cutting caching we already have applied.

However, we've seen how an application that abides by the SOLID principles is both composable and flexible 💪.

The same cache dependency keys we generate for data caching can be immediately passed to our output cache management implementation to ensure consistency in features and functionality in regards to caching.

I love it when a plan comes together 🤗!

{% youtube FPQlXNH36mI%}

As always, thanks for reading 🙏!

---

If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:

{% tag kentico %}

Or my Kentico blog series:

- [Kentico 12: Design Patterns](https://dev.to/search?q=Kentico%2012%3A%20Design%20Patterns)
- [Kentico CMS Quick Tips](https://dev.to/search?q=Kentico%20CMS%20Quick%20Tip)