ActionCache adds a caching layer to ASP.NET Core by annotating your endpoints. Cache a response, evict entries by namespace, or refresh cached actions with a single attribute — against an in-process or distributed backend, or several layered together.
Fair question, and worth answering before you install anything.
[OutputCache] |
HybridCache |
ActionCache | |
|---|---|---|---|
| Ships with ASP.NET Core | Yes | Yes (.NET 9+) | No — a package |
| Caches | Full HTTP response | Arbitrary values you fetch yourself | Action/endpoint responses |
| Attribute-driven | Yes | No — you call it in code | Yes |
| Stampede protection | No | Yes | Yes |
| L1 + L2 layering | No | Yes (memory + IDistributedCache) |
Yes, and any number of layers |
| Backends | Memory, IDistributedCache |
Memory + IDistributedCache |
Memory, Redis, SQL Server, Cosmos DB |
| Invalidate a group | Tags | Tags | Namespaces, with route parameters (Account:{id}) |
| Vary by caller | VaryByValue (manual) |
Your key, your problem | Automatic for authenticated requests |
| Re-warm entries | No | No | Yes — replays the recorded request |
Use [OutputCache] if it covers you. It is built in, costs no dependency, and needs no
explanation to the next person who reads your code.
Use HybridCache if you are caching values rather than responses, and want stampede
protection and two-tier caching from the framework.
Reach for ActionCache when you want attribute-driven response caching and something the other two don't do: eviction scoped to a route parameter, SQL Server or Cosmos as a backend, warming entries ahead of expiry, or per-user keys you don't have to remember to ask for.
- Attribute-driven — add caching, eviction, or refresh to any endpoint with one attribute. Works with both MVC controllers and Minimal APIs.
- Four backends — in-process Memory, Redis, SQL Server, and Azure Cosmos DB, used individually or layered together.
- Namespaced eviction — group entries under a namespace (with route-parameter
templates like
Account:{id}) and evict a whole namespace in one call. - Cache refresh — replay the request recorded on each entry to warm it ahead of expiry.
- Fail-open by default — a backend outage degrades to a cache miss and logs a warning so requests still succeed; opt into fail-closed to propagate errors instead.
ActionCache ships as one package per backend, so you only take the dependencies you use:
dotnet add package ActionCache # core + in-memory caching
dotnet add package ActionCache.Redis # add for Redis
dotnet add package ActionCache.SqlServer # add for SQL Server
dotnet add package ActionCache.AzureCosmos # add for Azure Cosmos DB| Package | Depends on |
|---|---|
ActionCache |
ActionCache.Abstractions only — no Redis, SqlClient, Cosmos SDK or Newtonsoft |
ActionCache.Redis |
StackExchange.Redis |
ActionCache.SqlServer |
Microsoft.Data.SqlClient |
ActionCache.AzureCosmos |
Microsoft.Azure.Cosmos, Newtonsoft.Json |
ActionCache.Abstractions |
nothing — reference it to write a custom backend |
Configuration is unchanged: options.UseRedisCache(...) and friends still read the same,
they just live in their own package now.
Targets .NET 8 and .NET 10.
Register a cache backend:
using ActionCache.Common.Extensions;
builder.Services.AddActionCache(options =>
{
options.UseMemoryCache(memory => { });
// or UseRedisCache(...), UseSqlServerCache(...), UseAzureCosmosCache(...)
});Then annotate your endpoints — cache a read, evict on write:
using ActionCache.Attributes;
[HttpGet("forecasts")]
[ActionCache(Namespace = "Forecasts")]
public IActionResult Get() => Ok(_forecasts);
[HttpPost("forecasts")]
[ActionCacheEviction(Namespace = "Forecasts")]
public IActionResult Create(Forecast forecast) => Ok(_repository.Add(forecast));See the documentation site for expiration, route-templated namespaces, layered backends, refresh, vary-by, resilience and observability.