CONTENTSTART
EXCLUDESTART EXCLUDEEND

How to Cache in Kentico 12 MVC

Caching is a big part of website performance.  In Kentico Portal Engine, we took caching for granted, and adding the Cache Keys, Durations, and Dependencies were a breaze.  Now that we are in MVC, we need to handle Caching in our own way.  The Kentico Blank Site didn't have anything to offer, and as noted in other article, the Dancing Goat example had a lot to dig through.  So I took the Caching elements out of the Dancing Goat example site, improved them, then packaged them up for you all to easily leverage.

MVC Caching for Kentico

First let's get you right into the meat of it.

Check out the Github Repo to get the NuGet package ID and instructions.

This article is pretty much a replica of the Documentation.md file on the repo, but before I cover that i want to get a little bit into the weeds.

How does caching work?

For the most part, Kentico Portal leveraged the same Caching that MVC does at it's base.  Caching is stored in the memory layer that the application sets (default is in memory, but can be other), and attaches Cache Dependencies to the various elements.  When an object is updated in the database, it triggers (through either the API or through the Webfarm system in cases of MVC getting notification of the Mother application's changes), it checks dependencies and clears any caches that match.  Each cache has a Cache Key, and a Duration.

What's different in MVC vs. Portal?

The main difference between Portal and MVC is how we tell the system "we want this cached" and "remove the cache when the dependencies are triggered."  In Portal engine, these were set through Properties on Webparts, Widgets, or even the Page's General tab.  In MVC, we have OutputCache for results and normal CacheHelper for data result caching.  The harder part of this all, is that the OutputCache (at it's base) didn't integrate with Kentico Variations (such as logged in user), and didn't integrate with Kentico's Cache Dependencies.  

This new package has added a couple big things to caching:

  1. Data Retrieval CacheDependency Attributes to automatically or manually set Cache Dependencies on Methods
  2. IOutputCacheDependencies to help set Kentico Cache Dependencies on ActionResults/OutputCache
  3. AutoFac integration to automatically handle these attributes and caching
  4. Integration with Kentico's Cache Dependency triggers to make sure that changes in Kentico properly reset your site

Boilerplate Updated and Examples

The MVC Boilerplate Repo has been updated to include this and has been split into an Examples repo (BoilerplateVariants/Examples).

The MVCCaching Repo also has a MVCCaching.Kentico.Examples folder with samples of how to implement various caching strategies.


Caching Data

Data caching is specifically caching data retrieval operations, such as getting items from a database. Below are different tools that should be leveraged when caching data:

Automatic Caching using IRepository w/ Attributes

Repository Caching is mostly handled automatically, using AutoFac and intercepts.

How to Use

  1. Create an Interface that inherits IRepository, with methods that start with Get
  2. Create an implementation of that Repository
  3. If needed, Decorate methods with [CacheDependency()] attributes

Autofac is set up to look for any method that starts with Get and implements the IRepository interface (CachingRepositoryDecorator.cs). It then intercepts (CachingRepositoryDecorator.Intercept) these methods and then based on either the CacheDependency attributes, or the return type, it creates a Cache Dependency for the method and runs it through Kentico’s CacheHelper (CachingRepositoryDecorator.GetCachedResult).

Culture and Latest Version Enabled

It is currently configured that any IRepository inherited class that has a constructor with the parameters string cultureName and/or bool latestVersionEnabled will have these values passed based on the DependencyResolverConfig.cs ConfigureDependencyResolverForMvcApplication. If your repositories need these values, please add them to your constructor so you can leverage them in your API calls.

You can see how to leverage these in the KenticoExamplePageTypeRepository.cs

CacheDependency Attribute

The CacheDependency Attribute take a String with the DependencyKeyFormat. You can use {#} in this format to pull in the Parameters from the call. If the Cache Dependencies require dynamic values that are not passed to the method, then you may need to do custom caching inside of the function.

KenticoExamplePageTypeRepository.cs for examples.

PagesCacheDependency Attribute

Similar to the CacheDependency, except takes a Page Type and constructs the node|sitename|pagetype|all dependency.

Nuances

  • If you apply custom CacheDependency attributes, these will be used only instead of the automatic dependency generation.
  • Both CacheDependency and PagesCacheDependency replace ##SITENAME## with the Current Site's Code Name in the DependencyKeyFormat thanks to the CachingRepositoryDecorator.cs GetDependencyCacheKeyFromAttributes
  • If your method returns any class that is of type BaseInfo or IEnuemrable<BaseInfo> it applies a cache dependency of ObjectType|all. If it returns an object of class TreeNode, or IEnumerable<TreeNode>, then it applies a cache dependency of nodes|[sitename]|[classname]|all.

Repository that returns BaseInfo/TreeNode vs. Generic Model

While the Cache Dependency is set up to detect BaseInfo and TreeNode, this means your Repository Interface and Implementation is returning a Kentico EMS specific object. This does break one of the points of having your Repository’s abstracted into Interface, since you should be able to switch out your implementation with other technologies (say Kentico Cloud).

Having a Generic Model has many possible advantages in that your controllers and views are potentially decoupled from Kentico’s API, meaning Unit Testing is relatively easy, swapping out sources can be easier (such as Kentico Cloud), and upgrades should require much less refactoring (since all Kentico API code is segregated to the Repository and Service Implementations).

But it does come with a potentially significant overhead, such as creating new Generic Models for each content type you are pulling in from Kentico, time to convert those models, and loss of the "automatic" cache dependency logic.

Having your Repositories return BaseInfo/TreeNode types so the CachingRepositoryDecorator can add automatic dependencies for these calls, as well as leveraging the OutputCacheDependencies helper methods will make working with Cache a lot quicker.

Adhoc Caching

If you need something specifically cached, feel free to either use the ICacheHelper interface (Adding a property of this type to your Class Constructor will automatically provide you with the Kentico Implementation), or you can bypass that and use the Kentico.Helpers.CacheHelper where applicable.

Other Structures

Along with using IRepository for your data retrieval, you should practice using IService Interfaces and implementations if any data manipulation or retrieval requires technology specific calls. For example, if you have a service that retrieves Page Banners for a given page, that should be abstracted out so it too can be easily swapped out.

Caching Renderings (Output Caching)

The other type of data we wish to focus on is generally called “Output Caching.” This is caching that is intended to reduce load times or rendering times once the data has been retrieved. Here are the following caching scenarios, their pros and cons and how to implement.

Cache Entire Output ('Donut-Hole + Donut')

In this model, the entire output is cached. This is the default behavior of [OutputCache] attribute in MVC, in which the Logic and View rendering are all cached together.

The positive side of this is it’s very easy to implement, and for very static sites it’s often great to leverage.

The downside though is that (unless you do ajax calls client side) none of the content within the rendering can be dynamic and vary per user without generating unique caches for each variation. The entire result is cached, so if there is any variation, that’s another entirely cached result for that user.

The other downside is cache dependencies become difficult, as in theory you would need to know not only the main content’s dependencies, but also any dependencies in the header and footer. Say you add a Navigation Item, you should invalidate ALL Output Caches since the main Layout has changed for all of them.

Implementation

  1. Use the [OutputCache()] Attribute on your ActionResult.

See ExampleCacheController.cs.CachedViewByID

Partial View Caching ("Donut-Hole")

In this model, the main ActionResult is not cached, but instead individual elements of the rendering are cached. For example, your Layout view calls a Cached Partial View of the Navigation, and a Cached Partial View of the Footer.

The Positive side is then each section can have its own Cache Dependencies and can clear individual element’s caches if that cache needs clearing.

The Downside is the Logic done in your ActionResult is not cached, so that can increase load times, and any element not cached of course will render fully each time.

Implementation

  1. Do not use the [OutputCache] attribute on your main ActionResult
  2. Add [OutputCache] Attributes on any Partial View on elements that you want to cache
  3. Call @Html.RenderAction or @Html.RenderView in your Layouts to call these cached elements.

See ExampleCacheController.cs.IndexByID and the ExampleSharedLayout.cshtml @{Html.RenderAction("CachedPartialExample", "ExampleCache", new { Index = 1 }); } of ExampleCacheController.cs.CachedPartialExample

Action Result Caching ("Donut" ?)

Action Result Caching is a combination of both worlds. In this, the Logic of the main ActionResult is cached, but the rendering View is not.

The Positive you can still cache individual elements on the page (such as the navigation) and have those on separate Cache Dependencies, and you are caching the logic to render the Model that is passed to the view in your main ActionResult.

The Downside is if you want your Action Result’s View to be cached (the items not found in the Layout), then you may have to call separate Cached Partial Views and pass parts of your Model out, thus segmenting your View.

Let’s use the example of an ActionResult RenderBlog that gets a Blog Article with Related Blogs. While the Retrieval of the Model of the Blog + Related Blogs is cached, the Repeater to display all those Related Blogs would not be cached unless you made a separate Partial View (ex @Html.RenderAction(“RenderRelatedBlogs”, Model.RelatedBlogs)) and cached that.

Implementation

  1. Use [ActionResultCache] on your main ActionResult
  2. Use [OutputCache] on Partial Views that you wish to cache.
  3. Call @Html.RenderAction or @Html.RenderView in your Layouts to call these cached elements.

ExampleCacheController.cs.CachedActionByID and ExampleCacheController.cs.CachedPartialExample.

Output Cache Variables

Of the [OutputCache] and [ActionResultCache], there are a handful of properties that I wish to explain:

Duration

Time in seconds the cache should be enacted

VaryByParam

MVC Defaults to all parameters, but you can specify in a semi-colon separated list which parameters should be included in making the CacheKey. none is the keyword to signify you wish to ignore the Parameters altogether.

VaryByCustom

This acts as a sort of keyword that you can add multiple, even dynamic values to your CacheKey. How it works is when you add a value, the Application’s GetVeryByCustomString(HttpContext context, string custom) is called and returns the CacheKey to be added. You can overwrite this method in your Global.asax.cs to add different options.

Examples are provided of how to convert this custom string into the cache key, and how to make your own VaryBy extension methods. Kentico provides some defaults (VaryByHost, VaryByBrowser, VaryByUser, VaryByPersona, VaryByCookieLevel, VaryByABTestVariant), but you can create your own. In the end, all these do is concatenate all the Variations into a long string for the CacheKey (Ex User=Trevor_Browser=Chrome)

VaryByHeader

You can specify Header Parameters that should be included in the Cache Key creation. Can be useful if an API can return XML or JSON depending on the header’s content-type value, you would want to cache the XML and JSON separately.

VaryByCookie

You can specify Cookie values that should be included in the Cache Key Creation.

Examples

Please see the below files for examples:

Setting Cache Dependencies

As with the Caching, Cache dependencies on your output cache are important. How these operate is Cache Dependencies are added to the Current HttpResponse, then cache handlers can use those to find out which responses should be cleared when a cache dependency is touched.

The CMSRegistrationSource.cs implements Kentico’s various services which include it’s Cache Dependency checks and clearing of those caches when the appropriate elements are updated in the database.

To leverage this however, an IOutputCacheDependency interface was created to more easily allow the addition of Cache Item Dependencies.

Implementation

  1. On your Class Constructor, add a Private Member of type `IOutputCacheDependencies'
  2. Include IOutputCacheDependencies OutputCacheDependencies in your constructor, and save that value to your private member.
  3. Then, call the private member's AddCacheItemDependencies(IEnumerable<string>) or AddCacheItemDependency(string). These will add the dependencies.

Note for Repositories Returning Generic Models

When calling various Repositories, I recommend you include adding IEnumerble<string> Get_____CacheDependencies() methods , so even the cache keys can be abstracted out. Since a cache dependency key may look different on Kentico EMS vs say Kentico Cloud or some other repository.

See ExampleCacheController.CacheByActionID


Conclusion

I hope this helps you make your Kentico sites faster and more responsive, I would love your feedback and if you have any contributions, feel free to fork any of my repos and do a pull request!  Happy coding!
Comments
Blog post currently doesn't have any comments.
= seven - one
CONTENTEND