C# Minimal API: Output Caching
Source: Dev.to
Stores the generated response on the server and serves it directly without re‑executing the endpoint.
Microsoft Docs
Output caching is a server‑side caching mechanism implemented as middleware. It stores the entire HTTP response and serves it for subsequent requests without executing the endpoint again.
- By default it uses in‑memory storage, but you can back it with distributed stores such as Redis.
- It’s most suitable for expensive server‑side operations that return the same response within a defined time window.
How it works
When a request arrives:
- Cache hit – a cached response exists → the middleware short‑circuits the pipeline and returns the cached response.
- Cache miss – no cached response → the request is processed normally, and the response is cached for future requests.
Adding Output Caching
- Register the service with
AddOutputCache(). - Define one or more policies.
- Apply the policies to endpoints.
- Add the middleware with
UseOutputCache().
var builder = WebApplication.CreateBuilder(args);
// 1️⃣ Register output‑cache services
builder.Services.AddOutputCache(options =>
{
// Default policy (10 s)
options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(10)));
// Named policy (20 s)
options.AddPolicy("OutputCache20Seconds", policy => policy.Expire(TimeSpan.FromSeconds(20)));
});
var app = builder.Build();
// 2️⃣ Apply policies to Minimal API endpoints
app.MapGet("/default-cache-policy", () => new[] { "someresponse2" })
.CacheOutput(); // uses default policy
app.MapGet("/custom-cache-policy", () => new[] { "someresponse" })
.CacheOutput("OutputCache20Seconds"); // uses named policy
// 3️⃣ Add the middleware (must be after UseCors() if you use CORS)
app.UseOutputCache();
app.Run();
Note: In apps that use CORS middleware,
UseOutputCache()must be called afterUseCors().
Output Caching with an Authorization Header
By default, output caching does not cache responses when an Authorization header is present – a safety measure to avoid serving authenticated content to the wrong user.
To override this behavior, create a custom output‑cache policy:
internal static class CustomOutputCachingPolicyFactory
{
internal static CustomOutputCachingPolicy Create(TimeSpan expiration)
=> new(expiration);
}
internal sealed class CustomOutputCachingPolicy : IOutputCachePolicy
{
private readonly TimeSpan _expiration;
internal CustomOutputCachingPolicy(TimeSpan expiration) => _expiration = expiration;
public ValueTask CacheRequestAsync(OutputCacheContext context, CancellationToken cancellation)
{
var canCache = HttpMethods.IsGet(context.HttpContext.Request.Method) ||
HttpMethods.IsHead(context.HttpContext.Request.Method);
context.EnableOutputCaching = canCache;
context.AllowCacheLookup = canCache;
context.AllowCacheStorage = canCache;
context.AllowLocking = true;
context.ResponseExpirationTimeSpan = _expiration;
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation)
=> ValueTask.CompletedTask;
public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation)
{
var response = context.HttpContext.Response;
// Do not cache if Set‑Cookie header is present
if (!StringValues.IsNullOrEmpty(response.Headers.SetCookie))
{
context.AllowCacheStorage = false;
return ValueTask.CompletedTask;
}
// Only cache successful (200) or permanent redirect (301) responses
if (response.StatusCode is not (StatusCodes.Status200OK or StatusCodes.Status301MovedPermanently))
{
context.AllowCacheStorage = false;
}
return ValueTask.CompletedTask;
}
}
Convenience Extensions for Minimal APIs
These extensions simplify registration of custom policies:
public static class OutputCachingExtensions
{
public static void AddCustomOutputCachingPolicy(
this OutputCacheOptions options,
params (string Name, TimeSpan Expiration)[] policies)
{
foreach (var (name, expiration) in policies)
{
options.AddPolicy(name,
CustomOutputCachingPolicyFactory.Create(expiration));
}
}
public static IServiceCollection AddOutputCacheWithCustomPolicy(
this IServiceCollection services,
params (string Name, TimeSpan Expiration)[] policies) =>
services.AddOutputCache(opts => opts.AddCustomOutputCachingPolicy(policies));
public static IServiceCollection AddOutputCacheWithCustomPolicy(
this IServiceCollection services,
Action<OutputCacheOptions> configure,
params (string Name, TimeSpan Expiration)[] policies) =>
services.AddOutputCache(opts =>
{
configure(opts);
opts.AddCustomOutputCachingPolicy(policies);
});
}
Additional helper methods:
options.AddCustomOutputCachingPolicy(policies);
configureOptions.Invoke(options);
});
public static RouteHandlerBuilder CustomCacheOutput(this RouteHandlerBuilder routeHandlerBuilder, string name)
=> routeHandlerBuilder.CacheOutput(name);
Important: Do not use output caching for authenticated or user‑specific endpoints unless the response is identical for all users.
When to use
Use output caching when responses are costly to generate and do not change frequently:
- Expensive server‑side computations
- Frequently requested responses where execution cost is high (e.g., report generation, user profile rendering)