ASP.NET 狀態管理(檢視狀態 ViewState)
無論 Web 程式框架多麼先進,它都不能改變一個事實:HTTP是一種無狀態協議。
每次 Web 請求後,客戶端和伺服器端斷開,同時 ASP.NET 引擎釋放頁面物件。這種架構保證了Web應用程式能夠同時響應數千個併發請求而不會導致伺服器記憶體崩潰。但其負面效應是你必須通過其他技術儲存 Web 請求之間的資訊並在需要的時候獲取它們。
檢視狀態
檢視狀態是在單個頁面中儲存資訊的第一選擇。ASP.NET Web控制元件也使用試圖狀態在回發間儲存屬性值。通過頁面內建的 ViewState 屬性,你可以把自己的資料放入到檢視狀態集合中,可以儲存的資訊型別包括簡單資料型別和自定義物件
和 ASP.NET 中大多數的狀態管理類似,檢視狀態依賴於字典集合。集合中每個專案通過一個唯一的字串名字進行索引。
ViewState["Counter"] = 1;
如果集合中沒有叫做 Counter 的索引項,它將被自動新增。
如果已經存在,它將被替換。
ViewState 通過鍵值來讀取值,由於需要處理不同的資料型別,它將所有的資料儲存為 Object 型別,因此在使用時涉及到資料型別的轉換。
int count;
if(VietState["Count"] != null)
{
count = (int)ViewState["Count"];
}
如果試圖查詢一個集合中不存在的值,則會得到一個 NullReferenceException 異常,因此需要進行判空操作。
檢視狀態示例
下面的程式碼演示了頁面如何使用檢視狀態,它允許使用者儲存一系列的值,並將其恢復。這段程式碼使用遞迴遍歷所有的子控制元件,由於控制元件ID在頁面中是唯一的,所有它被用作檢視狀態的鍵值。
public partial class Chapter06_ViewStateTest : System.Web.UI.Page
{
protected void btnSave_Click(object sender, EventArgs e)
{
SaveAllText(this.Table1.Controls, true);
}
protected void btnRestore_Click(object sender, EventArgs e)
{
RestoreAllText(this.Table1.Controls, true);
}
private void SaveAllText(ControlCollection controls, bool saveNested)
{
foreach (Control control in controls)
{
if (control is TextBox)
{
ViewState[control.ID] = ((TextBox)control).Text;
}
// bool型別的 saveNested 引數給方法提供了更大的靈活性
// 可以控制是否需要遞迴
if (control.Controls != null && saveNested)
{
SaveAllText(control.Controls, true);
}
}
}
private void RestoreAllText(ControlCollection controls, bool saveNested)
{
foreach (Control control in controls)
{
if (control is TextBox)
{
if (ViewState[control.ID] != null)
{
((TextBox)control).Text = ViewState[control.ID].ToString();
}
}
if (control.Controls != null && saveNested)
{
RestoreAllText(control.Controls, true);
}
}
}
}
在檢視狀態中儲存物件
在檢視狀態中儲存專案時,ASP.NET 需要將它轉換為位元流以便新增到頁面的隱藏欄位中,這個過程被叫做序列化。
物件在預設情況下不能被序列化,檢視將一個不能序列化的物件儲存到檢視狀態中,會得到一條錯誤資訊,為使物件可序列化,需要在類的宣告前加上 Serializable 特性。
// 指示一個類可以序列化
[Serializable]
public class Customer
{
public string FirstName;
public string LastName;
public Customer(string firstName,string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
因為 Customer 類現在被標記為可序列化的,所有它可以被儲存在檢視狀態中:
Customer cust = new Customer("Malia", "Carry");
ViewState["CurrentCustomer"] = cust;
也可以從中讀取自定義物件:
Customer cust;
if (ViewState["CurrentCustomer"]!=null)
{
cust = (Customer)ViewState["CurrentCustomer"];
}
為了讓類可序列化,它需要符合下列條件:
- 類必須有 Serializable 特性。
- 它的任何基類都必須有 Serializable 特性。
- 所有成員變數必須為可序列化的資料型別。否則所有不可序列化的資料必須標識 NonSerialized,它們將被簡單的忽略。
示例:
public partial class Chapter06_ViewStateTest : System.Web.UI.Page
{
protected void btnSave_Click(object sender, EventArgs e)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
SaveAllText(this.Table1.Controls, dict, true);
ViewState["ControlText"] = dict;
}
protected void btnRestore_Click(object sender, EventArgs e)
{
if (ViewState["ControlText"] != null)
{
Dictionary<string, string> dict
= ViewState["ControlText"] as Dictionary<string, string>;
// KeyValuePair 定義可設定或檢索的鍵/值對。
foreach (KeyValuePair<string, string> item in dict)
{
Label1.Text += item.Key + " = " + item.Value + "<br />";
}
}
}
private void SaveAllText(ControlCollection controls, Dictionary<string, string> dict, bool saveNested)
{
foreach (Control control in controls)
{
if (control is TextBox)
{
dict.Add(control.ID, ((TextBox)control).Text);
}
if (control.Controls != null && saveNested)
{
SaveAllText(control.Controls, dict, true);
}
}
}
}
效果:
檢視狀態評估
檢視狀態是一個理想的狀態管理方式,因為它既不消耗伺服器記憶體也不加強額外限制(如超時)。那麼什麼原因會迫使你放棄檢視狀態而採用其他的狀態管理方式呢?以下是 3 個可能的原因:
- 需要儲存業務的核心資料,它們不允許被使用者篡改。(聰明的使用者可能會在回傳請求中修改檢視狀態資訊)
- 需要儲存被多個頁面使用的資訊。(這種情況下考慮使用會話狀態、cookie、查詢字串)
- 需要儲存的資訊量非常大,並且不希望因此影響頁面傳送時間。(這種情況下,考慮使用資料庫或會話狀態)
有選擇的禁用檢視狀態
削減不必要的檢視狀態是減少頁面傳送時間的好辦法,下面 2 種情況沒有必要使用控制元件的檢視狀態:
- 控制元件從來不會變化(例如一個靜態文字的按鈕不需要使用檢視狀態)
- 控制元件在每次回傳中被重新填充
將某個控制元件的 EnableViewState 屬性設為 false 後,就可以關閉它的檢視狀態。而將頁面的 EnableViewState 設為 false 後,可以關閉整個頁面及其所有控制元件的檢視狀態(設定 Page 指令中的 EnableViewState 特性可以達到同樣效果)。還可以在 web.config 檔案裡把 <pages> 元素的 enableViewState 特性設定為 false 來禁用網站所有頁面的檢視狀態。
可以關閉某個頁面的檢視狀態,但通過對特定控制元件顯式起用檢視狀態,從而達到選擇性的覆蓋頁面設定!這項技術室 ASP.NET 4 新增的,特別受熱心於把頁面檢視狀態減小到最少的開發人員的歡迎。為了達到這一目的,需要用到另一個叫做 ViewStateMode 的屬性。
ViewStateMode 可以應用到所有控制元件和頁面,取值有以下 3 個:
- Enable:只要 EnableViewState 屬性啟用了檢視狀態,檢視狀態就可以工作。
- Disabled:該控制元件的檢視狀態不起作用,但該控制元件的子控制元件可以覆蓋這個值。
- Inherit:控制元件繼承該控制元件的容器的 ViewStateMode 值。(預設選項)
為了有選擇的使用檢視狀態,現在可以把頁面的 ViewStateMode 設為 Disabled 來關閉頁面級檢視狀態,需要使用檢視狀態的控制元件則單獨設定該屬性為 Enable。請注意,我們並沒有把 EnableViewState 設定為 false ,如果這麼做的話,ASP.NET 會完全關閉頁面的檢視狀態,沒有控制元件可以再起用它。
這個模型有點笨拙,但當檢視狀態的大小構成問題時它確實有用。唯一的缺點是你必須記得為那些具有需要持久化的動態值的控制元件或者必須依賴於檢視狀態才能正確工作的控制元件顯式起用檢視狀態。
檢視狀態安全
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="fakIvy3d8tjpX6spp/o260Nau17ykR" />
檢視狀態資訊以 Base64 編碼的單個字串形式儲存,由於這些值並沒有格式化為明文的形式,所有很多 ASP.NET 程式設計師誤以為他們的檢視狀態資料時被加密的,但事實並非如此,惡意的使用者可以在數秒內逆向的設計這個字串從而查到你的檢視狀態資料。
有兩個方法可以讓檢視狀態更加安全:
- 使用雜湊碼保證檢視狀態資訊不被篡改(雜湊碼是一種強校驗碼)。
- 啟用檢視狀態加密
注1:
雜湊碼預設就是啟用的,你不必作任何多餘的設定
注2:
啟用加密可以在 Page 指令中或 web.config 檔案中設定
<%@ Page ViewStateEncryptionMode="Always" />
<pages viewStateEncryptionMode="Always" />
加密設定有3個選項:
- Always:總是加密
- Never:從不加密
- Auto:總在控制元件要求時加密(預設選項)
當控制元件要求加密時,會呼叫 Page.RegisterRequiresViewStateEncryption()方法來啟用加密。但控制元件並沒有絕對的控制力,如果頁面加密模式是 Never,那麼控制元件即便呼叫了加密方法,也不會有任何作用。
非必要時不要加密檢視狀態資料。加密會導致效能下降,因為每次回發時 Web 伺服器都需要執行加密和解密。