ASP.NET MVC和IEcaching – 操纵响应头无效

背景

我试图帮助一个同事debugging过去6个月没有问题的问题。 在最近部署ASP.NET MVC 2应用程序FileResult ,强制用户打开或保存PDF文件的FileResult响应在客户端计算机上存在足够长的时间, FileResult PDF阅读器无法打开它们。

IE的早期版本(特别是6)是唯一受影响的浏览器。 Firefox和Chrome以及更新版本的IE(> 8)都按预期行事。 考虑到这一点,下一节将定义重新创build问题所需的操作。

行为

  1. 用户单击一个指向一个操作方法的链接(带有href属性的普通超链接)。
  2. 操作方法生成一个表示为字节stream的PDF。 该方法总是重新创buildPDF。
  3. 在action方法中,头文件被设置为指示浏览器如何caching响应。 他们是:

     response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0"); response.AddHeader("Pragma", "no-cache"); response.AddHeader("Expires", "0"); 

    对于那些不熟悉标题的人来说:

    一个。 caching控制:公共

    指示响应可以被任何cachingcaching,即使它通常只在非共享caching中是不可caching或可caching的。

    caching控制:必须重新validation

    当高速caching接收到的响应中存在must-revalidate指令时,该高速caching不应该在该条目失效之后才使用该条目来响应后续请求,而不必先用源服务器对其进行重新validation

    C。 caching控制:预先检查 (与IE5一起引入)

    定义以秒为单位的时间间隔,之后必须检查实体的新鲜度。 在用户显示资源之后可能会发生检查,但确保在下一次往返中,caching副本将是最新的。

    d。 caching控制:后检查 (与IE5引入)

    定义以秒为单位的时间间隔,在此之前必须检查实体的新鲜度,然后向用户显示资源。

    Pragma:no-cache (确保向后兼容HTTP / 1.0)

    当请求消息中存在no-cache指令时,应用程序应该将请求转发给原始服务器,即使它具有正被请求的caching副本

    F。 过期

    Expires实体标题字段给出了响应被认为是陈旧的date/时间。

  4. 我们从操作中返回文件

     return File(file, "mime/type", fileName); 
  5. 用户将看到一个打开/保存对话框

  6. 点击“保存”按预期工作,但点击“打开”启动PDF阅读器,但当读者试图打开文件时,存储的临时文件IE已经被删除,所以它抱怨文件丢失了是)。

这里有六个其他的应用程序使用相同的头文件强制在用户的Excel,CSV,PDF,Word,和大量的其他内容,从来没有一个问题。

问题

  • 标题是否正确,我们正在尝试做什么? 我们希望文件临时存在(获取caching),但是总是被新版本取代,即使请求可能是相同的)。

响应头文件在返回FileResult之前在action方法中设置。 我已经要求我的同事尝试创build一个从FileResultinheritance的新类,而是重写ExecuteResult方法,以便它修改头,然后执行base.ExecuteResult() – 而不是状态。

我有一个预感“0”的“Expires”头是罪魁祸首。 根据这个W3C文章 ,将其设置为“0”意味着“已经过期”。 我希望它过期,我只是不希望IE应用程序处理之前将其从文件系统中删除它有机会打开它。

一如既往,谢谢!

编辑:解决scheme

在进一步的testing中(使用Fiddler来检查头文件),我们看到我们认为被设置的响应头并不是被浏览器解释的。 由于我自己并不熟悉这些代码,所以我并不知道一个潜在的问题:头部在动作方法之外被踩踏了。

不过,我打算把这个问题留下来。 还有一个突出的地方就是: Expires头文件的值为0-1之间似乎有一些差异。 如果有人可以通过devise来声明差异,关于IE ,我还是想听听。 至于一个解决scheme,上面的头文件在所有浏览器中的Expires值设置为-1按照预期工作。

更新1

该post如何在所有浏览器上控制网页caching? 详细介绍了在设置Expires = 0的帮助下,可以在所有浏览器中防止caching。我仍然没有在这个0 vs -1参数上出售…

我想你应该使用

 HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0)); 

要么

 HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0"); 

设置max-age=0这意味着没有什么更多的caching重新validation(见这里 )。 如果在头文件中额外设置了ETag ,并使用自定义的数据散列校验和,则先前请求中的ETag将被发送到服务器。 服务器既可以返回数据,也可以在数据与以前完全一样的情况下,返回空主体和HttpStatusCode.NotModified作为状态码。 在这种情况下,Web浏览器将从本地浏览器caching中获取数据。

我推荐你使用Cache-Control: private来强制两个重要的事情:1)closures代理上的数据caching,有时候会有非常积极的caching设置2)它允许caching数据,但不允许共享与另一个用户的caching。 它可以解决隐私问题,因为您返回给一个用户的数据可能不被其他用户读取。 顺便说一下,默认情况下,HTTP头中的代码HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0))设置Cache-Control: private, max-age=0 。 如果你想使用Cache-Control: public你可以使用SetCacheability (HttpCacheability.Public); 覆盖行为或使用Headers.Set而不是Cache.SetMaxAge

如果您有兴趣研究HTTP协议的更多caching选项,我会build议您阅读caching教程 。

更新 :我决定写更多的信息来清除我的位置。 即使像Mozilla 2.7,Netscape 2.0和Internet Explorer 3.0这样的老的浏览器也支持Wikipedia 的信息 ,它支持1996年3月的RFC 2068中描述的HTTP / 1.1的预标准。所以我想(但不testing)旧的网页浏览器支持max-age=0 HTTP头。 Netscape 2.06和Internet Explorer 4.0以任何方式明确支持HTTP 1.1。

所以你应该首先问你:使用哪个HTML标准? 你仍然使用HTML 2.0而不是1997年1月发布的更晚期的HTML 3.2吗? 我想你至less使用了1997年12月发布的HTML 4.0。所以如果你至less在HTML 4.0中构build你的应用程序,你的网站可以面向支持HTTP 1.1的Web客户端,而忽略(不支持)Web客户端不支持HTTP 1.1。

现在关于其他“caching控制”标题为“私人,最大年龄= 0”。 包括标题在我看来是纯粹的偏执狂 。 由于我自己有一些caching问题,我还试图包含不同的其他标题,但稍后在仔细阅读RFC2616的14.9节之后,我只使用“Cache-Control:private,max-age = 0”。

唯一可以另外讨论的“caching控制”标题是我之前引用的14.9.4节中描述的“必须重新validation”。 这是报价:

必须重新validation指令对于支持某些协议function的可靠操作是必要的。 在任何情况下,HTTP / 1.1caching都必须遵守must-revalidate指令。 特别是如果caching由于某种原因无法到达原始服务器,它必须生成504(网关超时)响应。

当且仅当在实体上重新validation请求失败时,服务器才应该发送必须重新validation的指令,否则会导致不正确的操作,比如默默无闻的财务事务。 收件人不得采取任何违反此指令的自动操作,并且如果重新validation失败,则不得自动提供未经validation的实体副本。

虽然不build议这样做,但是在严格的连接约束条件下运行的用户代理可能会违反这个指令,但是,如果是的话,必须明确地警告用户已经提供了未经validation的响应。 警告务必提供在每个未经validation的访问,并应该需要明确的用户确认。

有时如果我有Internet连接的问题,我看到“网关超时”消息的空白页面。 它来自“必须重新validation”指令的使用。 我不认为“网关超时”消息真的帮助用户。

因此,如果在呼叫他的老板时听到“忙”信号,那么人们更喜欢开始自毁程序,还应该在“caching控制”报头中使用“必须重新validation”指令。 其他人,我build议只使用“caching控制:私人,最大年龄= 0”等等。

我知道这是迟到了,但是这个链接可以帮助大家对这个话题感兴趣: http : //dotnet.dzone.com/articles/output-caching-aspnet-mvc

对于IE,我记得必须设置Expires: -1 。 如何防止在Internet Explorer中的caching似乎用下面的代码片段确认这一点。

 <% Response.CacheControl = "no-cache" %> <% Response.AddHeader "Pragma", "no-cache" %> <% Response.Expires = -1 %> 

回顾代码,这是我发现的。 另外,我隐约记得,如果你设置Cache-Control: private可能无法正确使用SSL。

 Response.AddHeader("Cache-Control", "no-cache"); Response.AddHeader("Expires", "-1"); 

所以,你不想caching,嗯? 提到-1 ,但使用Response.Cache方法:

 // Stop Caching in IE Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache); // Stop Caching in Firefox Response.Cache.SetNoStore(); 

但是, ASP页面caching问题(IE8)说这个代码不起作用。