Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cache-control: no-store, must-revalidate not sent to client browser in IIS7 + ASP.NET MVC

I am trying to make sure that a certain page is never cached, and never shown when the user clicks the back button. This very highly rated answer (currently 1068 upvotes) says to use:

Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
Response.AppendHeader("Pragma", "no-cache");
Response.AppendHeader("Expires", "0");

However in IIS7 / ASP.NET MVC, when I send those headers then the client sees these response headers instead:

Cache-control: private, s-maxage=0 // that's not what I set them to
Pragma: no-cache
Expires: 0

What happened to the cache-control header? Does something native to IIS7 or ASP.NET overwrite it? I have checked my solution and I have no code that overwrites this header.

When I add Response.Headers.Remove("Cache-Control"); first, it makes no difference:

Response.Headers.Remove("Cache-Control");
Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
Response.AppendHeader("Pragma", "no-cache");
Response.AppendHeader("Expires", "0");

When I add an [OutputCache] attribute:

[OutputCache(Location = OutputCacheLocation.None)]
public ActionResult DoSomething()
{
   Response.Headers.Remove("Cache-Control");
   Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
   Response.AppendHeader("Pragma", "no-cache");
   Response.AppendHeader("Expires", "0");

   var model = DoSomething();
   return View(model);
}

Then the client response headers change to:

Cache-control: no-cache
Pragma: no-cache
Expires: 0

Which is closer, but still not the headers that I want to send. Where are these headers getting overridden and how can I stop it?

EDIT: I have checked and the incorrect headers are being sent to Chrome, FF, IE and Safari, so it looks to be a server problem not a browser related problem.

like image 758
JK. Avatar asked Mar 16 '14 22:03

JK.


2 Answers

Through trial and error, I have found that one way to set the headers correctly for IIS7 in ASP.NET MVC is:

Response.Cache.SetCacheability(HttpCacheability.NoCache); Response.Cache.AppendCacheExtension("no-store, must-revalidate"); Response.AppendHeader("Pragma", "no-cache"); Response.AppendHeader("Expires", "0"); 

The first line sets Cache-control to no-cache, and the second line adds the other attributes no-store, must-revalidate.

This may not be the only way, but does provide an alternative method if the more straightforward Response.AppendHeader("Cache-control", "no-cache, no-store, must-revalidate"); fails.

Other related IIS7 cache-control questions that may be solved by this are:

  • Something is forcing responses to have cache-control: private in IIS7
  • IIS7: Cache Setting Not Working... why?
  • IIS7 + ASP.NET MVC Client Caching Headers Not Working
  • Set cache-control for aspx pages
  • Cache-control: no-store, must-revalidate not sent to client browser in IIS7 + ASP.NET MVC
like image 122
JK. Avatar answered Sep 25 '22 15:09

JK.


I want to add something to JK's answer:
If you are setting the cache control to a more restrictive value than it already is, it is fine. (i.e: setting no-cache, when it is private)

But, if you want to set to a less restrictive value than it already is (i.e: setting to private, when it is no-cache), the code below will not work:

Response.Cache.SetCacheability(HttpCacheability.Private);

Because, SetCacheablitiy method has this code below and sets the cache flag only if it is more restrictive:

if (s_cacheabilityValues[(int)cacheability] < s_cacheabilityValues[(int)_cacheability]) {
    Dirtied();
   _cacheability = cacheability;
}

To overcome this in .net mvc, you need to get an instance of HttpResponseMessage and assign a CacheControlHeaderValue to its Headers.CacheControl value:

actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue
                                   {
                                       MaxAge = TimeSpan.FromSeconds(3600),
                                       Private = true
                                   };

An instance of the HttpResponseMessage is available in action filters. You can write an action filter to set cache header values like this:

public class ClientSideCacheAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var response = actionExecutedContext.ActionContext.Response;
        response.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue
        {
            MaxAge = TimeSpan.FromSeconds(9999),
            Private = true,
        };
    }
}
like image 34
Veysel Ozdemir Avatar answered Sep 24 '22 15:09

Veysel Ozdemir