HybridCache in ASP.NET Core - New Caching Library
Caching is essential for building fast, scalable applications. ASP.NET Core has traditionally offered two caching options: in-memory caching and distributed caching. Each has its trade-offs. In-memory caching using IMemoryCache
is fast but limited to a single server. Distributed caching with IDistributedCache
works across multiple servers using a backplane.
.NET 9 introduces HybridCache
, a new library that combines the best of both approaches. It prevents common caching problems like cache stampede. It also adds useful features like tag-based invalidation and better performance monitoring.
In this week's issue, I'll show you how to use HybridCache
in your applications.
What is HybridCache?
The traditional caching options in ASP.NET Core have limitations. In-memory caching is fast but limited to one server. Distributed caching works across servers but is slower.
HybridCache combines both approaches and adds important features:
Two-level caching (L1/L2)
L1: Fast in-memory cache
L2: Distributed cache (Redis, SQL Server, etc.)
Protection against cache stampede (when many requests hit an empty cache at once)
Tag-based cache invalidation
Configurable serialization
Metrics and monitoring
The L1 cache runs in your application's memory. The L2 cache can be Redis, SQL Server, or any other distributed cache. You can use HybridCache with just the L1 cache if you don't need distributed caching.
Installing HybridCache
Install the Microsoft.Extensions.Caching.Hybrid
NuGet package:
Install-Package Microsoft.Extensions.Caching.Hybrid
Add HybridCache
to your services:
builder.Services.AddHybridCache(options =>
{
// Maximum size of cached items
options.MaximumPayloadBytes = 1024 * 1024 * 10; // 10MB
options.MaximumKeyLength = 512;
// Default timeouts
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(30),
LocalCacheExpiration = TimeSpan.FromMinutes(30)
};
});
For custom types, you can add your own serializer:
builder.Services.AddHybridCache()
.AddSerializer<CustomType, CustomSerializer>();
Using HybridCache
HybridCache
provides several methods to work with cached data. The most important ones are GetOrCreateAsync
, SetAsync
, and various remove methods. Let's see how to use each one in real-world scenarios.
Getting or Creating Cache Entries
The GetOrCreateAsync
method is your main tool for working with cached data. It handles both cache hits and misses automatically. If the data isn't in the cache, it calls your factory method to get the data, caches it, and returns it.
Here's an endpoint that gets product details:
app.MapGet("/products/{id}", async (
int id,
HybridCache cache,
ProductDbContext db,
CancellationToken ct) =>
{
var product = await cache.GetOrCreateAsync(
$"product-{id}",
async token =>
{
return await db.Products
.Include(p => p.Category)
.FirstOrDefaultAsync(p => p.Id == id, token);
},
cancellationToken: ct
);
return product is null ? Results.NotFound() : Results.Ok(product);
});
In this example:
The cache key is unique per product
If the product is in the cache, it's returned immediately
If not, the factory method runs to get the data
Other concurrent requests for the same product wait for the first one to finish
Setting Cache Entries Directly
Sometimes you need to update the cache directly, like after modifying data. The SetAsync
method handles this:
app.MapPut("/products/{id}", async (int id, Product product, HybridCache cache) =>
{
// First update the database
await UpdateProductInDatabase(product);
// Then update the cache with custom expiration
var options = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromHours(1),
LocalCacheExpiration = TimeSpan.FromMinutes(30)
};
await cache.SetAsync(
$"product-{id}",
product,
options
);
return Results.NoContent();
});
Key points about SetAsync
:
It updates both L1 and L2 cache
You can specify different timeouts for L1 and L2
It overwrites any existing value for the same key
Using Cache Tags
Tags are powerful for managing groups of related cache entries. You can invalidate multiple entries at once using tags:
app.MapGet("/categories/{id}/products", async (
int id,
HybridCache cache,
ProductDbContext db,
CancellationToken ct) =>
{
var tags = [$"category-{id}", "products"];
var products = await cache.GetOrCreateAsync(
$"products-by-category-{id}",
async token =>
{
return await db.Products
.Where(p => p.CategoryId == id)
.Include(p => p.Category)
.ToListAsync(token);
},
tags: tags,
cancellationToken: ct
);
return Results.Ok(products);
});
// Endpoint to invalidate all products in a category
app.MapPost("/categories/{id}/invalidate", async (
int id,
HybridCache cache,
CancellationToken ct) =>
{
await cache.RemoveByTagAsync($"category-{id}", ct);
return Results.NoContent();
});
Tags are useful for:
Invalidating all products in a category
Clearing all cached data for a specific user
Refreshing all related data when something changes
Removing Single Entries
For direct cache invalidation of specific items, use RemoveAsync
:
app.MapDelete("/products/{id}", async (int id, HybridCache cache) =>
{
// First delete from database
await DeleteProductFromDatabase(id);
// Then remove from cache
await cache.RemoveAsync($"product-{id}");
return Results.NoContent();
});
RemoveAsync
:
Removes the item from both L1 and L2 cache
Works immediately, no delay
Does nothing if the key doesn't exist
Is safe to call multiple times
Remember that HybridCache
handles all the complexity of distributed caching, serialization, and stampede protection for you. You just need to focus on your cache keys and when to invalidate the cache.
Adding Redis as L2 Cache
To use Redis as your distributed cache:
Install the
Microsoft.Extensions.Caching.StackExchangeRedis
NuGet package:Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
Configure Redis and
HybridCache
:// Add Redis builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "your-redis-connection-string"; }); // Add HybridCache - it will automatically use Redis as L2 builder.Services.AddHybridCache();
HybridCache
will automatically detect and use Redis as the L2 cache.
Summary
HybridCache
simplifies caching in .NET applications. It combines fast in-memory caching with distributed caching, prevents common problems like cache stampede, and works well in both single-server and distributed systems.
Start with the default settings and basic usage patterns - the library is designed to be simple to use while solving complex caching problems.
Thanks for reading.
And stay awesome!
Whenever you're ready, there are 4 ways I can help you:
(COMING SOON) REST APIs in ASP.NET Core: You will learn how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API. Join the waitlist!
Pragmatic Clean Architecture: Join 3,150+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
Modular Monolith Architecture: Join 1,050+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
Patreon Community: Join a community of 1,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.
Subscribe to my newsletter
Read articles from Milan Jovanović directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Milan Jovanović
Milan Jovanović
I'm a seasoned software architect and Microsoft MVP for Developer Technologies. I talk about all things .NET and post new YouTube videos every week.