Caching is one of the most effective ways to improve application performance. Instead of repeatedly querying a database or calling external services, applications can retrieve frequently accessed data from a fast in-memory cache. This reduces response times, lowers database load, and improves scalability.
While the in-memory cache (IMemoryCache) works well for single-instance applications, it isn't suitable for load-balanced or distributed environments because each application instance maintains its own cache. Redis solves this problem by providing a centralized, high-performance distributed cache that can be shared across multiple application instances.
In this article, you'll learn how to implement distributed caching with Redis in ASP.NET Core 11, explore common caching patterns, and understand how to evaluate cache performance using a structured testing methodology.
Note: This article focuses on implementation and benchmark methodology. It does not include fabricated benchmark numbers.
Why Use Distributed Caching?
Without caching, every request may access the database.
Client Request
│
▼
ASP.NET Core API
│
▼
SQL Database
│
▼
Response
With Redis:
Client Request
│
▼
ASP.NET Core API
│
▼
Redis Cache
│ │
Hit Miss
│ │
▼ ▼
Response SQL Database
│
▼
Store in Redis
This significantly reduces database traffic for frequently requested data.
IMemoryCache vs Redis
| Feature | IMemoryCache | Redis |
|---|---|---|
| Shared across servers | No | Yes |
| Distributed | No | Yes |
| High availability | No | Yes |
| Persistent (optional) | No | Yes |
| Suitable for Web Farms | No | Yes |
| Performance | Excellent | Excellent |
For applications running on multiple servers or Kubernetes, Redis is generally the preferred choice.
Create the Project
dotnet new webapi -n RedisCachingDemo
Install the Redis caching package.
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Configure Redis
Register Redis as the distributed cache provider.
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
"localhost:6379";
options.InstanceName = "DemoApp:";
});
Replace the connection string with your Redis server in production.
Inject the Cache
Inject IDistributedCache into your service.
public class ProductService
{
private readonly IDistributedCache _cache;
public ProductService(
IDistributedCache cache)
{
_cache = cache;
}
}
IDistributedCache provides a provider-independent API that works with Redis and other distributed cache implementations.
Store Data in the Cache
Serialize the object before storing it.
var json = JsonSerializer.Serialize(product);
await _cache.SetStringAsync(
$"product:{product.Id}",
json);
The cache key should be unique and descriptive.
Read Data from the Cache
var json = await _cache.GetStringAsync(
"product:1");
if (json is not null)
{
var product =
JsonSerializer.Deserialize<Product>(json);
}
If the value exists, the application avoids a database query.
Configure Expiration
Use expiration policies to prevent stale data.
await _cache.SetStringAsync(
"product:1",
json,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(30)
});
Common expiration options include:
Absolute expiration
Sliding expiration
Combined expiration policies
Choose an expiration strategy based on how frequently the data changes.
Implement the Cache-Aside Pattern
The Cache-Aside pattern is the most common caching strategy.
public async Task<Product?> GetProductAsync(int id)
{
var key = $"product:{id}";
var cached =
await _cache.GetStringAsync(key);
if (cached is not null)
{
return JsonSerializer.Deserialize<Product>(
cached);
}
var product = await _repository.GetByIdAsync(id);
if (product is not null)
{
await _cache.SetStringAsync(
key,
JsonSerializer.Serialize(product));
}
return product;
}
The application checks Redis first. If the data is unavailable, it loads the data from the database and stores it in the cache.
Remove Cached Data
Whenever underlying data changes, invalidate the corresponding cache entry.
Join the conversation! Your thoughts help the community grow.