🌐 Website Implementation
Performance Caching
📖 Docs

Performance and Caching - Optimizely CMS 12

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


Overview (Academy Key Points)

  • 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-Control to guide intermediary/edge caching; configure CDN base paths for client resources.
  • Authoring vs Runtime: Two distinct performance contexts sharing infrastructure — optimize selectively.

1. Object Caching

ISynchronizedObjectInstanceCache:

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}");
    }
}

2. IObjectInstanceCache (Simpler)

// 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));
    }
}

3. CacheEvictionPolicy

// 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" });

4. Content-based Cache Keys

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

5. Output Caching / Response Caching (Academy – 4970333/4970334)

Fragment caching (Razor <cache> tag helper):

<cache expires-after="00:10:00">
    @await Html.PartialAsync("_Menu")
</cache>

Response caching (full page controller):

// Only for content identical for ALL users
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]
public IActionResult Index()
{
    return View();
}

Middleware setup:

builder.Services.AddResponseCaching();
app.UseResponseCaching();

Warning: Disable caching on personalized or editor-specific pages — aggressive caching can prevent editors from seeing updated content.


6. Distributed Cache (Redis)

// 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;
    }
}

7. CDN (Content Delivery Network) – Academy 4970333

Configure CDN base path for client resources:

{
  "EPiServer": {
    "Cms": {
      "ClientResources": {
        "BasePath": "https://yourcdn.example.com"
      }
    }
  }
}

Cache invalidation strategies (Academy):

  1. Time-based expirationCache-Control: max-age=3600
  2. Versioning/fingerprintingstyle.css?v=2 (CDN treats it as a new resource)
  3. Manual purging — For urgent updates; requires support on DXP environments

7b. Authoring-time vs Runtime Performance (Academy – 4970334)

DimensionFocus
RuntimePage rendering, API latency, CDN delivery, caching effectiveness, DB access
Authoring-timeCMS UI responsiveness, publishing latency, indexing speed, tree browsing

Authoring-time best practices:

Shared infrastructure conflict:

Rule: Use selective caching + environment-aware configuration — never blanket-cache.


8. Read-Only Object Cache

// Read-only cache for immutable objects (better for performance)
var readOnlyCache = ServiceLocator.Current.GetInstance<IReadOnlyObjectCache>();

// Or inject
public MyService(IReadOnlyObjectCache readOnlyCache)
{
    _readOnlyCache = readOnlyCache;
}

9. Caching Best Practices

// 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

10. Cache Invalidation with CMS Events

[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}");
    }
}

Review Questions

  1. What is the primary interface for caching objects? (ISynchronizedObjectInstanceCache)
  2. What is the <cache> tag helper used for? (Fragment caching — caches partial view output in Razor)
  3. When should you not use [ResponseCache]? (When the page has personalized content — all users would receive the same cached response)
  4. Where is the CDN base path configured? (EPiServer.Cms.ClientResources.BasePath in appsettings.json)
  5. How does authoring-time performance differ from runtime? (Authoring = CMS UI/publish speed; Runtime = visitor rendering speed)
  6. Why is selective caching important? (Prevents editorial workflows from being broken by aggressive caching)
  7. Which cache invalidation strategy is best for urgent content updates? (Manual purging — but time-based + versioning should serve as the baseline)