接獲報案,某使用者今天送出的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程式寫多了常就疏忽掉這點,本起案例讓我重新喚起警覺,筆記之。