6

I have an MVC4 GET action method that returns a FileStreamResult. A requirement exists to only use SSL and to not allow caching of the served document so SSL it is and I've also used the OutputCache filter with the following properties:

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None", Location = OutputCacheLocation.None)]

This behaves as expected and produces the following response headers:

Cache-Control: no-cache, no-store
Expires: -1
Pragma: no-cache

All was well until asked to also support IE8 and as many here have also encountered the documents just won't download with both no-cache set and SSL in the mix. The workaround for IE8 and below is to add some registry setting which is not really viable, or to remove the no-cache headers which breaks a fundamental requirement.

I experimented with Fiddler and IE8 and was able to download a document if I just removed the pragma: no-cache header but left the Cache-Control header intact. This didn't appear to leave a copy of the document in my temporary internet files but I might need to test this some more.

With this information in mind I thought it might be a simple task to remove the pragma using a filter on the action but it seems no matter what I do I cannot change whatever the OutputCache is going to set. I've even removed the OutputCache attribute and used:

Response.Cache.SetCacheability(HttpCacheability.NoCache)

Using this method alone ensures I get the same cache settings as before but they are not set at the point of this method call. This merely sets up the cache policy which gets applied at some point in the response pipeline but I just don't know where.

Does anyone know if there is a way of hooking into the response pipeline to alter the cache headers as they are being written?

EDIT I've added a simple custom IHttpModule into the pipeline that looks for and removes any pragma header in the response NameValueCollection and whilst the cache-control is set the pragma is not there. Does this mean that IIS 7.5 is inserting the pragma itself based upon what it sees in the cache-control perhaps? I know for sure I have not set anything beyond defaults for a simple web site.

EDIT Checked the Cache-Control header value within the module and it is set private so the cache headers haven't been applied to the response yet. So it would appear the cache headers get added after modules are executed perhaps?

Yennefer
  • 5,704
  • 7
  • 31
  • 44
Mark
  • 1,059
  • 13
  • 25
  • FWIW, you may wish to read this: http://blogs.msdn.com/b/ieinternals/archive/2009/10/03/internet-explorer-cannot-download-over-https-when-no-cache.aspx. Even with No-Cache headers set, the response will still generally end up in a temporary file on disk; ideally it will be cleaned up shortly, but there's no guarantee of that. – EricLaw Oct 11 '12 at 21:26
  • Thanks EricLaw. I did come across that article in my searches which is what prompted me to use fiddler to alter the response headers before the browser receives the stream. This is when I noticed that by just not including the pragma but leaving the cache-control intact the file would download but I could not see it in Temp Internet Files folder. I still think I need to retest this somewhat to be sure it isn't cached somewhere. – Mark Oct 11 '12 at 22:10
  • I have a [similar problem](http://stackoverflow.com/questions/13119340/ie6-8-unable-to-download-file-from-https-site) and couldn't find a way to make `Response.Cache.SetCacheability` work - it always adds the `Pragma` header and it always puts `no-cache` before `no-store`. That should be controllable though - unless you're dealing with IE3/Netscape Navigator then sending the `Pragma` heading is pretty pointless. – Keith Oct 29 '12 at 13:28
  • I did do some further testing to see across different browsers what the effect of "private, no-store" would be and it would appear that I cannot locate any downloaded file in a browsers cache so this could be the solution. My thinking here is that the private just tells any proxy servers not to store the content and the no-store overrides the private on the client. – Mark Nov 16 '12 at 23:01
  • Mark, did you ever find a way to remove the pragma header while leaving no-cache set? I'm running into the same issue with downloading documents on IE8. – Agreene Jul 03 '13 at 16:01
  • I continue to use the "private, no-store" by using the following:[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None", Location = OutputCacheLocation.Client)] – Mark Jul 04 '13 at 23:04

1 Answers1

1

I was troubleshooting this same issue and ran into the same issue removing the pragma header. When .NET renders a Page object, it outputs the cache headers. The cache handling is controlled by an HttpModule. I've tried several ways to remove the pragma header, but to no avail.

One method I haven't tried yet that looks like it might work, but also looks like a PITA is to implement a filter on the Response output stream via Response.Filter = new MyCustomFilter(...).

Prior to this I tried checking the headers in various locations, but the output cache processing had not been executed yet and pragma header did not exist and so could not be removed. Notably the HttpApplication event PreSendRequestHeaders did not work.

Some other options include implementing your own OutputCache module instead of using the built-in framework version, or somehow overriding the System.Web.HttpCachePolicy class where the pragma header is rendered.

The pragma header is rendered as part of the HttpCacheability.NoCache option:

  if (httpCacheability == HttpCacheability.NoCache || httpCacheability == HttpCacheability.Server)
  {
    if (HttpCachePolicy.s_headerPragmaNoCache == null)
      HttpCachePolicy.s_headerPragmaNoCache = new HttpResponseHeader(4, "no-cache");
    this._headerPragma = HttpCachePolicy.s_headerPragmaNoCache;
    if (this._allowInHistory != 1)
    {
      if (HttpCachePolicy.s_headerExpiresMinus1 == null)
        HttpCachePolicy.s_headerExpiresMinus1 = new HttpResponseHeader(18, "-1");
      this._headerExpires = HttpCachePolicy.s_headerExpiresMinus1;
    }
  }

The only pragmatic option I've found is to set the cache-control to private and also set a short expiration for the URL. It doesn't address the root cause on either end, but it does end up with almost the same desired effect.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
ulty4life
  • 2,972
  • 1
  • 25
  • 31