26. Output Caching
- Output Caching
About this chapter
Implement server-side output caching with tag-based invalidation to cache responses and automatically clear caches when underlying data changes.
- Output caching overview: Server-side caching more flexible than response caching
- Configuration: Setting default and named policies with expiration
- Named policies: Creating specific caching rules for different endpoints
- Tag-based invalidation: Marking caches with tags for targeted clearing
- Cache busting: Invalidating caches when data is created, updated, or deleted
- Comparing strategies: Output caching vs response caching use cases
Learning outcomes:
- Install and configure output caching middleware
- Create named caching policies for different endpoints
- Use OutputCache attribute on controllers and actions
- Implement tag-based cache invalidation
- Automatically invalidate caches on mutations
- Combine response and output caching strategies
26.1 Output Caching Overview
Output caching is newer and more flexible than response caching. It caches at the server level and can be invalidated by tags.
dotnet add package Microsoft.AspNetCore.OutputCaching
// Program.cs
builder.Services.AddOutputCache(options =>
{
// Default policy: cache all responses for 60 seconds
options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(60)));
// Named policies for specific endpoints
options.AddPolicy("GetPlatforms", builder =>
builder
.Expire(TimeSpan.FromMinutes(5))
.Tag("platforms")); // Tag for invalidation
options.AddPolicy("GetCommands", builder =>
builder
.Expire(TimeSpan.FromMinutes(1))
.Tag("commands")
.VaryByQuery("pageIndex", "pageSize")); // Different cache per params
options.AddPolicy("NoCache", builder =>
builder.NoCache());
});
var app = builder.Build();
app.UseOutputCache(); // Must be before MapControllers
app.MapControllers();
26.2 Using Output Caching
[HttpGet]
[OutputCache(PolicyName = "GetPlatforms")]
public async Task<ActionResult> GetPlatforms(int pageIndex = 1, int pageSize = 10)
{
// Cached for 5 minutes, tagged as "platforms"
var platforms = await _repository.GetPlatformsAsync(pageIndex, pageSize);
return Ok(platforms);
}
[HttpPost]
public async Task<ActionResult> CreatePlatform(PlatformMutateDto dto)
{
var platform = _mapper.Map<Platform>(dto);
await _repository.CreatePlatformAsync(platform);
await _repository.SaveChangesAsync();
// Invalidate all caches tagged "platforms"
// When someone creates a new platform, the list is stale
await _outputCacheStore.EvictByTagAsync("platforms", default);
return CreatedAtRoute(nameof(GetPlatformById), new { id = platform.Id }, platform);
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeletePlatform(int id)
{
var platform = await _repository.GetPlatformByIdAsync(id);
_repository.DeletePlatform(platform);
await _repository.SaveChangesAsync();
// Invalidate both platform and command caches since we're removing data
await _outputCacheStore.EvictByTagAsync("platforms", default);
await _outputCacheStore.EvictByTagAsync("commands", default);
return NoContent();
}
Tag-based Invalidation: When data changes, clear all caches tagged with that data type. Next request refills them.
Output Caching vs Response Caching:
- Response Caching: Client-side (browser, proxy), controlled by HTTP headers
- Output Caching: Server-side, fully controlled by code, easier to invalidate
Use both:
- Response caching for truly cacheable public data (products, documentation)
- Output caching for API responses that change when data is modified