2.2.4 StateBag類
ViewState是控件的一個屬性,用來使用控件具有記憶功能。在前邊的講述中,我們可以看到控件的一些屬性通過使用ViewState能夠恢復原來的值,保存本次的值,在Control類中很多方法的實現也是直接調用了ViewState的方法。ViewState的類型是StateBag,下面我們就了解一下在StateBag中是如何實現這些功能的。StateBag定義在System.Web.UI中聲明如下:
public sealed class StateBag : IStateManager, IDictionary, ICollection, IEnumerable
StateBag類可以理解為是一個具有狀態管理功能的字典,因為它實現了IStateManager, IDictionary 這兩個接口。StateBag類可以象字典那樣保存Key/Value對,其中Key是字符串而Value是對象。下面是一個使用StateBag的例子。
protected void Button2_Click(object sender, EventArgs e) { StateBag TestSB = new StateBag(); TestSB["b"] = "bbbbb"; TextBox1.Text = TestSB["b"].ToString(); }
在上面的例子中使用StateBag保存一個Key為“b”,其值為“bbbbb”的Key/Value對。ViewState屬性也是StateBag的一個實例,當然也就可以象上面那樣使用。在ViewState中保存了很多的Key/Value對(鍵值對),這些Key/Value對用來保存控件的屬性,這些Key/Value對是有ASP.Net來維護的。當然我們也可以增加一些自己的Key/Value對,來保存一些信息。
StateBag還實現System.Web.UI.IStateManager接口,這樣它具有狀態管理功能。下面對StateBag如何提供狀態管理功能進行說明。
1) StateItem類
StateBag中保存Key/Value對,Key是String類型,Value是Object類型。但是在StateBag內部保存Value不是Object類型,而是將Object類型轉換為StateItem類型然後保存,從StateBag中取出的時候再將StateItem類型轉換為Object類型,也就是說StateBag中的Key/Value對實際上是String/StateItem類型。轉換過程是在StateBag內部實現客戶感覺不到。StateItem的聲明如下:
public sealed class StateItem { internal StateItem(object initialValue); public bool IsDirty { get; set; } public object Value { get; set; } }
通過上面的代碼我們可以看出實際上多了一個IsDirty屬性,來標記當前的Value是否已經被修改過。
2) Add
Add方法是將傳入的Key,Value保存到字典中,並處理IsDirty屬性。在StateBag.LoadViewState方法中會調用Add方法。其示意代碼如下:
public StateItem Add(string key, object value) { StateItem item = this.bag[key] as StateItem; if (item == null) { if ((value != null) || this.marked) { item = new StateItem(value); this.bag.Add(key, item); } } else { item.Value = value; } if ((item != null) && this.marked) { item.IsDirty = true; } return item; }
雖然函數的名稱是Add,其實也包括了更新。如果當前的項在字典中不存在則新增,否則更新。新增時新建一個StateItem類型的對象item,將Key和item增加到字典中。如果Item不為null,並且跟蹤狀態標記為true,則item的IsDirty為true。什麼情況下會調用Add方法呢?主要有兩種情形一種是StateBag.LoadViewState,在下面會具體介紹到。還有一種情況就是對控件的屬性賦值的時候,比如Button.Text=”button”,此時會調用Text屬性的Set,在Set中執行的代碼this.ViewState["Text"] = value,這個代碼實際上執行this.ViewState.Add("Text",value)。
3) LoadViewState
還原以前保存的ViewState。將傳入的ArrayList對象加載到字典中。示意代碼如下:
internal void LoadViewState(object state) { ArrayList list = (ArrayList) state; for (int i = 0; i < list.Count; i += 2) { string key = ((IndexedString) list[i]).Value; object obj2 = list[i + 1]; this.Add(key, obj2); } }
我們知道在初始化階段StateBag.TrackViewState都已經被調用過了,也就是說marked為true了,這樣在StateBag.LoadViewState中調用Add方法第一次加載完ViewState的數據後,所有的StateItem的IsDirty屬性都是true。
4) SaveViewState
將字典中已經修改過的Key/Value存放在一個ArrayList對象中返回。
internal object SaveViewState() { ArrayList list = null; IDictionaryEnumerator enumerator = this.bag.GetEnumerator(); while (enumerator.MoveNext()) { StateItem item = (StateItem) enumerator.Value; if (item.IsDirty) { list.Add(new IndexedString((string) enumerator.Key)); list.Add(item.Value); } } } return list; }
在SaveViewState中只會將字典中item.IsDirty=true的項目返回,哪些項的IsDirty=true呢?通過上面對Add方法及LoadViewState的分析我們知道,當我們第一次設置了控件的某個屬性後會調用Add方法,這個屬性對應的StateItem的屬性IsDirty會被設置為true,在SaveViewState時會保存這個item。在下次請求時在LoadViewState中也會將IsDirty設置為true,在SaveViewState是會保存這個item。總之只要控件屬性的被修改過和默認值不一致都會一直被保存,即使這個屬性的值僅僅被修改過一次,之後保存不不變,也會在多次PostBack之間保存起來。
5) TrackViewState
在TrackViewState方法中,設置跟蹤標記為True,其目的就是開始狀態跟蹤了。示意代碼如下:
internal void TrackViewState() { this.marked = true; }
3 ViewState與ControlState
ControlState是一個自定義的狀態保持機制,也就是說保持狀態的機制需要開發人員自己去完成,而不像ViewState,它有自己默認的狀態保持機制。既然已經有了ViewState為什麼還需要ControlState呢?因為ViewState是可以被禁用的,而ControlState卻不能被禁用,對於有些必須保存的信息,就可以使用ControlState。ControlState的實現思路基本上與ViewState類似,ControlState需要保存的信息也被序列化後保存在__VIEWSTATE中。使用ControlState,需要在OnInit 方法中調用 RegisterRequiresControlState向頁面注冊,而且要重寫SaveAdapterControlState,LoadAdapterControlState這兩個方法自己,實現要保存什麼,怎樣保存。
4 ViewState的使用
4.1ViewState的優缺點
使用ViewState首先要了解ViewState與其他的保持狀態機制相比有什麼優缺點。
使用ViewState具有以下優點:
一、耗費的服務器資源較少(與Application、Session相比)。因為,視圖狀態數據都寫入了客戶端計算機中。
二、易於維護。默認情況下,DotNet系統自動啟用對狀態數據的維護。
三、因為它不使用服務器資源、不會超時,並且適用於任何浏覽器。
使用視圖狀態具有以下缺點:
一、性能問題。由於視圖狀態存儲在頁本身,因此如果存儲較大的值,用戶顯示頁和發送頁時的速度仍然可能減慢。ViewState 增加了發送到浏覽器的頁面的大小,同時也增加了回傳的窗體的大小,因此不適合存儲大量數據
二、設備限制。移動設備可能沒有足夠的內存容量來存儲大量的視圖狀態數據。
三、潛在的安全風險。視圖狀態存儲在頁上的一個或多個隱藏域中。雖然視圖狀態以哈希格式存儲數據,但它可以被篡改。如果直接查看頁輸出源,可以看到隱藏域中的信息,盡管 ViewState 數據已被編碼,並且可以選擇對其進行加密,但始終不將數據發送到客戶端才是最安全的。
4.2ViewState安全性
ViewState將一些信息保存在客戶端,而且默認情況下客戶端的數據僅僅是進行了Base64編碼了,很容易被看到原文。當然ViewState還有一些安全性措施來改善這一點。ViewState 安全性對於處理和呈現 ASP.NET 頁面所需的時間有直接的影響。簡單地說,安全性越高,速度越慢。因此如果不需要,不要為 ViewState 添加安全性。
4.2.1 MAC
MAC是message authentication check的簡寫即消息驗證檢查。MAC可以防止ViewState數據被篡改。可以通過設置EnableViewStateMAC="true"屬性來啟用消息驗證檢查。可以在頁面級別上設置 EnableViewStateMAC,也可以在應用程序級別上設置。有一點需要特別說明的是在MSDN中說EnableViewStateMAC的默認值是Flase,但是實際上發現EnableViewStateMAC的默認值是True,這一點大家可以Page_Load中輸出EnableViewStateMAC屬性來驗證。也就是說默認情況下都是對ViewState進行防篡改處理的。下面的描述結合“2.2.1Page中的處理”中的“4)ViewState序列化與反序列化”進行理解。
在ObjectStateFormatter中進行序列化的時候如果EnableViewStateMAC="true",則在MachineKeySection.GetEncodedData中根據字節流buf計算出一個20位的字節表示Hash值,增加在buf字節流的後邊,形成一個新的字節流。
在ObjectStateFormatter中進行反序列化的時候如果EnableViewStateMAC="true",則在MachineKeySection.GetDecodedData中取buf的前(length-20)位計算出一個20位的字節表示Hash值,將這個Hash值與buf的最後20位進行比較,如果不一直則說明ViewState被篡改了則拋出異常。
默認情況下,.NET框架使用SHA1算法來生成ViewState散列代碼。此外,也可以通過在machine.config文件中設置<machineKey>來選擇 MD5 算法,如下所示:<machineKey validation="MD5" />。MD5算法的性能要比SHA1算法好一些,但是同樣不夠安全。
4.2.2加密
默認情況下__VIEWSTATE中存儲的值僅僅僅進行了Base64編碼,並沒有進行加密,如果ViewState中有一些敏感信息需要增加安全性,我們也可以對ViewState進行加密。我們可以設置ViewStateEncryptionMode的值來決定是否加密,其值是“Auto”,“Always”,“Never”,默認值是“Auto”。“Always”表示進行加密,“Never”表示不進行加密,“Auto”時如果調用了RegisterRequiresViewStateEncryption方法後則進行加密。ViewStateEncryptionMode屬性不能在代碼中設置page指令或者配置文件中使用。
4.2.3設置ViewStateUserKey
設置 ViewStateUserKey 屬性有助於防止您的應用程序受到惡意用戶的點擊式攻擊。必須在頁處理的 Page_Init 階段設置此屬性。具體的信息可以MSDN中的說明,鏈接如下:
http://msdn2.microsoft.com/zh-cn/library/ms972969.aspx
4.3ViewState的禁用
因為ViewState會一定程度上影響性能所以在不需要的時候禁用 ViewState。默認情況下 ViewState 將被啟用,並且是由每個控件(而非頁面開發人員)來決定存儲在 ViewState 中的內容。有時,這一信息對應用程序並沒有什麼用處。盡管也沒什麼害處,但卻會明顯增加發送到浏覽器的頁面的大小。因此如果不需要使用 ViewState,最好還是將它關閉,特別是當 ViewState 很大的時候。通過將對象的EnableViewState屬性設置為False禁用ViewState。可以針對單個控件、整個頁面或整個應用程序禁用ViewState,如下所示:
每個控件(在標記上)<asp:datagrid EnableViewState="false" ?/>
每個頁面(在指令中) <%@ Page EnableViewState="False" ?%>
每個應用程序(在 web.config 中) <Pages EnableViewState="false" ?/>
以下情況將不再需要ViewState:(1)控件未定義服務器端事件(這時的控件事件均為客戶端事件且不參加回送的);(2)控件沒有動態的或數據綁定的屬性值。
4.4ViewState優化
ViewState優缺點並存,有些人支持使用,有些人反對使用,也有人提出了對ViewState的優化,主要是壓縮ViewState減少傳輸量及修改ViewState的存儲位置。具體可以參考一下的鏈接。
壓縮:http://www.cnblogs.com/mack/archive/2005/07/27/201411.html
修改存儲位置:http://czhenq.cnblogs.com/archive/2006/04/03/365807.html