DEV Community

Sean G. Wright for WiredViews

Posted on

Kentico CMS Quick Tip: FluentCacheKeys - Consistent Cache Dependency Key Generation

keys

Photo by Florian Berger on Unsplash

Kentico πŸ’• Caching

Data-driven Content Management Systems, like Kentico CMS, rely heavily on caching to enable flexible content composition while still delivering information as quickly as possible to site visitors.

The Portal Engine technology, which has driven the majority of Kentico applications prior to Kentico 12, provided caching configuration out-of-the-box and allowed for highly configurable caching at the data, component,file, and page levels.

Now, developers carry more of the responsibility for caching the data they use to run their Kentico 12 MVC applications πŸ’ͺ.

Fortunately the APIs for caching in custom code, provided by Kentico, are both powerful and relatively simple to use.

The Importance of Cache Dependency Keys

In a previous post (linked below), I wrote about how we can cache the results of our database queries using software design patterns like Aspect Oriented Programming, Dependency Injection, and the Single Responsibility Principle πŸ€“.

In that post I also detailed how specifying the correct cache dependency keys, for the data being cached, was important for guaranteeing the cache's Validity.

When content is updated in the CMS, we expect the caches of the (now out of date) data will be evicted from the cache πŸ‘’.

We take the risk of this not happening if we don't specify the correct cache dependency keys when we store something in the cache 😨.

Cache dependency keys are effectively the glue between specific content or information in the CMS and the data cached in our MVC application.

Building Cache Dependency Keys Can Be Tricky

So, now you might be thinking to yourself, "This seems pretty important, how do we ensure we are creating the correct keys πŸ€·β€β™€οΈ?"

Well, Kentico does provide a nice table of cache key patterns for all the various types of data we might cache (pages, attachments, media files) πŸ‘.

These patterns are strings with runtime data from our application substituted in for specific placeholders:

string key = "node|<site name>|<alias path>|<culture>";
Enter fullscreen mode Exit fullscreen mode

When the tokens (like <site name>) are replaced with real site values, we end up with a key like the following:

string key = "node|corporatesite|/home|en-us";
Enter fullscreen mode Exit fullscreen mode

We can also use C# string interpolation to help us substitute in variables instead of using string concatenation πŸ‘.

string siteName = "Sandbox";
string nodeAliasPath = "/home";
string culture = "en-us";

string key = $"node|{siteName}|{nodeAliasPath}|{culture}";
Enter fullscreen mode Exit fullscreen mode

In my opinion they can be kinda hard to read 🧐 and very easy to typo ⌨... (don't forget all those pipes | !)

Another developer on our team, Michael, thought it would be a good idea to come up with a way to consistently generate these keys to avoid the issues noted above πŸ’‘.

I liked the idea a lot ⚑!

It could help us avoid bugs in what would inevitably be some repetitive code (lots of queries == lots of caching == lots of keys).

It could also help us when writing tests that ensure the correct cache keys are being created 😎.

So, I created a library and made it open source so the rest of the Kentico community could try it out and give some feedback πŸ‘!

FluentCacheKeys

The library, FluentCacheKeys, is inspired πŸ’— by several other projects with readable and easy to use APIs, like FluentAssertions, FluentValidation, and GuardClauses.

GitHub logo wiredviews / kentico-fluent-cache-keys

Utility library for generating consistent cache dependency keys for Kentico CMS applications

Kentico Fluent Cache Keys

Utility library for generating consistent cache dependency keys for Kentico Xperience applications

GitHub Actions CI: Build

Publish Packages to NuGet

Packages

NuGet Package

How to Use?

  1. Install the WiredViews.Kentico.FluentCacheKeys NuGet package in your project:

    dotnet add package WiredViews.Kentico.FluentCacheKeys
    Enter fullscreen mode Exit fullscreen mode

Examples

Creating cache keys for pages / documents / nodes

FluentCacheKey.ForPage().WithDocumentId(5);

FluentCacheKey.ForPage().WithNodeId(4);

FluentCacheKey.ForPage().RelationshipsOfNodeId(4);

FluentCacheKey.ForPage().OfSite("Sandbox").WithAliasPath("/home");

FluentCacheKey.ForPage().OfSite("Sandbox").WithAliasPath("/home", "en-us");

FluentCacheKey.ForPages().OfSite("Sandbox").OfClassName(HomePage.CLASS_NAME);

FluentCacheKey.ForPages().OfSite("Sandbox").UnderAliasPath("/home");

FluentCacheKey.ForPagesNodeOrder().All();
Enter fullscreen mode Exit fullscreen mode

Creating cache keys for CMS objects / custom module classes

…
Enter fullscreen mode Exit fullscreen mode

The library is built on .NET Standard 2.0, so it should work with any project using .NET Framework 4.6.1 or newer and any .NET Core project.

It also has tests, of course 🀘, so you can see examples and ensure the code works correctly.

You can check out the source code in the repository above, so here I'd like to give some examples of how it might be used.

If we want to create a key for the page in the CMS tree found under the alias path of /Products/Coffee/Light-Roast for the site named Sandbox, we could use the following call to produce the correct string:

FluentCacheKey
    .ForPage()
    .OfSite("Sandbox")
    .WithAliasPath("/Products/Coffee/Light-Roast");
Enter fullscreen mode Exit fullscreen mode

The intellisense in Visual Studio will make sure that once you select your first method (in this case .ForPage()), only methods that make sense in that context will be allowed to follow (like .OfSite(string siteName)) 😊.

I also reversed some of the language of creating cache keys to make it more readable and intuitive:

FluentCacheKey
    .ForPages()
    .OfSite("Sandbox")
    .UnderAliasPath("/Products/Coffee/Light-Roast");
Enter fullscreen mode Exit fullscreen mode

Here we immediately know that we are creating a dependency on multiple documents, due to .ForPages(), the pages need to all belong to the same site, and under the same path πŸ€—.

We can also create keys for CMS objects or custom Module class instances:

FluentCacheKey
    .ForObject()
    .OfClassName(UserInfo.OBJECT_TYPE)
    .WithId(2);
Enter fullscreen mode Exit fullscreen mode

And attachments:

FluentCacheKey
    .ForAttachment()
    .WithGuid(new Guid("f4f35038-0b05-4e20-ae4e-807d9632a364"));
Enter fullscreen mode Exit fullscreen mode

Or media files:

FluentCacheKey
    .ForMediaFile()
    .WithGuid(new Guid("a6df379b-6c14-4578-8817-298bfe54a4c3"));
Enter fullscreen mode Exit fullscreen mode

To Kentico's credit, there aren't an overwhelming number of variations of cache key patterns, but it can be confusing that some take siteName while others don't. Do we remember which ones take a Document Id compared to a Node Id 🀯.

For these reasons, I think an API like this, that forces the correct values to be supplied, could be helpful.

I also like that the raw cache key strings aren't visible here, with all their pipes and specific order of tokens.


Feel free to install the NuGet package:

$ dotnet add package WiredViews.Kentico.FluentCacheKeys
Enter fullscreen mode Exit fullscreen mode

Or just copy the code into your own project and make some changes if you'd like.


Summary

I hope this package either helps you organize and clarify your cache management code, or inspires you to come up with your own technique πŸ˜ƒ.

I've been thinking about writing a tool to help build consistent cache item name parts, but I haven't solidified on anything I really like yet - there's too many variations and use-cases!

Let me know what you think or if you have any ideas for some utilities that could help in our Kentico projects πŸ“.

Thanks for reading πŸ™!


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

#kentico

Or my Kentico blog series:

Top comments (1)

Collapse
 
davidconder profile image
David Conder

Very cool - thanks for sharing this Sean!