接獲報案,某使用者今天送出的ASP.NET表單,有某個應為隨機Guid<input type="hidden">欄位,內容竟與幾週前送出的資料重複,因而導致錯誤。

推測最大可能是使用到被IE快取的舊內容導致,查看使用者的IE設定,登楞!

竟被設定「永不」檢查是否有較新版本。經實測,一旦調成此設定,就算重開IE,連上ASP.NET網頁裡的Hidden欄位是上次的舊內容,要等到按F5或重新網頁才會更新。

由此推測問題出在使用者設定了「只要有Cache,永不檢查新版本」,而ASP.NET未防止Cache,因而產生問題。

不過,畫面中的四個選項有何不同,我還真沒認真研究過,藉此機會整理MS KB對「檢查儲存的畫面是否有較新的版本」選項的說明並加上我自己的詮釋:

  • 每次造訪網頁時
    每次連上網頁都重新檢查是否內容有更新,若有更新就顯示新網頁並快取內容。
    背後原理照常送出HTTP Request,而在Header透過If-Modified-Since或If-None-Match傳送上次取得內容時間或ETag,伺服器端依此判斷,若內容已變動則以HTTP 200傳回更新內容,否則傳回HTTP 304 Not Modifed告知瀏覽器使用快取內容。
    此選項不易誤用過期內容,但也不會減少下載Request數(遇HTTP 304資料傳輸量會減少),效率較差。
  • 每次啟動Internet Explorer時:
    在重啟IE之前,重複造訪相同網頁將直接使用快取內容(省略發出Request詢問伺服器是否有更新),按下F5或重新整理時則會重新由網站下載。重啟IE後,造訪相同網頁會檢查是否有新內容。
  • 自動:
    與上一選項相似,但加入額外邏輯演算法偵測網頁中圖片等靜態項目的更新頻率,降低不常變動者的檢查新內容的頻率(即使重啟IE也不會檢查新內容)。
  • Never:
    從不檢查新內容,一律使用快取,除非使用者按下F5或重新整網頁。

由此可知,除非選擇「每次造訪網頁時」,IE在存取ASP.NET網頁時,都有可能直接取用Cache內容而不是重新執行ASP.NET程式,若網頁中有某些每次開啟都不同的隨機內容,需使用一些技巧避免因取用Cache內容生錯。最簡單的做法是設定No-Cache,ASP.NET程式可加入Response.Cache.SetCacheability(HttpCacheability.NoCache); 禁止網頁被Cache,在ASP.NET MVC可用[OutputCacheAttribute(VaryByParam = "*", Duration = 0, NoStore = true)][參考];另一個思考方向則是使用JavaScript,載入網頁後再更新或檢查隨機內容。

總之,設計網站時一定要防範使用者「使用Cache內過時內容送出表單或進行交易」,AJAX及SPA程式寫多了常就疏忽掉這點,本起案例讓我重新喚起警覺,筆記之。


Comments

# by otaku119

請問一下一個外行話 <meta name="Expired" CONTENT="01-jan-1990 00:00:00"> 不能在HTML端設定每次都過期? 還是說因為MVC的razor的原因?

# by Jeffrey

to otaku119,也可行,但我覺得依賴HTML Meta不夠方便,例如:需在每個網頁加註較麻煩、當使用MasterPage或MVC Layout Page時不能針對Action或頁面個別設定。相較之下,由程式端控制,效果相同但較具彈性。

# by s861175

加上這段也會有同樣效果嗎? <meta http-equiv="Pragma" content="no-cache">

# by Jeffrey

to s861175, Pragma是HTTP 1.0舊寫法,現在多改用<meta http-equiv="cache-control" content="no-cache">,效果相同,都是告知瀏覽器不要Cache網頁內容。參考:http://www.i18nguy.com/markup/metatags.html

Post a comment