| name | extensions-caching |
| description | USE FOR: In-memory caching with IMemoryCache, distributed caching with IDistributedCache, output caching in ASP.NET Core, hybrid cache strategies, and cache invalidation patterns. DO NOT USE FOR: Persistent data storage, session state management without a backing store, or scenarios requiring strong consistency guarantees across nodes.
|
| license | MIT |
| metadata | {"displayName":"Caching","author":"Tyler-R-Kendrick","version":"1.0.0"} |
| compatibility | ["claude","copilot","cursor"] |
| references | [{"title":"Caching in .NET Documentation","url":"https://learn.microsoft.com/dotnet/core/extensions/caching"},{"title":"Microsoft.Extensions.Caching.Memory NuGet Package","url":"https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory"},{"title":".NET Runtime GitHub Repository","url":"https://github.com/dotnet/runtime"}] |
Microsoft.Extensions.Caching
Overview
Microsoft.Extensions.Caching provides two primary abstractions for caching in .NET applications: IMemoryCache for in-process caching and IDistributedCache for shared caching across multiple application instances. Both are registered through dependency injection and integrate with the standard hosting model. Starting with .NET 9, HybridCache combines both layers into a unified API that handles stampede protection and serialization automatically.
The Microsoft.Extensions.Caching.Memory package provides the in-memory implementation, while Microsoft.Extensions.Caching.StackExchangeRedis, Microsoft.Extensions.Caching.SqlServer, and Microsoft.Extensions.Caching.Cosmos provide distributed cache backends.
In-Memory Caching with IMemoryCache
Register IMemoryCache and use GetOrCreateAsync to implement the cache-aside pattern.
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache(options =>
{
options.SizeLimit = 1024;
});
var app = builder.Build();
using Microsoft.Extensions.Caching.Memory;
public sealed class ProductCatalogCache
{
private readonly IMemoryCache _cache;
private readonly IProductRepository _repository;
public ProductCatalogCache(IMemoryCache cache, IProductRepository repository)
{
_cache = cache;
_repository = repository;
}
public async Task<Product?> GetProductAsync(int productId, CancellationToken ct)
{
string cacheKey = $"product:{productId}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
entry.SetSlidingExpiration(TimeSpan.FromMinutes(2));
entry.SetSize(1);
entry.SetPriority(CacheItemPriority.Normal);
return await _repository.GetByIdAsync(productId, ct);
});
}
public void InvalidateProduct(int productId)
{
_cache.Remove($"product:{productId}");
}
}
Distributed Caching with IDistributedCache
Use IDistributedCache when cached data must be shared across multiple application instances or survive process restarts.
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "myapp:";
});
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
public sealed class SessionDataCache
{
private readonly IDistributedCache _cache;
public SessionDataCache(IDistributedCache cache)
{
_cache = cache;
}
public async Task SetAsync<T>(string key, T value, CancellationToken ct)
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
SlidingExpiration = TimeSpan.FromMinutes(15)
};
byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(value);
await _cache.SetAsync(key, bytes, options, ct);
}
public async Task<T?> GetAsync<T>(string key, CancellationToken ct)
{
byte[]? bytes = await _cache.GetAsync(key, ct);
if (bytes is null)
{
return default;
}
return JsonSerializer.Deserialize<T>(bytes);
}
public Task RemoveAsync( key, CancellationToken ct)
{
_cache.RemoveAsync(key, ct);
}
}
HybridCache (.NET 9+)
HybridCache unifies in-memory and distributed caching with built-in stampede protection, meaning only one caller fetches the value while others wait.
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(30),
LocalCacheExpiration = TimeSpan.FromMinutes(5)
};
options.MaximumPayloadBytes = 1024 * 1024;
});
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
using Microsoft.Extensions.Caching.Hybrid;
public sealed class OrderSummaryService
{
private readonly HybridCache _cache;
private readonly IOrderRepository _orders;
public OrderSummaryService(HybridCache cache, IOrderRepository orders)
{
_cache = cache;
_orders = orders;
}
public async Task<OrderSummary> GetSummaryAsync(Guid orderId, CancellationToken ct)
{
return await _cache.GetOrCreateAsync(
$"order-summary:{orderId}",
async cancel => await _orders.GetSummaryAsync(orderId, cancel),
cancellationToken: ct);
}
public async Task InvalidateAsync(Guid orderId, CancellationToken ct)
{
await _cache.RemoveAsync($"order-summary:{orderId}", ct);
}
}
Output Caching in ASP.NET Core
Output caching stores entire HTTP responses, eliminating repeated request processing.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(60)));
options.AddPolicy("ProductList", policy =>
policy.Tag("products").Expire(TimeSpan.FromMinutes(5)));
});
var app = builder.Build();
app.UseOutputCache();
app.MapGet("/api/products", async (IProductRepository repo) =>
{
return await repo.GetAllAsync();
}).CacheOutput("ProductList");
app.MapPost("/api/products", async (
Product product,
IProductRepository repo,
IOutputCacheStore store,
CancellationToken ct) =>
{
await repo.CreateAsync(product, ct);
await store.EvictByTagAsync("products", ct);
return Results.Created($"/api/products/{product.Id}", product);
});
app.Run();
Caching Strategy Comparison
| Strategy | Scope | Persistence | Stampede Protection | Serialization |
|---|
| IMemoryCache | Single process | No | No (manual) | Not needed |
| IDistributedCache | Cross-process | Yes (backend) | No (manual) | Manual (bytes) |
| HybridCache | Both layers | Yes (backend) | Built-in | Automatic |
| Output Caching | HTTP responses | Configurable | N/A | N/A |
Best Practices
- Always set both
AbsoluteExpiration and SlidingExpiration on cache entries to prevent unbounded memory growth and stale data accumulating over time.
- Use
SetSize on every IMemoryCache entry when SizeLimit is configured; entries without a size are rejected when a limit is enforced.
- Prefer
HybridCache.GetOrCreateAsync over manual IMemoryCache plus IDistributedCache layering to get built-in stampede protection without custom locking.
- Namespace cache keys with a prefix (e.g.,
"catalog:product:{id}") to avoid collisions between different features sharing the same cache instance.
- Use tag-based eviction with Output Caching or
HybridCache to invalidate groups of related entries when source data changes, rather than tracking individual keys.
- Register
IDistributedCache with AddStackExchangeRedisCache or another durable backend in production; never rely on the in-memory distributed cache implementation for multi-instance deployments.
- Log cache misses at the
Debug level and cache evictions at Information to diagnose hit-rate problems without flooding logs under normal operation.
- Avoid caching mutable objects with
IMemoryCache because it stores references, not copies; mutations to the returned object silently corrupt the cached value.
- Set
CacheItemPriority.NeverRemove only for entries that are truly critical and small; overusing it defeats the purpose of memory-pressure eviction.
- Measure cache hit rates and latency in production using metrics from
System.Diagnostics.Metrics or Application Insights to validate that caching actually improves throughput.