Data caching
The Xperience API allows developers to cache data in website code. We recommend caching any frequent API calls that load significant data from the Xperience database (or other external sources). For example, caching is typically a good idea when retrieving content in the code of your sites.
To cache data, the system provides the CMS.Helpers.IProgressiveCache
service.
The service supports sliding expiration (cache duration is refreshed upon successive requests for the same item), and progressive caching (parallel caching requests from multiple threads don’t result in redundant database operations; instead, one thread is used to fetch the data which is then shared with other waiting threads). Use the following methods to cache data:
Load
– loads data using a delegate method and caches the results.LoadAsync
– an equivalent of theLoad
method for loading and caching data in asynchronous code.
An instance of the service can be obtained using dependency injection.
See the following sections for examples of usage:
Cache reusable content items and pages
The following example demonstrates the usage of IProgressiveCache
to cache a content item retrieval operation on article pages. Assumes content type model classes generated by the code file generator.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CMS.ContentEngine;
using CMS.DataEngine;
...
// Instances of services for caching and content retrieval obtained via dependency injection
private readonly IContentQueryExecutor executor;
private readonly IProgressiveCache progressiveCache;
public async Task<IEnumerable<ArticlePage>> GetArticles(int topN = 0, CancellationToken cancellationToken = default)
{
// Caches the loaded data
return await progressiveCache.LoadAsync(async (cacheSettings) =>
{
// Gets the data to cache
var articles = (await GetAllArticles(topN, cancellationToken)).ToList();
// Configures cache dependencies for the data. See 'Set cache dependencies'
cacheSettings.CacheDependency = CacheHelper.GetCacheDependency(GetDependencyCacheKeys(articles));
return articles;
},
// Configures cache behavior and generates the cache key for the entry
new CacheSettings(cacheMinutes: 5,
useSlidingExpiration: true,
cacheItemNameParts: new[] { "MyWebsiteChannel", nameof(GetArticles), "/Articles", topN.ToString() }));
}
// Gets all articles according to the provided parameterization using content item query
private Task<IEnumerable<ArticlePage>> GetAllArticles(int topN, CancellationToken cancellationToken)
{
return executor
.GetMappedWebPageResult<ArticlePage>(
new ContentItemQueryBuilder()
.ForContentType($"{nameof(DancingGoat)}.{nameof(ArticlePage)}",
config => config
.WithLinkedItems(1)
.TopN(topN)
.OrderBy(OrderByColumn.Desc(nameof(ArticlePage.ArticlePagePublishDate)))
.ForWebsite("MyWebsiteChannel", PathMatch.Children("/Articles"))), cancellationToken: cancellationToken);
}
The example caches asynchronous retrieval of page content items from the database and ensures the following cache behavior:
- Duration: 5 minutes
- Cache key: MyWebsiteChannel|GetArticles|/Articles|<topN>
- Sliding expiration: true
Set cache dependencies
Correctly configuring cache dependencies is a critical part of building effective caching solutions. Cache dependencies inform the system whenever the source data of a cached entity changes and prompt it to revoke the corresponding cache entry. This is especially important in richly-linked content models with many dependencies between various content types, where any change to linked content items must clear the cache as well.
Building upon the caching example above, assume the article object being retrieved is modeled like so:
Then the following snippet shows a sample implementation of a GetDependencyCacheKeys
method that creates dependencies on the retrieved articles as well as their linked items. The example uses IWebPageLinkedItemsDependencyAsyncRetriever
to generate dependency cache keys for all linked content items of the page up to the specified depth. See Cache dependencies on linked content items.
using System;
using System.Linq;
using System.Collections.Generic;
using CMS.Websites;
using CMS.Helpers;
...
// Instances of services obtained via dependency injection
private readonly IWebPageLinkedItemsDependencyAsyncRetriever linkedItemsDependencyRetriever;
public async Task<ISet<string>> GetDependencyCacheKeys(IEnumerable<ArticlePage> articles)
{
// Builds a cache key for each linked content item associated with the articles,
// up to the depth of 1 (first-level references). The generated keys ensure
// the cache is cleared when linked items are modified.
var dependencyCacheKeys =
(await linkedItemsDependencyRetriever
.Get(articles.Select(article => article.SystemFields.ContentItemID), maxLevel: 1))
// Ensures unique values
.ToHashSet(StringComparer.InvariantCultureIgnoreCase);
// Adds cache dependencies on each page in the collection
foreach (var article in articles)
{
// Builds a cache key "webpageitem|byid|<pageId>" for each article
dependencyCacheKeys.Add(CacheHelper.BuildCacheItemName(new[] { "webpageitem",
"byid",
article.SystemFields.WebPageItemID.ToString() },
lowerCase: false));
// Builds a cache key "webpageitem|bychannel|MyWebsiteChannel|bypath|<pagePath>" for each article
dependencyCacheKeys.Add(CacheHelper.BuildCacheItemName(new[] { "webpageitem",
"bychannel",
"MyWebsiteChannel",
"bypath",
article.SystemFields.WebPageItemTreePath },
lowerCase: false));
}
// Creates the cache dependency object from the generated cache keys
return dependencyCacheKeys;
}
Cache dependencies on linked content items
For content items composed of multiple linked items, it may be required to trigger a cache refresh when not only the main object but also any of its linked items change.
To generate cache dependencies on linked items, use:
CMS.ContentEngine.ILinkedItemsDependencyAsyncRetriever
– for reusable content items.CMS.WebPages.IWebPageLinkedItemsDependencyAsyncRetriever
– for web pages.
Both services contain Get
methods that generate the dependencies based on content item identifiers and their content type definition. For example:
contentitem|byid|<contentItemId>
cms.contenttype|byname|<contentTypeCodeName>
See Set cache dependencies for an example of usage.
Preview mode and caching
We strongly suggest disabling caching for preview mode in order to prevent cache bloat. You can check whether the current request is under preview mode via IWebsiteChannelContext.IsPreview
. For example, you can create a helper method that you can use together with CacheSettings.Cached
.
using CMS.Helpers;
using CMS.Websites.Routing;
...
private bool IsCacheEnabled()
{
// An instance of IWebsiteChannelContext can be retrieved using dependency injection
return !websiteChannelContext.IsPreview;
}
...
await progressiveCache.LoadAsync(async (cacheSettings) =>
{
// Do not use the cache if under preview (e.g., when previewing pages in a channel)
cacheSettings.Cached = IsCacheEnabled();
...
},
// Configures cache behavior and generates the cache key for the entry
new CacheSettings(cacheMinutes: 10, cacheItemNameParts: "cacheKey");
Cache general objects
The following code example shows how to synchronously load and cache user data from the database using IProgressiveCache.Load
.
using CMS.DataEngine;
using CMS.Helpers;
using CMS.Membership;
...
// Instances of services used for data retrieval and caching (e.g., obtained using dependency injection)
private readonly IUserInfoProvider userInfoProvider;
private readonly IProgressiveCache progressiveCache;
// Caches the data for 10 minutes under the cache key "customdatasource|users"
// Automatically checks whether the given key is already in the cache
ObjectQuery<UserInfo> data = progressiveCache.Load(cs => LoadUsers(cs), new CacheSettings(10, "customdatasource|users"));
// Loads the required data. Called only if the data doesn't already exist in the cache.
private ObjectQuery<UserInfo> LoadUsers(CacheSettings cs)
{
// Loads all user objects from the database
ObjectQuery<UserInfo> result = UserInfo.Provider.Get();
// Sets a cache dependency for the data
// The data is removed from the cache if the objects represented by the dummy key are modified (all administration user objects in this case)
cs.CacheDependency = CacheHelper.GetCacheDependency("cms.user|all");
return result;
}
The caching logic checks if the key specified by the CacheSettings
object is in the cache:
- If yes, the method directly loads the data from the cache.
- If not, the code calls the method specified by the delegate parameter (
LoadUsers
in the example) with theCacheSettings
as a parameter. The method loads the data from the database, sets a cache dependency, and saves the key into the cache for the specified number of minutes
You can use the caching API when handling data anywhere in your code.
Asynchronous caching example
The following code example shows how to asynchronously load and cache user data from the database using IProgressiveCache.LoadAsync
.
using System;
using System.Threading.Tasks;
using CMS.Helpers;
...
// Instances of services used for data retrieval and caching (e.g., obtained using dependency injection)
private readonly IUserInfoProvider userInfoProvider;
private readonly IProgressiveCache progressiveCache;
// Asynchronously loads data and ensures caching
var data = await progressiveCache.LoadAsync(async cacheSettings =>
{
// Calls an async method that loads the required data (implementation not included in the example)
var result = await LoadUsersAsync();
cacheSettings.CacheDependency = CacheHelper.GetCacheDependency("cms.user|all");
return result;
}, new CacheSettings(TimeSpan.FromMinutes(10).TotalMinutes, "customdatasource|users"));
CacheSettings
When using IProgressiveCache
caching methods, you need to provide CMS.Helpers.CacheSettings
as a parameter. The settings configure the cache key that stores the data. If you set the same cache key name for multiple data loading operations, they share the same cached value.
You can work with the following properties of the CacheSettings
:
CacheSettings property | Type | Description |
CacheMinutes | int | The number of minutes for which the cache stores the loaded data. The default value is 10 minutes. We recommend using an interval of 1 to 60 minutes. |
CacheDependency | CMSCacheDependency | Sets dependencies for the cache key (use the Make sure you always set cache dependencies, unless the |
BoolCondition | bool | A boolean condition that must evaluate to |
Cached | bool | Indicates whether the data should be cached (based on |
AllowProgressiveCaching | bool | Enables or disables progressive caching, which ensures that multiple threads accessing the same data only load it once and reuse the result. |