We moved from old-fashioned Controllers to minimal API in our c# projects. So far so good for most view points we prefer minimal API. However one topic we can't solve: Response Caching.
In Controller based development one can add the attribute to add the response cache (important, we are talking about client side caching, not server side)
[ResponseCache(VaryByHeader = "User-Agent", Duration = 30)]
For minimal API I found this here: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-8.0
But I can't understand, how I can chose which endpoints to have response cache and which one don't. I appears, all endpoints will use the same cache which is something I absolutely don't want.
My copilot proposes this here
app.MapGet("/data", () =>
{
var response = Results.Json(new { message = "Cached Data" });
response.Headers["Cache-Control"] = "public,max-age=60";
return response;
});
I can live with that, but can we combine that to something like?
app.MapGet("/data", () =>
{
var response = new { message = "Cached Data" };
return response;
}).UseResponseCache(60);
I would define such extension method for RouteHandlerBuilder
:
public static RouteHandlerBuilder WithResponseCache(
this RouteHandlerBuilder builder,
int durationSeconds,
string varyByHeader)
{
builder.Add(endpointBuilder =>
{
endpointBuilder.Metadata.Add(new ResponseCacheAttribute()
{
Duration = durationSeconds,
VaryByHeader = varyByHeader,
});
});
return builder;
}
This unforutnately won't work out of the box with Minimal APIs, for that we need to register custom middleware (as described in the documentation) to handle the metadata:
app.Use(async (context, next) =>
{
var endpointMetadata = context.GetEndpoint()?.Metadata;
if(endpointMetadata is not null)
{
var responseCache = endpointMetadata.FirstOrDefault(x => x is ResponseCacheAttribute);
if(responseCache is ResponseCacheAttribute attribute)
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(attribute.Duration)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { attribute.VaryByHeader };
}
}
await next();
});
Of course, you need to also call AddResponseCaching
and UseResponseCaching
to add needed services.
And I just used ResponseCacheAttribute
class, but you could of course use also custom class for that.