Caching the output of personalized content
When using ASP.NET output caching to improve the performance of your MVC website, you need to take additional steps to ensure that the output cache correctly stores personalized pages. Because personalized pages serve different content based on various conditions, you need to adjust your application to cache separate output versions according to the personalization criteria.
To cache different versions of content, use the OutputCache attribute together with a custom implementation of the GetVaryByCustomString method.
Configuring output caching using custom strings
Kentico offers a fluent API built around .NET framework’s VaryByCustomString output caching feature that allows you to construct output cache keys according to your project’s caching requirements. The system provides an IOutputCacheKeyOptions interface that allows you to create configurable objects for building output cache keys. The options object is configured by chaining any number of VaryBy methods (representing cache key parts). The complete cache key is then constructed by calling OutputCacheKeyHelper.GetVaryByCustomString with the options object as the parameter.
VaryBy methods
By default, the system allows you to construct the cache key using the following request variables (extension methods for IOutputCacheKeyOptions objects):
Method |
Namespace |
Cache key |
VaryByBrowser |
Kentico.Web.Mvc |
The name and the major version of the client’s browser. |
VaryByHost |
Kentico.Web.Mvc |
The host name of the server. |
VaryBySite |
Kentico.Web.Mvc |
The code name of the site. |
VaryByUser |
Kentico.Web.Mvc |
The name of the current user. |
VaryByCookieLevel |
Kentico.Web.Mvc |
The cookie level of the current user. |
VaryByPersona |
Kentico.OnlineMarketing.Web.Mvc |
|
VaryByABTestVariant |
Kentico.OnlineMarketing.Web.Mvc |
The GUID of the A/B test variant assigned for the current request. |
Example – Configuring output caching
The following steps describe how to cache multiple versions of personalized output using custom strings:
Edit your project’s Global.asax.cs file and override the GetVaryByCustomString method.
The method’s custom parameter contains the string that you set in the VaryByCustom property of OutputCache attributes in your controllers.
For each custom string, you need to define the caching requirements – use VaryBy methods on an IOutputCacheKeyOptions object to create a set of request variables according to which the output cache needs to vary.
using System; using System.Web; using Kentico.Web.Mvc; using Kentico.OnlineMarketing.Web.Mvc;
Examplepublic override string GetVaryByCustomString(HttpContext context, string custom) { // Creates the options object used to store individual cache key parts IOutputCacheKeyOptions options = OutputCacheKeyHelper.CreateOptions(); // Selects a caching configuration according to the current custom string switch (custom) { case "Default": // Sets the variables that compose the cache key for the 'Default' VaryByCustom string options .VaryByHost() .VaryByBrowser() .VaryByUser(); break; case "OnlineMarketing": // Sets the variables that compose the cache key for the 'OnlineMarketing' VaryByCustom string options .VaryByCookieLevel() .VaryByPersona() .VaryByABTestVariant(); break; } // Combines individual 'VaryBy' key parts into a cache key under which the output is cached string cacheKey = OutputCacheKeyHelper.GetVaryByCustomString(context, custom, options); // Returns the constructed cache key if (!String.IsNullOrEmpty(cacheKey)) { return cacheKey; } // Calls the base implementation if the provided custom string does not match any predefined configurations return base.GetVaryByCustomString(context, custom); }
Important
The cache keys created in the example above ensure caching for a broad range of variables contained within each request. This may be unnecessary for your website. For optimal caching, you need to prepare output cache variables tailored according to personalization conditions that you use on the site or individual pages.
For example, pages that check for contact personas in their personalization condition only require the persona identifier in the cache variables, and you can share the cached content for other variables, such as contact groups to which the contacts belong. On the other hand, strongly personalized pages with dynamic content may require separate cache versions for every contact.
You always need to balance website performance, memory usage on the server, and the risk of displaying incorrect cached content.
Edit the controller classes that serve personalized content on your MVC site.
Set the VaryByCustom property of OutputCache attributes to one of the specified configurations according to the action’s caching requirements.
Exampleusing System.Web.Mvc; using CMS.ContactManagement; using LearningKit.Models.Personalization; namespace LearningKit.Controllers { public class PersonalizationController : Controller { /// <summary> /// Gets the current contact, if contact tracking is enabled for the connected Kentico instance. /// </summary> private ContactInfo CurrentContact => ContactManagementContext.GetCurrentContact(); /// <summary> /// Displays a page with a personalized greeting. /// The content depends on whether the current contact belongs to the "YoungCustomers" persona. /// Caches the output for 10 minutes, with different cache versions defined by the "OnlineMarketing" custom string. /// The "OnlineMarketing" configuration separately caches each combination of persona and AB test variant variables. /// </summary> [OutputCache(Duration = 600, VaryByCustom = "OnlineMarketing")] public ActionResult PersonalizedGreeting() { CurrentContactViewModel model; // If on-line marketing is disabled, CurrentContact is null if (CurrentContact != null) { model = new CurrentContactViewModel(CurrentContact); } else { model = null; } return View(model); } } }
The application now caches separate versions of action output based on your custom variables. This ensures that visitors do not receive incorrect cached content when viewing personalized pages.
Varying the output cache using additional variables
In addition to the cache variables provided by the system, you can vary the cache key based on any accessible request variable.
There are two ways to extend the general support provided by Kentico:
- Implement additional extension methods for IOutputCacheKeyOptions
- Modify the constructed cache key by appending additional request variables
Implementing additional cache key parts
You can extend the provided support by implementing additional cache key parts for IOutputCacheKeyOptions. This approach allows for easy reusability of the implemented methods across multiple projects.
Cache key parts consist of:
- A class implementing the IOutputCacheKey interface. The interface defines members necessary to implement additional cache key parts.
- An extension method for IOutputCacheKeyOptions. The method needs to add an instance of the IOutputCacheKeyobject to the cache key part collection of the options object by calling IOutputCacheKeyOptions.AddCacheKey.
The following sample demonstrates the implementation of a cache key that varies cached content based on a contact’s gender.
using System.Web;
using CMS.ContactManagement;
using Kentico.Web.Mvc;
namespace OutputCacheCustomization
{
public class ContactGenderOutputCacheKey : IOutputCacheKey
{
// Used as a prefix for this cache key part
public string Name => "KenticoContactGender";
// Invoked when constructing a cache key from the configured 'IOutputCacheKeyOptions' options object
public string GetVaryByCustomString(HttpContextBase context, string custom)
{
// Gets the current contact, without creating a new anonymous contact for new visitors
ContactInfo existingContact = ContactManagementContext.GetCurrentContact(createAnonymous: false);
// Gets the contact's gender
int? contactGender = existingContact?.ContactGender;
return $"{Name}={contactGender}";
}
}
}
Create a corresponding extension method for IOutputCacheKeyOptions. The method adds an instance of the class to an IOutputCacheKeyOptions object using the AddCacheKeymethod.
using Kentico.Web.Mvc;
namespace OutputCacheCustomization
{
public static class OutputCacheKeyOptionsExtensions
{
// Varies the output cache based on the contact's gender
public static IOutputCacheKeyOptions VaryByContactGender(this IOutputCacheKeyOptions options)
{
// Adds the ContactGenderOutputCacheKey to the options object
options.AddCacheKey(new ContactGenderOutputCacheKey());
return options;
}
}
}
You can now use the ContactGender variable when building cache keys in the site’s GetVaryByCustomString implementation.
Modifying the constructed cache key
An alternative approach to implementing reusable extension methods is constructing the cache key using OutputCacheKeyHelper.GetVaryByCustomString and further customizing it by directly appending additional request variables.
The following example implements a custom GetContactGroupsCacheKey method that returns a semicolon-separated list of all contact groups to which the current contact belongs. The string is appended to the cache key constructed by the GetVaryByCustomString helper method, adding another level of variance to the output cache.
private string GetContactGroupsCacheKey()
{
// Gets the current contact without creating a new anonymous contact for new visitors
var existingContact = ContactManagementContext.GetCurrentContact(createAnonymous: false);
if (existingContact == null)
{
return String.Empty;
}
// Gets the contact groups assigned to the contact
var groups = existingContact.ContactGroups?.Select(x => x.ContactGroupID).OrderBy(x => x).ToArray() ?? new int[] { };
// Creates a string used to vary the resulting cache key
var groupsKey = String.Join(";", groups);
return $"ContactGroups={groupsKey}";
}
public override string GetVaryByCustomString(HttpContext context, string custom)
{
// The configuration of the 'IOutputCacheKeyOptions' object is omitted
...
// Combines individual 'VaryBy' components into a cache key under which the output is cached
string cacheKey = OutputCacheKeyHelper.GetVaryByCustomString(context, custom, options);
// Prepares an empty string object for the contact groups key part
string contactGroupsKey = String.Empty;
// Checks if the current request requires caching based on contact groups
// As an example, we use contact group caching for the 'OnlineMarketing' configuration
if (String.Equals(custom, "OnlineMarketing", StringComparison.InvariantCultureIgnoreCase))
{
contactGroupsKey = GetContactGroupsCacheKey();
}
// Appends the contact groups string to the end of the constructed cache key
// Separates the key part using the pipe '|' character - a separator used internally
// for all cache key parts by the 'OutputCacheKeyHelper.GetVaryByCustomString' method
if (!String.IsNullOrEmpty(contactGroupsKey))
{
cacheKey = String.Join("|", cacheKey, contactGroupsKey);
}
// Returns the constructed cache key
if (!String.IsNullOrEmpty(cacheKey))
{
return cacheKey;
}
// Calls the base implementation if the provided custom string does not match any predefined configuration
return base.GetVaryByCustomString(context, custom);
}