Exam Area: Content Area 3 – Website Implementation & Delivery (25%)
Source: https://academy.optimizely.com/student/path/3128969/activity/4970333 | /4970334
Published: Feb 13–14, 2026
- Internal object cache: CMS repository-layer caching reduces database round-trips automatically.
- Fragment caching: Cache partial output in Razor using the
<cache>tag helper.- Output/response caching: Cache full responses selectively (only where output is identical for all users).
- HTTP headers + CDN: Use
Cache-Controlto guide intermediary/edge caching; configure CDN base paths for client resources.- Authoring vs Runtime: Two distinct performance contexts sharing infrastructure — optimize selectively.
using EPiServer.Framework.Cache;
public class MyService
{
private readonly ISynchronizedObjectInstanceCache _cache;
public MyService(ISynchronizedObjectInstanceCache cache)
{
_cache = cache;
}
public MyData GetData(int id)
{
var cacheKey = $"MyData:{id}";
// Try get from cache
var cached = _cache.Get(cacheKey) as MyData;
if (cached != null) return cached;
// Load from DB
var data = LoadFromDatabase(id);
// Store in cache
var evictionPolicy = new CacheEvictionPolicy(
TimeSpan.FromMinutes(10),
CacheTimeoutType.Sliding);
_cache.Insert(cacheKey, data, evictionPolicy);
return data;
}
public void InvalidateCache(int id)
{
_cache.Remove($"MyData:{id}");
}
}
// Simpler interface
public class SimpleCache
{
private readonly IObjectInstanceCache _cache;
public SimpleCache(IObjectInstanceCache cache)
{
_cache = cache;
}
public T GetOrCreate<T>(string key, Func<T> factory, TimeSpan? duration = null) where T : class
{
return _cache.ReadThrough(
key,
factory,
duration ?? TimeSpan.FromMinutes(10));
}
}
// Absolute expiration
var policy = new CacheEvictionPolicy(
TimeSpan.FromHours(1),
CacheTimeoutType.Absolute);
// Sliding expiration (reset on access)
var policy = new CacheEvictionPolicy(
TimeSpan.FromMinutes(10),
CacheTimeoutType.Sliding);
// Cache dependent on master keys
var policy = new CacheEvictionPolicy(
masterKeys: new[] { "MasterKey1", "MasterKey2" },
cacheKeys: new[] { "DependentKey" });
// Combination
var policy = new CacheEvictionPolicy(
TimeSpan.FromHours(1),
CacheTimeoutType.Absolute,
new[] { "DependentKey1" });
CMS automatically manages cache invalidation when content changes:
// Cache key based on ContentReference
var cacheKey = _contentCacheKeyCreator.CreateCommonCacheKey(contentLink);
// Invalidated when content is published
// CMS handles this automatically for content cache
<cache> tag helper):<cache expires-after="00:10:00">
@await Html.PartialAsync("_Menu")
</cache>
// Only for content identical for ALL users
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]
public IActionResult Index()
{
return View();
}
builder.Services.AddResponseCaching();
app.UseResponseCaching();
Warning: Disable caching on personalized or editor-specific pages — aggressive caching can prevent editors from seeing updated content.
// For multi-server environments
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "redis-connection-string:6380,ssl=True,abortConnect=False";
options.InstanceName = "MyCMS:";
});
// Use IDistributedCache
public class MyDistributedService
{
private readonly IDistributedCache _distributedCache;
public async Task<string> GetValueAsync(string key)
{
var cached = await _distributedCache.GetStringAsync(key);
if (cached != null) return cached;
var value = LoadValue();
await _distributedCache.SetStringAsync(key, value, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});
return value;
}
}
{
"EPiServer": {
"Cms": {
"ClientResources": {
"BasePath": "https://yourcdn.example.com"
}
}
}
}
Cache-Control: max-age=3600style.css?v=2 (CDN treats it as a new resource)| Dimension | Focus |
|---|---|
| Runtime | Page rendering, API latency, CDN delivery, caching effectiveness, DB access |
| Authoring-time | CMS UI responsiveness, publishing latency, indexing speed, tree browsing |
SavingContent/PublishedContent lightweightRule: Use selective caching + environment-aware configuration — never blanket-cache.
// Read-only cache for immutable objects (better for performance)
var readOnlyCache = ServiceLocator.Current.GetInstance<IReadOnlyObjectCache>();
// Or inject
public MyService(IReadOnlyObjectCache readOnlyCache)
{
_readOnlyCache = readOnlyCache;
}
// 1. Use meaningful cache keys
var key = $"Article:{articleId}:Lang:{language}";
// 2. Cache at the correct layer (service layer, not controller)
// 3. Invalidate correctly when data changes
// 4. Do not cache sensitive data
// 5. Monitor cache hit rate
// 6. Avoid caching too much (memory pressure)
// 7. Use sliding expiration for frequently accessed items
// 8. Use absolute expiration for time-sensitive data
[InitializableModule]
public class CacheInvalidationModule : IInitializableModule
{
private readonly IObjectInstanceCache _cache;
public void Initialize(InitializationEngine context)
{
var events = context.Locate.Advanced.GetInstance<IContentEvents>();
events.PublishedContent += InvalidateCache;
events.DeletedContent += InvalidateCache;
}
private void InvalidateCache(object sender, ContentEventArgs e)
{
_cache.Remove($"MyContent:{e.ContentLink.ID}");
}
}
ISynchronizedObjectInstanceCache)<cache> tag helper used for? (Fragment caching — caches partial view output in Razor)[ResponseCache]? (When the page has personalized content — all users would receive the same cached response)EPiServer.Cms.ClientResources.BasePath in appsettings.json)