Home C# / .NET Mastering Output Caching & HybridCache in ASP.NET Core
Advanced 3 min · July 13, 2026

Mastering Output Caching & HybridCache in ASP.NET Core

Learn how to implement output caching and HybridCache in ASP.NET Core 8+.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of ASP.NET Core (controllers, middleware, services)
  • Familiarity with dependency injection
  • Understanding of HTTP and REST APIs
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Output caching stores entire HTTP responses to reduce server load and improve response times.
  • HybridCache combines in-memory and distributed caching for flexibility and performance.
  • Use [OutputCache] attribute to cache controller actions or Razor Pages.
  • HybridCache supports cache tags, eviction policies, and serialization.
  • Proper cache invalidation is critical to avoid serving stale data.
✦ Definition~90s read
What is Output Caching and HybridCache in ASP.NET Core?

Output caching and HybridCache are ASP.NET Core features that store HTTP responses or data objects to improve application performance and scalability.

Imagine you run a busy bakery.
Plain-English First

Imagine you run a busy bakery. Instead of baking a fresh cake for every customer who orders the same type, you bake a few in advance and keep them ready. Output caching is like that: it stores the final webpage (the cake) so that when many users request the same page, the server just serves the cached copy instead of rebuilding it from scratch. HybridCache is like having both a display case (fast but small) and a freezer (slower but bigger) – you use the best storage based on how often the cake is requested.

In modern web applications, performance is paramount. Users expect pages to load in milliseconds, and servers must handle thousands of concurrent requests without breaking a sweat. One of the most effective ways to achieve this is through caching. ASP.NET Core offers powerful caching mechanisms, and with the release of .NET 8, a new player entered the scene: HybridCache. This article dives deep into output caching and HybridCache, showing you how to implement them in production-ready applications. You'll learn the differences between traditional output caching and the new HybridCache, when to use each, and how to avoid common pitfalls. We'll explore real-world scenarios, such as caching a dashboard that displays frequently updated data, and walk through debugging techniques for when things go wrong. By the end, you'll be equipped to boost your app's performance significantly while maintaining data freshness.

What is Output Caching?

Output caching is a technique that stores the entire HTTP response generated by an endpoint (controller action, Razor Page, or Minimal API) and serves it for subsequent identical requests. This avoids re-executing the server-side logic, reducing CPU load and improving response times. In ASP.NET Core, output caching is implemented via middleware and attributes. It supports vary-by rules (e.g., by query string, header, or user) to cache different versions of the same endpoint. The cache can be stored in-memory or in a distributed cache like Redis. Output caching is ideal for endpoints that produce the same response for many users, such as a list of products or a static homepage.

OutputCacheExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache(options =>
{
    options.DefaultExpirationTimeSpan = TimeSpan.FromMinutes(10);
    options.AddPolicy("VaryByQuery", policy =>
    {
        policy.VaryByQuery("category");
    });
});

var app = builder.Build();
app.UseOutputCache();

app.MapGet("/products", () =>
{
    return Results.Ok(new[] { "Product1", "Product2" });
}).CacheOutput(policy => policy.Expire(TimeSpan.FromMinutes(5)));

app.Run();
🔥Output Caching vs Response Caching
📊 Production Insight
Always set a reasonable expiration time. Too long leads to stale data; too short defeats the purpose. Monitor cache hit ratios.
🎯 Key Takeaway
Output caching stores entire HTTP responses server-side, reducing processing for identical requests.

Introducing HybridCache

HybridCache is a new caching abstraction introduced in .NET 8 that combines an in-memory cache (L1) with a distributed cache (L2) like Redis. It provides a unified API for caching data, automatically managing the two tiers. The in-memory cache offers low latency, while the distributed cache provides durability and scalability across multiple servers. HybridCache supports cache tags, which allow you to invalidate groups of cache entries together. It also handles serialization and deserialization of cached objects. This is particularly useful for caching complex objects like database query results or computed data. HybridCache is not a replacement for output caching; rather, it's a lower-level caching mechanism that you can use in your services and middleware.

HybridCacheExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Install-Package Microsoft.Extensions.Caching.Hybrid
// Program.cs
using Microsoft.Extensions.Caching.Hybrid;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(1)
    };
});
// Add distributed cache (e.g., Redis)
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

var app = builder.Build();

app.MapGet("/data", async (HybridCache cache) =>
{
    var data = await cache.GetOrCreateAsync("mykey", async cancel =>
    {
        // Simulate expensive operation
        await Task.Delay(1000, cancel);
        return new { Timestamp = DateTime.UtcNow, Value = 42 };
    });
    return Results.Ok(data);
});

app.Run();
💡HybridCache Requires a Distributed Cache Backend
📊 Production Insight
Set local cache expiration shorter than global expiration to ensure freshness. Use cache tags to invalidate related entries.
🎯 Key Takeaway
HybridCache provides a two-tier caching system with automatic synchronization and tag-based invalidation.

Configuring Output Caching Policies

Output caching policies define how responses are cached and vary. You can create named policies in the AddOutputCache call and apply them to endpoints using the [OutputCache] attribute or the CacheOutput extension method. Policies can specify expiration, vary-by rules (query, header, route, user), and whether to cache authenticated responses. For example, you might want to cache a product listing but vary by the 'category' query parameter. You can also lock cache entries to prevent multiple requests from triggering the same expensive operation simultaneously (cache stampede protection).

PolicyExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Program.cs
builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("ProductPolicy", policy =>
    {
        policy.Expire(TimeSpan.FromMinutes(5));
        policy.VaryByQuery("category");
        policy.VaryByHeader("Accept-Language");
        policy.Tag("products");
    });
});

// Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [OutputCache(PolicyName = "ProductPolicy")]
    public IActionResult GetProducts([FromQuery] string category)
    {
        // Simulate database call
        return Ok(new[] { $"Product in {category}" });
    }
}
⚠ Avoid Caching Authenticated Responses
📊 Production Insight
Use cache tags to invalidate groups of entries. For example, tag all product-related caches with 'products' and invalidate when a product is updated.
🎯 Key Takeaway
Policies allow fine-grained control over caching behavior, including vary-by rules and tags.

Cache Invalidation Strategies

Cache invalidation is the process of removing or updating cached data when the underlying source changes. Without proper invalidation, users see stale data. In ASP.NET Core output caching, you can invalidate entries by tag using the IOutputCacheStore interface. For HybridCache, you can use the RemoveByTagAsync method. Common strategies include: time-based expiration (TTL), event-driven invalidation (e.g., when a product is updated, invalidate its cache), and dependency-based invalidation (e.g., cache depends on a database row version). For output caching, you can also use the [InvalidateOutputCache] attribute to invalidate caches when a write operation occurs.

InvalidationExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Invalidation using IOutputCacheStore
public class ProductService
{
    private readonly IOutputCacheStore _cacheStore;
    public ProductService(IOutputCacheStore cacheStore)
    {
        _cacheStore = cacheStore;
    }

    public async Task UpdateProduct(int id)
    {
        // Update database
        await _cacheStore.EvictByTagAsync("products", CancellationToken.None);
    }
}

// HybridCache invalidation
public class OrderService
{
    private readonly HybridCache _cache;
    public OrderService(HybridCache cache)
    {
        _cache = cache;
    }

    public async Task PlaceOrder()
    {
        // Process order
        await _cache.RemoveByTagAsync("orders");
    }
}
🔥Cache Stampede Protection
📊 Production Insight
For high-traffic endpoints, use cache stampede protection to avoid thundering herd problems.
🎯 Key Takeaway
Invalidation is critical. Use tags and event-driven logic to keep caches fresh.

HybridCache with Redis in Production

In production, HybridCache typically uses Redis as the distributed cache backend. Redis provides high availability and persistence. To configure HybridCache with Redis, you need to add the Microsoft.Extensions.Caching.StackExchangeRedis package and register the Redis cache service. HybridCache automatically uses it as the L2 cache. You can also configure serialization (e.g., JSON or protobuf) for cached objects. It's important to set appropriate timeouts and connection limits to avoid bottlenecks. Additionally, consider using Redis Cluster for large-scale deployments.

RedisHybridCache.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Program.cs
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.Caching.StackExchangeRedis;

var builder = WebApplication.CreateBuilder(args);

// Add Redis distributed cache
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "MyApp";
});

// Add HybridCache
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(10),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

var app = builder.Build();

app.MapGet("/user/{id}", async (int id, HybridCache cache) =>
{
    var user = await cache.GetOrCreateAsync($"user-{id}", async cancel =>
    {
        // Fetch from database
        await Task.Delay(500, cancel);
        return new { Id = id, Name = "John Doe" };
    });
    return Results.Ok(user);
});

app.Run();
💡Serialization Matters
📊 Production Insight
Monitor Redis memory usage and set maxmemory policy. Use separate Redis instances for caching vs. other concerns.
🎯 Key Takeaway
HybridCache with Redis provides a scalable, two-tier caching solution for production.

Security Considerations for Caching

Caching can introduce security vulnerabilities if not handled carefully. Never cache responses that contain sensitive user data (e.g., personal information, authentication tokens). Use vary-by-user or disable caching for authenticated endpoints. Be cautious with caching responses that depend on user roles or permissions. Additionally, ensure that cache keys are not predictable to prevent cache poisoning attacks. For output caching, use the [OutputCache(NoStore = true)] attribute on actions that should never be cached. For HybridCache, avoid caching sensitive data altogether, or encrypt the cached values.

SecureCaching.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Disable caching for authenticated endpoints
[Authorize]
[OutputCache(NoStore = true)]
public IActionResult MyProfile()
{
    return Ok(new { User = User.Identity.Name });
}

// HybridCache with encryption (conceptual)
// Use a custom serializer that encrypts data before caching
public class EncryptedCacheSerializer : IHybridCacheSerializer<MyData>
{
    public MyData Deserialize(ReadOnlySpan<byte> source)
    {
        var decrypted = Decrypt(source);
        return JsonSerializer.Deserialize<MyData>(decrypted);
    }

    public void Serialize(MyData value, IBufferWriter<byte> target)
    {
        var json = JsonSerializer.SerializeToUtf8Bytes(value);
        var encrypted = Encrypt(json);
        target.Write(encrypted);
    }
}
⚠ Cache Poisoning
📊 Production Insight
Implement cache key validation to prevent injection attacks. Use a whitelist of allowed vary-by values.
🎯 Key Takeaway
Never cache sensitive data. Use vary-by-user or disable caching for authenticated endpoints.

Performance Monitoring and Optimization

To ensure your caching strategy is effective, monitor cache hit ratios, memory usage, and latency. ASP.NET Core provides metrics for output caching via the Microsoft.AspNetCore.OutputCaching counters. For HybridCache, you can use the IDistributedCache metrics. Use Application Insights or Prometheus to collect and visualize these metrics. Optimize by adjusting cache sizes, expiration times, and eviction policies. For in-memory cache, set a size limit to prevent memory exhaustion. For distributed cache, ensure network latency is low. Also, consider using cache warming to pre-populate caches after a deployment.

MonitoringExample.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Enable metrics in Program.cs
builder.Services.AddOutputCache(options =>
{
    options.EnableMetrics = true;
});

// Access metrics via IMetricsCollector
public class CacheMetricsMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMetricsCollector _metrics;

    public CacheMetricsMiddleware(RequestDelegate next, IMetricsCollector metrics)
    {
        _next = next;
        _metrics = metrics;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Custom metric collection
        await _next(context);
    }
}
🔥Cache Warming
📊 Production Insight
Set alerts for low cache hit ratios. This could indicate misconfiguration or a change in traffic patterns.
🎯 Key Takeaway
Monitor cache metrics to tune performance. Use cache warming to avoid cold start issues.
● Production incidentPOST-MORTEMseverity: high

The Stale Dashboard Fiasco

Symptom
Users reported that the sales dashboard displayed numbers from yesterday, even though new orders were coming in.
Assumption
The developer assumed the cache duration was set to 5 minutes, so data would refresh automatically.
Root cause
The output cache duration was set to 86400 seconds (24 hours) due to a copy-paste error from another project. Additionally, no cache invalidation logic was implemented.
Fix
Reduced cache duration to 60 seconds and added a cache tag that gets invalidated when new orders are placed.
Key lesson
  • Always double-check cache duration values in configuration.
  • Implement cache invalidation for data that changes frequently.
  • Use cache tags to group related cache entries for easy invalidation.
  • Monitor cache hit ratios in production to detect stale data.
  • Consider using HybridCache for more granular control.
Production debug guideSymptom to Action4 entries
Symptom · 01
Page shows outdated data even after updates.
Fix
Check cache duration and invalidation logic. Use cache tags to invalidate specific entries.
Symptom · 02
Cache hit ratio is very low.
Fix
Verify that caching is enabled for the correct endpoints. Check if vary-by rules are too restrictive.
Symptom · 03
Memory usage spikes after enabling caching.
Fix
Review cache size limits and eviction policies. Consider using distributed cache for large data.
Symptom · 04
Distributed cache (Redis) throws timeouts.
Fix
Check network latency and connection pool size. Increase timeout settings or scale Redis.
★ Quick Debug Cheat SheetCommon caching issues and immediate actions.
Stale data served
Immediate action
Reduce cache duration or add invalidation
Commands
dotnet add package Microsoft.AspNetCore.OutputCaching
builder.Services.AddOutputCache(options => options.DefaultExpirationTimeSpan = TimeSpan.FromMinutes(1));
Fix now
Set a shorter expiration or use [OutputCache(Duration = 60)]
Cache not working at all+
Immediate action
Ensure middleware is added
Commands
app.UseOutputCache();
Check that [OutputCache] attribute is applied
Fix now
Add app.UseOutputCache() after app.UseRouting()
High memory usage+
Immediate action
Limit cache size or switch to distributed cache
Commands
options.SizeLimit = 100 * 1024 * 1024; // 100 MB
builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost"; });
Fix now
Set a size limit or use HybridCache with Redis
FeatureOutput CachingHybridCache
ScopeHTTP responsesArbitrary objects
Cache TiersSingle (memory or distributed)Two-tier (L1 memory, L2 distributed)
APIMiddleware + attributesIHybridCache service
InvalidationBy tag via IOutputCacheStoreBy tag via RemoveByTagAsync
Stampede ProtectionOptional via policyBuilt-in
SerializationNot needed (response bytes)Required (JSON, protobuf, etc.)
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
OutputCacheExample.csvar builder = WebApplication.CreateBuilder(args);What is Output Caching?
HybridCacheExample.csusing Microsoft.Extensions.Caching.Hybrid;Introducing HybridCache
PolicyExample.csbuilder.Services.AddOutputCache(options =>Configuring Output Caching Policies
InvalidationExample.cspublic class ProductServiceCache Invalidation Strategies
RedisHybridCache.csusing Microsoft.Extensions.Caching.Hybrid;HybridCache with Redis in Production
SecureCaching.cs[Authorize]Security Considerations for Caching
MonitoringExample.csbuilder.Services.AddOutputCache(options =>Performance Monitoring and Optimization

Key takeaways

1
Output caching stores entire HTTP responses; HybridCache caches objects in a two-tier system.
2
Use cache tags and invalidation to keep data fresh.
3
Never cache sensitive or user-specific data without proper vary-by rules.
4
Monitor cache metrics and adjust expiration and size limits based on traffic patterns.
5
HybridCache provides stampede protection and simplifies distributed caching.

Common mistakes to avoid

3 patterns
×

Caching authenticated responses without varying by user.

×

Setting cache expiration too long for frequently updated data.

×

Not configuring a size limit for in-memory cache.

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain how output caching works in ASP.NET Core and how you would confi...
Q02SENIOR
What are the advantages of HybridCache over using MemoryCache and IDistr...
Q03SENIOR
How would you invalidate all cached responses related to a specific prod...
Q01 of 03SENIOR

Explain how output caching works in ASP.NET Core and how you would configure it for a controller action that varies by query string.

ANSWER
Output caching stores the HTTP response. You configure it by calling AddOutputCache in Program.cs and applying the [OutputCache] attribute. To vary by query string, use a policy with VaryByQuery("param") or set the VaryByQuery property on the attribute.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between output caching and HybridCache?
02
Can I use HybridCache without a distributed cache?
03
How do I invalidate output cache for a specific endpoint?
04
Is output caching suitable for API endpoints that return JSON?
05
What is cache stampede and how does HybridCache prevent it?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
🔥

That's ASP.NET. Mark it forged?

3 min read · try the examples if you haven't

Previous
OpenAPI Documentation in ASP.NET Core 9/10
26 / 35 · ASP.NET
Next
FluentValidation in ASP.NET Core