在ASP.NET里面,View State使用較為廣泛。它作為一個隱藏字段,可以幫助服務端”記住“客戶端的改變,這樣客戶端 收到服務器對PostBack的響應后,仍然可以展現在PostBack之前設定的值 (具體參見http://msdn.microsoft.com/en-us/library/bb386448(v=vs.100).aspx )
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="..." />
為了防止惡意客戶端的PostBack里的ViewState被解讀,ASP.NET會用消息驗證碼(MAC)來檢查每個ViewState。可是一旦服務器無法正確解釋正常客戶端PostBack回來的ViewState時,整個應用都會停止工作。比如出現 ”Validation of viewstate MAC failed” 的錯誤。
通常來說,一旦這樣的錯誤出現,首先會考慮以下幾種情況:
1. 是不是有多台Web Server在負載均衡情況下運行。如果是的話,需要各台服務器使用相同的MAC進行ViewState的加密和解密工作。否則如果這個負載均衡環境沒有完全做的Session Affinity,這種錯誤就會出現。
2. 測試本機訪問是否也有這種錯誤。如果是的話,除了嘗試重新產生新的MachineKey (參見http://blogs.msdn.com/b/amb/archive/2012/07/31/easiest-way-to-generate-machinekey.aspx ),也可以用Process Monitor在復現問題的時候跟蹤文件和注冊表的訪問,看看是不是因為W3WP.exe缺少權限而不能獲取和MachineKey相關的信息。
3. 在客戶端和服務器端抓取網絡包,比較ViewState是否被中間設備改動。這種情況不多見,但是也遇到過。算是復雜的一種情況。如果連接是SSL的,抓包沒有辦法查看,客戶端就要使用Fiddler,而服務端需要采取額外診斷日志或者Debug的方法。
4. 和具體代碼相關,尤其是對ViewStateUserKey有特殊設置。
這里談到的是一個在一個大型的生產應用環境里遇到的實際問題,和上面的情況有關,但有些有趣的變化。
這個環境里面有多台Web 服務器,采用了負載均衡方式。在客戶試圖登錄時(login.aspx),總是會遇到”Validation of viewstate MAC failed”的錯誤。
起初,懷疑是不同機器上WebAppication的MachineKey不一樣引起的。檢查了Web.Config里的配置,各個機器都是一樣:
<system.web> <machineKey decryptionKey="6284D74F8D9745C38712047622FFA047B02CA5C4049FB74E,IsolateApps" validationKey="137B974DC38A910D946AAF3ADF1D0386072170236F39C8165098035126FE7DFDF68C7BD3646052CE1769A47A45F098A65CEC3089523543370DD37830A5B2D13,IsolateApps" /> </system.web>
而且負載均衡也設置了Class C的Session Affinity
后來發現這個問題即使本機訪問也會出現。把應用程序池身份改為Admin后,問題同樣。表明和權限無關。重新創建MachineKey,也沒有變化。
這時需要關注代碼。獲取了頁面代碼做Review. 在Login頁面的Page_Init里面, 看到ViewStateUserKey
protected void Page_Init(object sender, EventArgs e) { this.ViewStateUserKey = this.Session.SessionID; }
表面上看這樣的寫法也沒有什么問題。
在IE上啟用了Fiddler (連接是SSL) 后,發現客戶端的PostBack里面沒有Cookie的信息。這就是問題出現的直接原因:
a. 在第一次訪問Login頁面時,用一個隨機的SessionID A被嵌入了ViewState里面
b. 在PostBack時,由於沒有Cookie傳回來(ASP.NET SessionID缺省存放在Cookie里),服務器就判斷道客戶端沒有SessionID, 於是又使用一個新的SessionID B做ViewStateUserKey. 這樣,從ViewState里面解出的是上次的SessionID A, 和新的不匹配。錯誤就出現了。
可是客戶端為什么不發送Cookie呢?
原來這個服務器第一次回復時,對客戶端的HTTP Header里面根本沒有Set-Cookie字段。
這是沒有賦予Session 變量時ASP.NET的缺省行為 ( SessionID每次請求都會形成,但是未必會發送Set-Cookie 來讓客戶端保留這個SessioID,除非有賦予Session變量的行為)
於是解決這個問題的直接方法就是在這個Page_Init里面第一時間加一個語句:
protected void Page_Init(object sender, EventArgs e) { this.ViewStateUserKey = this.Session.SessionID; session("forViewSate")="value" }
這樣服務器把SessionID在”Set-Cookie”里發送回去,客戶端也可以用Cookie保留SessionID。問題就立刻解決了。
更多參考:
微軟技術文章討論如何處理” ”Validation of viewstate MAC failed”: