ASP.NET Core Output Caching: Reduce Expensive Endpoint Work with Server‑Side Cache
Learn how ASP.NET Core’s Output Cache middleware can store entire HTTP responses, cut database calls, and reduce latency on expensive endpoints. Start with a short duration and scale as needed.
01 Oct 2026, 11:18 UTC

Concrete Problem: Expensive Endpoints Re‑Exposed
When a web API performs a costly database aggregation or a CPU‑heavy calculation on every request, the server spends the same work repeatedly. If the data rarely changes, each request could be served from a cache instead of recomputing.
Thesis: Output Cache Middleware Stores Full HTTP Responses
The Output Cache middleware, added in .NET 7, caches the entire body, headers, and status code of a response in memory (or a distributed store). Subsequent identical requests are answered directly from the cache, bypassing the endpoint and any downstream services.
1. What Is Output Caching?
Unlike HTTP response caching, which relies on client or proxy headers, output caching is explicit and server‑side. The middleware creates a cache key based on the request path, method, and any VaryBy settings. If a cached entry exists, the middleware writes it to the response stream and short‑circuits the pipeline.
2. Configuring the Middleware
- Add the services:
builder.Services.AddOutputCache(); - Register the middleware:
app.UseOutputCache(); - Define a policy (global or named):
builder.Services.AddOutputCache(options => { options.AddPolicy("ReportCache", policy => policy .Duration(TimeSpan.FromSeconds(60)) .VaryByQuery("region") .Tag("ReportData")); }); - Apply to an endpoint:
- Attribute style (minimal API):
[OutputCache(Duration = 60)] app.MapGet("/report", GetReport); - Policy name style:
app.MapGet("/report", GetReport) .RequireOutputCache("ReportCache");
- Attribute style (minimal API):
3. Worked Example: A Minimal API Returning a Slow Report
Assume GetReport queries a database and returns a JSON report. The following demonstrates output caching with a 30‑second duration.
app.MapGet("/slow-report", async (AppDb db) =>
{
// Simulate expensive work
await Task.Delay(2000);
var data = await db.Reports.ToListAsync();
return Results.Json(data);
})
[OutputCache(Duration = 30)];
After enabling the attribute:
- First request: 2 s latency, database hit.
- Subsequent requests within 30 s: ~0 ms latency, no database call.
- After 30 s: cache expires, next request recomputes.
To verify cache hits, enable the middleware’s diagnostics:
builder.Services.AddOutputCache(options =>
{
options.AddPolicy("ReportCache", policy => policy
.Duration(30)
.Diagnostic();
});
When Diagnostic() is enabled, the response header X-Output-Cache appears with values Hit or Miss.
4. Trade‑Offs and Limitations
- Invalidation: The middleware does not automatically purge entries when underlying data changes. Use short durations,
Tagand manual eviction viaOutputCacheService, or trigger eviction on data updates. - Memory Pressure: In‑memory store scales with payload size. For large responses or high traffic, consider configuring a distributed cache (e.g., Redis) as the store.
- Personalization: Do not cache authenticated responses unless you vary by user identity. Otherwise, one user may see another’s data.
- Distributed Environments: The default store is per‑process. In a web‑farm, each instance holds its own cache, leading to inconsistent responses unless a shared store is used.
- Streaming and Large Files: Output caching buffers the entire response. Avoid caching large file downloads.
5. Actionable Next Steps
- Identify endpoints that perform expensive, read‑only work.
- Start with a short duration (e.g., 30 s) to observe latency gains.
- Add
VaryByQueryorVaryByHeaderif the response changes per client. - Monitor memory usage; if it spikes, switch to a distributed cache:
- Implement manual eviction in your data‑update logic:
builder.Services.AddOutputCache(options =>
{
options.UseRedis("localhost:6379");
});
var cache = app.Services.GetRequiredService<IOutputCacheService>();
await cache.EvictAsync("ReportData");
By following these steps, you can reduce redundant work, lower CPU and database load, and deliver faster responses to clients—all while keeping control over cache behavior.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.