I have an API REST .NET 7 and the attribute [OutputCache] is not caching even in a endpoint without authorization
I added this:
services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache()); //removing this line doesn't work either
});
And then in Configure():
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOutputCache(); //putting it higher doesn't work either
My endpoint looks like this:
[AllowAnonymous]
[OutputCache(Duration = 99999)]
[HttpGet]
public async Task<IActionResult> GetAsync([FromQuery] Dto dto) => ReturnDataFromDataBase();
But it doesn't work.
I followed the documentation https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output?view=aspnetcore-7.0
Edit: I add more information, this project was an .net 5 and recently updated to .net 7 ([OutputCache] was introduced in .net 7, that is what I understood). It doesn't work because every time I make a request (with Postman) to this endpoint it enters the ReturnDataFromDataBase method (I put a breakpoint). I can't share my project because it's not mine, but this is the Configure method of startup (feel free to correct me):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
//app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
app.UseCors();
app.UseAuthentication();
app.UseExceptionHandler("/Error");
app.UseIpRateLimiting();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
var pathBase = Configuration["APPL_PATH"];
if (pathBase == "/")
pathBase = "";
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
ServiceProviderResolver.ServiceProvider = app.ApplicationServices;
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseOutputCache();
}
Edit 2: If I move app.UseOutputCache() to the first place in Configure() it works, but the documentation says that it must be placed after UseCors() and UseRouting(), in that case it doesn't work.
Edit 3 (Solution for unauthenticated endpoints): The problem was app.UseMvc(), for some reason all controllers were inheriting from Controller(mvc) and not from ControllerBase, I changed it and then I could remove app.UseMvc() that made it works. I also changed the order like this:
Public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
app.UseIpRateLimiting();
var pathBase = Configuration["APPL_PATH"];
if (pathBase == "/")
pathBase = "";
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
ServiceProviderResolver.ServiceProvider = app.ApplicationServices;
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseOutputCache();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The next problem is that it doesn't work in endpoints that require authentication (jwt token).
The default OutputCache policy don't cache any method with authorize end point. If you want to cache authorized api, you should customered policy to indicate what you want to cache.
the example of output cache policy
public class OutputCacheWithAuthPolicy : IOutputCachePolicy
{
public static readonly OutputCacheWithAuthPolicy Instance = new();
private OutputCacheWithAuthPolicy() { }
ValueTask IOutputCachePolicy.CacheRequestAsync(OutputCacheContext context, CancellationToken cancellationToken)
{
var attemptOutputCaching = AttemptOutputCaching(context);
context.EnableOutputCaching = true;
context.AllowCacheLookup = attemptOutputCaching;
context.AllowCacheStorage = attemptOutputCaching;
context.AllowLocking = true;
// Vary by any query by default
context.CacheVaryByRules.QueryKeys = "*";
return ValueTask.CompletedTask;
}
private static bool AttemptOutputCaching(OutputCacheContext context)
{
// Check if the current request fulfills the requirements to be cached
var request = context.HttpContext.Request;
// Verify the method, we only cache get and head verb
if (!HttpMethods.IsGet(request.Method) && !HttpMethods.IsHead(request.Method))
{
return false;
}
// we comment out below code to cache authorization response.
// Verify existence of authorization headers
//if (!StringValues.IsNullOrEmpty(request.Headers.Authorization) || request.HttpContext.User?.Identity?.IsAuthenticated == true)
//{
// return false;
//}
return true;
}
public ValueTask ServeFromCacheAsync(OutputCacheContext context, CancellationToken cancellation) => ValueTask.CompletedTask;
public ValueTask ServeResponseAsync(OutputCacheContext context, CancellationToken cancellation) => ValueTask.CompletedTask;
}
and then register your policy:
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Cache());
options.AddPolicy("OutputCacheWithAuthPolicy", OutputCacheWithAuthPolicy.Instance);
});
then when you cached output , you should mark it with this policy:
[Authorize] // the end point is authorized
[OutputCache(Duration = 600, PolicyName = "OutputCacheWithAuthPolicy")] // incicate policy name
[HttpGet("GetWeatherForecastWithAuth/{id}/{second}")]
public IEnumerable<WeatherForecast> GetWithAuth([FromRoute] string id, [FromRoute] string second)