ASP.NET Core Response Caching Middleware - How to stop caching (500) error responses
Asked Answered
Y

1

11

I implemented ASP.NET Core Response Caching in my application to cache some API requests.

I have added the ResponseCaching middleware to the IServicesCollection in ConfigureServices method like this.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
            builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyHeader()
                       .AllowAnyMethod();

            });
        });

        services.AddResponseCaching();
        services.AddControllersWithViews();

        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/dist";
        });
    }

I have used it in Configure method like like this.

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }
        app.UseCors("CorsPolicy");
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        if (!env.IsDevelopment())
        {
            app.UseSpaStaticFiles();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });

        app.UseResponseCaching();

        app.Use(async (context, next) =>
        {
           context.Response.GetTypedHeaders().CacheControl =
              new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
              {
                  Public = true,
                  MaxAge = TimeSpan.FromSeconds(apiConfiguration.CacheDuration),
              };

        context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = new string[] { "Accept-Encoding" };
     
            await next();
        });

        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "ClientApp";
            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });
    }

I have also decorated my Web API action methods with ResponseCache attribute like below

    [HttpGet]
    [ResponseCache(Duration = 60)]
    public IActionResult Get(){
      //code
    }

Finally in my angular application I have setted Cache-control headers too.

  fetchAPIData(){
    return this.http.get('https://localhost:44325/data',  {
      headers: new HttpHeaders({
        'Cache-Control': 'public',
        "Content-Type": "application/json"
      })
    });
  }

I want to cache server responses only with 200 (OK) status code. According to the Microsoft documentation that should work

Response Caching Middleware only caches server responses that result in a 200 (OK) status code. Any other responses, including error pages, are ignored by the middleware.

But when it runs it's caching 500 error responses too. look at below image enter image description here

  1. What can be the reason for this?
  2. What should I do for fixing this. I just want to cache 200(OK) requests.

Note

  • I tried with Chrome, Edge and IE browsers too.
  • I used ASP.NET Core 3.1
  • My Frontend application is Angular 7
Ytterbium answered 24/5, 2021 at 21:24 Comment(8)
Try adding [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] for Error action method in the respective controller.Predecessor
Hi @Thilina Sandunsiri, I could not reproduce your issue. What about move app.UseResponseCaching(); before app.UseEndpoints?Easternmost
@Predecessor This is a Web API action method for a GET request. So same method returns the Success/Error Responses If I set "Location = ResponseCacheLocation.None", then Cache-control response header set to "no-cache", and If I set "NoStore = true" then Cache-control response header set to "no-store". So in both scenarios it will not cache any response (even it was success)Ytterbium
@Predecessor And if I set "Duration = 0" then max age of Cache-control response header becomes 0. It means cache expires as soon as response get. So each request, browser will requests a new version of the API response. So every time new API call will be send to the server. I actually want to cache successful(200) API calls and ignore Error responses like 500,400,400Ytterbium
@ThilinaSandunsiri. It should be set on the Error action method.(There must be a separate action for that) According to your code, all the errors will be redirected to Error action method in the respective controller. app.UseExceptionHandler("/Error"); So the no-caching should be set on the Error action method.Predecessor
@Predecessor That is applicable only for MVC isn't it? Since it returns a separate view for Errors. For Web API it is a single API endpoint neh? We call those endpoints from the front end application.(Ex : GET 'api/students' at the time client calls that endpoint it doesn't know that will return either success/error response.It just returns the data/error info with the status code With response caching after got the response for a given API call, client browser or proxy between the client and server which running on client's end, caching data based on the response header instructions. Isn't it?Ytterbium
@Easternmost Thanks. that was fixed my issue partially. Now it's working perfectly for IE and Firefox browsers. But for Chromium based browsers like Chrome and Edge it didn't work.Ytterbium
@ThilinaSandunsiri did you figure this out? I've been having the same problem.Phallus
K
7

I had exactly the problem described in the question. I'm using the ResponseCache attribute on a web api endpoint which can return either success or failure. I was getting cache headers for both success and failure of the call.

I solved this by adding custom middleware to remove the cache headers in the failure case:

app.Use(async (context, next) =>
{
    // Do not cache error conditions
    context.Response.OnStarting(() =>
    {
        if (context.Response.StatusCode >= 400)
        {
            context.Response.Headers.Remove("Cache-Control");
        }
        return Task.FromResult(0);
    });
    await next();
});
Kislev answered 10/12, 2022 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.