ASP.NET MVC和IE缓存 - 操纵响应头无效

Cᴏʀ*_*ᴏʀʏ 24 asp.net iis caching http-headers asp.net-mvc-2

背景

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

早期版本的IE(特别是6)是受影响的唯一浏览器.Firefox和Chrome以及较新版本的IE(> 8)都表现得如预期.考虑到这一点,下一节将定义重新创建问题所需的操作.

行为

  1. 用户单击指向操作方法的链接(具有href属性的普通超链接).
  2. action方法生成表示为字节流的PDF.该方法始终重新创建PDF.
  3. 在action方法中,标头设置为指示浏览器如何缓存响应.他们是:

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

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

    一个.缓存控制:公共

    表示任何缓存都可以缓存响应,即使它通常只是非缓存或只能在非共享缓存中缓存.

    缓存控制:必须重新验证

    当高速缓存接收到的响应中存在must-revalidate指令时,该高速缓存必须在该条目变为陈旧后才能响应后续请求而不首先使用源服务器重新验证它

    C.缓存控制:预检(IE5引入)

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

    d.缓存控制:后检查(IE5引入)

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

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

    当请求消息中存在no-cache指令时,应用程序应该将请求转发到源服务器,即使它具有所请求内容的缓存副本

    F.过期

    Expires entity-header字段给出了响应被视为过时的日期/时间.

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

    return File(file, "mime/type", fileName);
    
    Run Code Online (Sandbox Code Playgroud)
  5. 向用户显示"打开/保存"对话框

  6. 单击"保存"按预期工作,但单击"打开"启动PDF阅读器,但是当读者尝试打开文件时,已存储的临时文件IE已被删除,因此它抱怨文件丢失(并且它是).

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

问题

  • 标题是否适合我们正在尝试的内容?我们希望文件暂时存在(获得缓存),但总是被新版本替换,即使请求可能相同).

在返回a之前,在action方法中设置响应头FileResult.我已经让我的同事尝试创建一个继承的新类,FileResult然后重写该ExecuteResult方法,以便它修改标题,然后base.ExecuteResult()改为 - 没有状态.

我预感到"0"的"Expires"标题是罪魁祸首.根据这篇W3C文章,将其设置为"0"意味着"已经过期".我确实希望它过期,我只是不希望IE在处理它的应用程序有机会打开它之前将其从文件系统中删除.

一如既往,谢谢!

编辑:解决方案

在进一步测试(使用Fiddler检查标头)时,我们发现我们认为设置的响应头不是浏览器解释的响应头.由于我自己并不熟悉代码,因此我没有意识到一个潜在的问题:标题在操作方法之外被踩到了.

尽管如此,我还是要打开这个问题.依然突出是这样的:有似乎是有出入Expires具有数值头0-1.如果任何人可以通过设计声称存在差异,就IE而言,我仍然希望听到它.至于解决方案,上面的标题可以按预期工作,并在所有浏览器中Expires设置值-1.

更新1

帖子如何在所有浏览器中控制网页缓存?详细描述了在设置Expires = 0的帮助下,可以在所有浏览器中防止缓存.我仍未在此0vs -1参数上出售...

Ole*_*leg 16

我想你应该用

HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));
Run Code Online (Sandbox Code Playgroud)

要么

HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");
Run Code Online (Sandbox Code Playgroud)

设置max-age=0哪个更像是缓存重新验证(见这里).如果您ETag在标题中另外设置了一些来自数据的哈希自定义校验和,则上一个请求中的ETag将被发送到服务器.服务器能够返回数据,或者,如果数据与之前完全相同,则它可以返回空体并HttpStatusCode.NotModified作为状态代码.在这种情况下,Web浏览器将从本地浏览器缓存中获取数据.

我建议你使用Cache-Control: private哪个强制两个重要的东西:1)关闭缓存代理上的数据,这有时非常积极的缓存设置2)它将允许缓存数据,但不允许与另一个共享缓存用户.它可以解决隐私问题,因为您返回给一个用户的数据可能不被其他用户读取.顺便说一下,默认情况下在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协议的更多缓存选项,我建议您阅读缓存教程.

更新:我决定写一些更多信息来清除我的立场.对应来自维基百科的信息,即使像Mosaic 2.7,Netscape 2.0和Internet Explorer 3.0这样的旧网络浏览器也支持1996年3月,RFC 2068中描述的HTTP/1.1的预标准.所以我想(但不测试它)旧的Web浏览器支持max-age=0HTTP标头.无论如何,Netscape 2.06和Internet Explorer 4.0最终支持HTTP 1.1.

所以你应该先问你:你使用哪种HTML标准?您是否仍然使用HTML 2.0而不是1997年1月发布的更晚HTML 3.2?我想您至少使用了1997年12月发布的HTML 4.0.因此,如果您至少在HTML 4.0中构建应用程序,您的站点可以面向支持HTTP 1.1的Web客户端,并忽略(不支持)Web客户端.不支持HTTP 1.1.

现在关于其他"Cache-Control"标题为"private,max-age = 0".在我看来,包括标题是纯粹的偏执狂.由于我自己有一些缓存问题,我还尝试包含不同的其他标题,但后来仔细阅读了RFC2616的第14.9节后,我只使用了"Cache-Control:private,max-age = 0".

可以另外讨论的唯一"Cache-Control"标题是我之前引用的14.9.4节中描述的"必须重新验证".这是引用:

必须重新验证指令对于支持某些协议功能的可靠操作是必需的.在所有情况下,HTTP/1.1缓存必须遵守must-revalidate指令; 特别是,如果缓存因任何原因无法到达原始服务器,它必须生成504(网关超时)响应.

当且仅当未能重新验证实体上的请求可能导致不正确的操作(例如无声的未执行的金融交易)时,服务器应该发送必须重新验证的指令.收件人不得采取违反此指令的任何自动操作,如果重新验证失败,则不得自动提供实体的未经验证的副本.

尽管不建议这样做,但在严格连接限制下运行的用户代理可能违反此指令,但如果是这样,则必须明确警告用户已提供未经验证的响应.必须在每个未经验证的访问上提供警告,并且应该要求明确的用户确认.

有时如果我遇到Internet连接问题,我会看到带有"Gateway Timeout"消息的空白页面.它来自"必须重新验证"指令的使用.我不认为"Gateway Timeout"消息真的对用户有帮助.

因此,如果人们在打电话给他的老板时听到"忙碌"信号,他们更喜欢开始自我毁灭程序,还应该在"Cache-Control"标题中使用"must-revalidate"指令.我推荐的其他人只使用"Cache-Control:private,max-age = 0",仅此而已.