ASP.NET Core Response Cache
Where is output cache in ASP.NET Core? Is it gone? No, it’s there but in new and better extensible form and it is called now response caching. This blog post shows how to use response caching on ASP.NET Core and provides tips about some internals of it.
In ASP.NET Core caching is solved as middleware service that comes with Microsoft.AspNetCore.ResponseCaching NuGet package. In MVC caching is driven by ResponseCache attribute. It’s possible to use ResponseCache attribute also without response caching middleware to just set response headers. This way it is possible to tell browser to cache content and also cache or proxy servers on the way can read these headers to find out how to cache page.
By default response caching uses memory based cache but there are extension points that allow to use custom cache storage providers. This blog post doesn’t cover extensibility. It covers just basics and focuses on stateless public sites.
Using respone caching
After adding package reference to Microsoft.AspNetCore.ResponseCaching we can register response caching middleware in Startup class using AddResponseCaching() and UseResponseCaching() extension methods.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddResponseCaching();
services.AddMvc();
// ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ...
app.UseResponseCaching();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "Default",
template: "{controller=Home}/{action=Index}/{id?}",
defaults: ""
);
});
}
For MVC controller action we add ResponseCache attribute. The following example shows primitive controller action that returns just a simple view it has.
[ResponseCache(Duration = 30)]
public IActionResult Article()
{
return View();
}
This is enough to get caching work with minimal effort. When we run our application and request controller action that is cached we get the following result:
NB! For full list of cache settings available check out the following pages from official documentation:
- Response Caching (ASP.NET Core documentation)
- Response Caching Middleware (ASP.NET Core documentation)
Caching profiles can be defined in application Startup class and they are supported by ResponseCache attribute.
Conditions for caching
Response caching middleware page by Microsoft lists conditions that request must meet for caching:
- The request must result in a 200 (OK) response from the server.
- The request method must be GET or HEAD.
- Terminal middleware, such as Static File Middleware, must not process the response prior to the Response Caching Middleware.
- The Authorization header must not be present.
- Cache-Control header parameters must be valid, and the response must be marked public and not marked private.
- The Pragma: no-cache header/value must not be present if the Cache-Control header is not present, as the Cache-Control header overrides the Pragma header when present.
- The Set-Cookie header must not be present.
- Vary header parameters must be valid and not equal to *.
- The Content-Length header value (if set) must match the size of the response body.
- The HttpSendFileFeature is not used.
- The response must not be stale as specified by the Expires header and the max-age and s-maxage cache directives.
- Response buffering is successful, and the total length of the response is smaller than the configured limit.
- The response must be cacheable according to the RFC 7234 specifications. For example, the no-store directive must not exist in request or response header fields. See Section 3: Storing Responses in Caches of the RFC document for details.
Problems with cookies
Currently it is not possible to use cookies with response caching. As soon as cookies header is present the caching of request is considered as impossible. Services like Application Insights and Antiforgery system by ASP.NET Core doesn’t work with response caching because they use cookies or set cache headers.
For Application Insights and services like this it is technically possible to create custom middleware based on default one if cookies are still needed for client-side libraries and there are no dependencies with application or session state.
Troubleshooting caching
To some point it is possible to monitor response caching. The library uses ASP.NET Core logging framework to log some activities by response caching middleware. Screen fragment below shows some response caching middleware logs.
Sadly it does not report reasons why response was not added to cache. It would greatly help us when testing our response caching in different environments.
If logs are not enough then we can always include projects from response caching GitHub repository to our solution and run these on debugger to see what’s going on.
NB! When debugging response caching with browser make sure that developers tools that come with browser doesn’t set any cookies or cache headers that may affect response caching.
Getting rid of ARR affiinity cookie on Azure Web Sites
If your site doesn’t need Application Request Routing (ARR) you can disable ARR affinity cookie by adding the following block of settings to web.config of your web application.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="ARR-Disable-Session-Affinity" value="true"/>
</customHeaders>
</httpProtocol>
</system.webServer>
After this modification there is no more ARR cookie in your site available anymore.
Wrapping up
Although I got response caching work I didn’t got it work with Application Insights and other services that work in browser and that use cookies. Still I was able to create simple prototype of public facing application that is cookieless and uses ASP.NET Core response caching. In this example I used memory based cache as I wrote proof-of-concept code. It was easy to set ResponseCache attributes to controller actions I wanted to cache. Troubleshooting was a little bit tricky because response cache logs are limited and browser tools may get to the way with their handling of cache headers. As a conclusion I can say that response caching works okay but needs some improvements to be easier to use.
You can remove the ARR cookie via the portal as well. It’s in “Application settings” and called “ARR Affinity”
Thanks for comment, Brent! You suggested the polite and appropriate way how to disable ARR. I provided the solution that gives the same effect in all Azure based environments so there’s no chance that this header is back when somebody just forgot to turn ARR off.
Also, I don’t think it works on localhost… at least, for me!
Ricardo, I made it first work on localhost. Make sure your browser tools doesn’t screw up things. It goes specially about Chrome. If you close browser tools then Chrome doesn’t send out headers that avoid response caching. You can see from logs if caching worked or not.
If the link is loaded from the browser (e.g., not explicitly on the browser with F5 or entering the URL), it does work.
Got the answer from here: http://stackoverflow.com/questions/11245767/is-chrome-ignoring-control-cache-max-age.
I also found out that there are some issues with Chrome but everything works fine with Edge and Firefox.
>The Authorization header must not be present.
This is a major no-go for me. *Sigh
I think many things in this list should be configurable. I also found that max-age=0 means that request is not served from cache.
I’ve created a repo for custom cache implementations. It currently only has a disk-based cache. It also allows ignoring the browser’s no-cache/no-store headers & has extensions which make ResponseCaching easily overridable for others who wish to code other custom implementations.
https://github.com/speige/AspNetCore.ResponseCaching.Extensions
https://www.nuget.org/packages/AspNetCore.ResponseCaching.Extensions
And what about ASP.NET Core 2.0 PURE RAZOR PAGES. This pages no use controller? I configured Startup.cs like:
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCaching(); //2018-03-01
services.AddMvc();
}
And:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Other code at this point ….
app.UseResponseCaching(); //2018-03-01
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: “default”,
template: “{controller}/{action=Index}/{id?}”);
});
}
And in the Index.cshtml.cs:
//No surte efecto
[ResponseCache(Duration = 60)]
public class IndexModel : PageModel
{
//No surte efecto
[ResponseCache(Duration = 30, Location = ResponseCacheLocation.Any)]
public void OnGet()
{
}
}
But with Firefox F12, the chache-control has the value Cache-Control: max-age=0
I don´t found any information about caching on ASP.NET Core 2.0 Razor Pages.
How can i do?
Thanks
Thanks.