ASP.NET MVC 和 IE 記憶體 - 操作響應標頭無效
背景
我正在嘗試幫助一位同事調試過去 6 個月內沒有出現的問題。在最近部署 ASP.NET MVC 2 應用程序之後
FileResult,強制使用者打開或保存 PDF 文件的響應在客戶端電腦上存在足夠長的時間以供 PDF 閱讀器打開它們時遇到問題。IE 的早期版本(尤其是 6)是唯一受影響的瀏覽器。Firefox 和 Chrome 以及更新版本的 IE (>8) 都按預期執行。考慮到這一點,下一部分定義了重新創建問題所需的操作。
行為
- 使用者點擊指向操作方法的連結(帶有
href屬性的普通超連結)。- action 方法生成一個表示為字節流的 PDF。該方法總是重新創建 PDF。
- 在 action 方法中,設置標頭以指示瀏覽器如何記憶體響應。他們是:
response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0"); response.AddHeader("Pragma", "no-cache"); response.AddHeader("Expires", "0");對於那些不熟悉標題的確切作用的人:
一種。記憶體控制:公共
表示響應可以被任何記憶體記憶體,即使它通常是不可記憶體的或只能在非共享記憶體中記憶體。
灣。記憶體控制:必須重新驗證
當記憶體接收到的響應中存在 must-revalidate 指令時,該記憶體不能在該條目變得陳舊後使用該條目來響應後續請求,而無需首先使用原始伺服器重新驗證它
C。Cache-Control:預檢查(IE5 引入)
以秒為單位定義必須檢查實體是否新鮮的時間間隔。檢查可能在向使用者顯示資源後進行,但確保在下一次往返中記憶體的副本將是最新的。
d。Cache-Control:後檢查(IE5 引入)
以秒為單位定義一個時間間隔,在該時間間隔之後,必須在向使用者顯示資源之前檢查實體的新鮮度。
e. Pragma:no-cache(確保向後兼容 HTTP/1.0)
當請求消息中出現 no-cache 指令時,應用程序應該將請求轉發給源伺服器,即使它有正在請求的內容的記憶體副本
F。過期
Expires entity-header 欄位給出了響應被認為是陳舊的日期/時間。
- 我們從動作中返回文件
return File(file, "mime/type", fileName);
- 向使用者顯示打開/保存對話框
- 點擊“保存”按預期工作,但點擊“打開”會啟動 PDF 閱讀器,但儲存的臨時文件 IE 在閱讀器嘗試打開文件時已被刪除,因此它抱怨文件失去(並且它是)。
這裡有六個其他應用程序使用相同的標題來強制 Excel、CSV、PDF、Word 和大量其他內容向使用者發送,並且從來沒有出現過問題。
問題
- 標題是否適合我們正在嘗試做的事情?我們希望文件暫時存在(被記憶體),但總是被新版本替換,即使請求可能相同)。
響應標頭在返回 a 之前在 action 方法中設置
FileResult。我已經要求我的同事嘗試創建一個新類,該類繼承自該方法FileResult並改為覆蓋該ExecuteResult方法,以便它修改標頭,然後base.ExecuteResult()改為執行此操作–對此沒有任何狀態。我有一種預感,“0”的“Expires”標題是罪魁禍首。根據這篇 W3C 文章,將其設置為“0”意味著“已經過期”。我確實希望它過期,我只是不希望 IE 在處理它的應用程序有機會打開它之前將其從文件系統中刪除。
一如既往,謝謝!
編輯:解決方案
經過進一步測試(使用 Fiddler 檢查標頭),我們看到我們認為設置的響應標頭不是瀏覽器正在解釋的標頭。由於我自己不熟悉程式碼,我沒有意識到一個潛在的問題:標題在操作方法之外被踩到了。
儘管如此,我將保留這個問題。仍然很突出的是:標頭之間似乎存在一些差異,
Expires其值為0vs.-1。如果有人可以聲稱設計上的差異,關於 IE,我仍然想听聽。不過,就解決方案而言,上述標頭確實可以在所有瀏覽器中Expires設置為值的情況下按預期工作。-1更新 1
文章如何在所有瀏覽器中控製網頁記憶體?詳細描述了通過設置 Expires = 0 可以在所有瀏覽器中防止記憶體。我仍然不相信這個
0vs-1參數……
我認為你應該只使用
HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));要麼
HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");設置
max-age=0這意味著記憶體重新驗證(請參閱此處)。如果您將ETag在標頭中另外設置一些您自定義的數據雜湊校驗和,則來自上一個請求的 ETag 將被發送到伺服器。伺服器可以返回數據,或者,如果數據與以前完全相同,它可以返回空正文並HttpStatusCode.NotModified作為狀態碼。在這種情況下,Web 瀏覽器將從本地瀏覽器記憶體中獲取數據。我建議您使用
Cache-Control: privatewhich force 兩件重要的事情: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 客戶端,而忽略(不支持)支持 HTTP 1.1 的 Web 客戶端不支持 HTTP 1.1。
現在將其他“Cache-Control”標頭稱為“private,max-age = 0”。在我看來,包括標題是純粹的偏執狂。由於我自己有一些記憶體問題,我也嘗試包含不同的其他標頭,但後來在仔細閱讀了 RFC2616 的第 14.9 節後,我只使用了“Cache-Control: private, max-age=0”。
唯一可以另外討論的“Cache-Control”標頭是我之前引用的第 14.9.4 節中描述的“must-revalidate”。這是報價:
must-revalidate 指令對於支持某些協議功能的可靠操作是必要的。在所有情況下,HTTP/1.1 記憶體必須遵守 must-revalidate 指令;特別是,如果記憶體因任何原因無法到達源伺服器,它必須生成 504(網關超時)響應。
伺服器應該發送 must-revalidate 指令當且僅當未能重新驗證實體上的請求可能導致不正確的操作,例如靜默未執行的金融交易。接收者不得採取任何違反此指令的自動操作,並且如果重新驗證失敗,則不得自動提供未經驗證的實體副本。
儘管不建議這樣做,但在嚴格的連接約束下執行的使用者代理可能會違反此指令,但如果是這樣,必須明確警告使用者已提供未經驗證的響應。必須在每個未經驗證的訪問上提供警告,並且應該需要明確的使用者確認。
有時,如果我遇到 Internet 連接問題,我會看到帶有“網關超時”消息的空白頁面。它來自“必須重新驗證”指令的使用。我不認為“網關超時”消息真的對使用者有幫助。
因此,如果在給老闆的電話中聽到“忙”信號時,如何更願意開始自毀程序,應該在“記憶體控制”標題中另外使用“必須重新驗證”指令。我建議其他人只使用“Cache-Control: private, max-age=0”,僅此而已。