• |
ViewState 如何工作 |
• |
ASP.NET 2.0 ViewState 的改進 |
• |
使用控制項狀態維護功能 |
• |
效能考慮 |
|
本文使用以下技術: ASP.NET、C# 代碼下載: ViewState.exe(122KB) |
本頁內容
|
ViewState 基本原理 |
|
ViewState 問題 |
|
ASP.NET 2.0 中的 ViewState 改進 |
|
控制項狀態 |
|
聲明性資料來源和 ViewState |
|
小結 |
如果您是個經驗豐富的 ASP.NET 開發人員,一提起 ViewState ,您可能會不寒而慄,因為您想到的是大量通過“雞尾酒吸管”吸入的 Base 64 編碼資料。除非採取步驟進行預防,否則大部分 ASP.NET 頁面將有大量輔助資料被儲存在一個名為 __VIEWSTATE 的隱藏欄位中,多數情況下,甚至不需要這個欄位。瀏覽用 ASP.NET 產生的您喜愛的網站,查看頁面原始碼,計算隱藏在 __VIEWSTATE 欄位中的字元數。我嘗試了一下,數量為 800 到 7,800 個字元。
當然, ViewState 在 ASP.NET 中有個重要的角色。如果使用恰當,它能夠簡化頁面開發,改進使用者與網站的互動。如果置之不理,它能夠顯著增加網站響應大小,在連線速度慢的情況下,使您的回應時間更加緩慢。ASP.NET 2.0 的發布帶來了 ViewState 機制的一些改進,這使得 ViewState 使用更簡單,又不會防礙網站效能。這些改進包括:減少編碼數量,採用控制項狀態從內容中分離出行為狀態,以及智能整合資料繫結控制項。
ViewState 基本原理
在介紹 ASP.NET 2.0 ViewState 的改進之前,簡要總結目前版本中 ViewState 的用途和實現是適宜的。 ViewState 為 ASP.NET 開發人員解決了一個特定問題 — 保留伺服器端不形成元素的控制項的狀態。這很重要,因為 ASP.NET 中的大部分伺服器端控制項模型是根據這樣一個假設產生的,那就是 — 如果使用者回傳到相同頁面,所有控制項保持其狀態不變。也就是說,如果在處理請求期間修改任何控制項的內容,任何後續 POST 請求回到相同頁面時,您可以依賴於那些仍然存在的修改。作為一個活動的 ViewState 樣本,嘗試運行圖 1 顯示的頁面。
每次按下 Submit 按鈕時,_sum 範圍值遞增。因為 ViewState 在請求期間保持以前的值,因此它將從上一次顯示的值開始,顯示 1、2、3、4 等等。如果想知道 ViewState 不可用時發生的事情,嘗試添加 enableviewstate='false' 作為範圍元素的一個屬性。因為以前的範圍值在請求處理時沒有傳播,所以不論頁面發布多少次,都將顯示值為 1。
在 ASP.NET 中, ViewState 完成基於控制項的編程模型。如果沒有 ViewState ,一些控制項(如文字框和下拉式清單)在 POST 請求期間保持狀態,而其他控制項不保持,使用這些狀態各異的控制項記錄一些特殊的情況是令人沮喪的體驗。使用 ViewState ,開發人員能夠專註於編程模型和使用者介面,而不用擔心狀態保持。還能對 ViewState 進行雜湊或加密,以防止使用者篡改或解碼。關於這個主題的更多資訊,請參見線上書籍 Securing Your ASP.NET Application and Web Services 的第 19 章。
使用 ViewState 的另一個重要之處是在控制項中發行伺服器端變更事件。如果使用者改變了文字框中的值或切換了下拉式清單中的選定元素,您就能夠註冊一個事件處理常式,引發事件時,執行代碼。這些控制項比較其當前值與以前值,如果有任何過程預訂變更事件,以前值隱式儲存在 ViewState 中。如果禁用一個控制項的 ViewState ,而您正在處理該控制項的更改通知事件,因為總是假定以前值與表單預設值相同,所以更改通知事件不會正確激發。
返回頁首
ViewState 問題
正如我早前指出的,在 ASP.NET 1.x 中, ViewState 有很多問題。預設情況下,它是啟用的,除非您知道在不需要使用時找到並禁用它,否則它能顯著增加頁面呈現的資料量。當使用資料繫結控制項時,所有控制項都使用 ViewState 儲存回傳後的狀態,這將變得異常痛苦。作為一個簡單而生動的樣本,考慮圖 2 所示的 ASP.NET 頁面。
這個頁面有一個 DataGrid 控制項,該控制項綁定對 pubs 資料庫中 authors 表格進行簡單查詢的結果。如果運行這個頁面(對連接字串做出必要改正),查看 ViewState 欄位,您可能吃驚地發現裡面有超過 12,000 個字元。當查看頁面內容,意識到顯示瀏覽器中整個表格內容所需的實際字元數量大約是 1,600,您會更加吃驚。造成這個結果的原因之一是 ViewState 不僅編碼資料,而且編碼資料類型(中繼資料);同樣,Base 64 編碼一般要增加大約 33 % 的空間系統開銷。而且,大量的系統開銷只是為了保留 POST 請求後的控制項狀態,這似乎與時間不成比例,必須竭盡全力避免發生。
很明顯,如果您關心響應大小,那麼決定何時禁用控制項的 ViewState 是重要的。剛才的樣本就是一個典型的情況,傳播了 ViewState ,但是從未使用,這是最佳化網站的 ViewState 使用時建議採用的主要規則:如果每次請求頁面時填充控制項內容,禁用該控制項的 ViewState 一般是安全(而明智)的。
另一方面,您可能決定利用 ViewState 保留控制項狀態這一實事,並且只在頁面的首次 GET 請求時,填充控制項內容(只是貫穿後續 POST 請求)。這省去了使用的任何後端資料來源的往返行程。通過修改我的頁面來利用這一結論,我更改了 OnLoad 方法,使它在填充 DataGrid 前檢查 IsPostBack 標誌,如圖 3 所示。
當然,有一些其他可能更好的選擇,例如快取服務器的查詢結果,每次發出請求時重新繫結控制項。由您權衡狀態儲存的位置和特定體繫結構與應用程式的重要性。
您可能注意到我曾小心提過,如果每次請求頁面時填充控制項內容,那麼禁用 ViewState “一般”是安全的。例外情況是,一些控制項既使用 ViewState 保留行為,也保留一般狀態。如前所述,下拉式清單和文字框控制項使用 ViewState 儲存以前的值來正確發行伺服器上的更改通知事件。同樣地,DataGrid 類使用 ViewState 發布分頁、編輯和排序事件。遺憾的是,如果您想要使用 DataGrid 中諸如排序、分頁或編輯的功能,則不能禁用其 ViewState 。對於嘗試產生快速有效網站的開發人員來說,伺服器端控制項 ViewState 的非全有即全無的狀況,是 ASP.NET 1.x 的伺服器端控制項模型另人沮喪的一面。
返回頁首
ASP.NET 2.0 中的 ViewState 改進
既然我概述了這些問題,是時候討論 ASP.NET 2.0 中的所有改進了。第一個是 ViewState 序列化時的總大小。在 ASP.NET 1.x 中,兩個字串進入 ViewState 緩衝區中的序列化如下所示:
<p<l<string1;>;l<string2;>>;>;
ASP.NET 1.x 中使用的 ViewState 序列化格式是元組格式,由三個一組的層次集合和使用大於符號和小於符號的序列對組成。大於符號之前的字母代表格儲存體對象的類型(t=triplet,p=pair,i=integer,l=ArrayList,等)。大於符號和小於符號內的每個子項目由分號分隔。這是有趣的序列化格式,有點像一個壓縮的 XML。但是,如果您關心空間,那麼它不是最有效序列化格式(只是比 XML 稍好一點)。
ASP.NET 2.0 改變了這種序列化格式。在 ASP.NET 2.0 中,相同的兩個字串進入 ViewState 緩衝區的序列化如以下程式碼所示:
[][]string1[]string2
至少,這很像瀏覽器呈現的格式。方括弧實際上是非列印字元,如果我們使用 Unicode 字元引用來重寫,則變成了以下顯示的編碼:
string1string2
ASP.NET 2.0 使用一些非列印字元標識對象的起始並表述對象的類型,而非使用可列印字元集(“<”、“>”、“;”、“l”、“i”、“p”等)來分割 ViewState 流中的對象。
使用非列印字元分割 ViewState 中儲存的對象有兩個目的。第一個目的是改進解析 ViewState 字串時進行詞法分析的效率,因為此時不再需要匹配字元或解析標記。第二個,也是更重要的目的是它減少了為 ViewState 中的對象編碼所使用的字元數量。在為兩個字串編碼的簡單樣本中,第一種編碼方法使用了 16 個分割字元,而 2.0 格式使用了 3 個。這種作用很快地複合而產生了重大的影響。
例如,如果我們在表單上放置一個 DataGrid,將其綁定到 pubs 資料庫的 authors 表格,如圖 3所示,則 1.x 中的 ViewState 字串大小是 12,648 個字元。如果在 2.0 中也這樣做, ViewState 大小減少到 6,728 個字元,幾乎減少了一半。圖 4 顯示 ASP.NET 1.x、ASP.NET 2.0 中 ViewState 所需的空間對比,以及不同輸入大小在頁面實際呈現的字元數。在這種情況下,authors 表格在 500 行處增加了額外的行,達到了 2,000 行。
圖 4 ViewState 大小對比
隨著 ASP.NET 2.0 版本的發布,您能夠期望立即減小 ViewState 大小,而無需做任何特別的工作。這不意味著您應該停止考慮控制項正常工作是否需要 ViewState ,這是因為它仍然對響應大小有很大影響,在圖 4 中表現的很明顯。然而, ViewState 的下一個改進能潛在地節省更多。
返回頁首
控制項狀態
我先前提過,ASP.NET 1.x 中使用伺服器端控制項最另人沮喪的一面是關於 ViewState 的非全有即全無的狀態。控制項的行為方面,如 DataGrid 中的分頁或文字框中的選定更改通知,需要啟用 ViewState 來正常運行。我確定在任何 DataGrid 中啟用 ViewState 的前景令所有人十分沮喪。在 ASP.NET 2.0 中,Microsoft 通過將 ViewState 分割成兩個獨立不同的類別解決了這一特殊問題: ViewState 和控制項狀態。
控制項狀態是另一類隱藏的狀態,專門為維護控制項的核心行為功能而保留,而 ViewState 只包含維護控制項內容 (UI) 的狀態。從技術上來說,控制項狀態儲存在與 ViewState 相同的隱藏欄位中(只是在 ViewState 階層末端的另一個葉子節點),但是如果禁用一個特定控制項或整個頁面的 ViewState ,控制項狀態仍然傳播。實現這一技術好的一面是,現在我們能夠改進 ASP.NET 2.0 最佳化 ViewState 的原始原則,使之更強健:如果在每次請求頁面時傳播控制項內容,您應該禁用該控制項的 ViewState 。
只要控制項產生器將其狀態正確劃分成行為狀態(為了維護核心功能)和 UI 狀態(為了保留內容),就應該堅定地遵守這個原則。我建議您現在使用 ASP.NET 2.0 時開始遵循這個原則,但是 DataGrid 還未重構以保留控制項狀態中的行為狀態。幸運的是,新的 GridView 是 DataGrid 的邏輯繼承者,它像 TextBox 和 DropDownList 控制項的新版本一樣正確使用控制項狀態。圖 5 顯示的是目前使用控制項狀態的控制項列表及其儲存的屬性,供您參考。隨著 ASP.NET 2.0 的最終發布,這個列表可能會作出變動。
如果您願意進一步研究 ASP.NET 2.0 的新控制項如何使用控制項狀態和 ViewState ,可以使用我編寫的一個叫做 View State Decoder 的工具 + 生產力,它用來顯示給定頁面上所有的 ViewState 和控制項狀態。圖 6 顯示的是活動的工具 + 生產力的一個螢幕快照。您能夠從本文頂端的連結下載該程式。注意,這個應用程式依賴於目前 ViewState 使用的編碼字元,在最終版本發布前可能會改變。
對於您的那些產生控制項,控制項狀態的使用模型不像 ViewState 一樣方便。您必須重寫 LoadControlState 和 SaveControlState 虛方法,手動管理您那部分映射到控制項狀態中的對象集合,而非提供一個帶有索引的狀態包用來插入和移除選項。另一件您必須做的事情是在初始化期間調用 Page.RegisterRequiresControlState 方法,使頁面能夠在正確的時間詢問控制項狀態。在控制項狀態中儲存選項比在 ViewState 中更難可能是一件好事,因為使用者無法禁用它(控制項狀態有一個選擇模型也有效能方面的好處)。開發人員在儲存任何狀態前應該認真考慮,因為它總是添加到呈現的頁面中。圖 7 顯示在控制項狀態中儲存一個字串的自訂控制項(本例中是它的顏色)。
當您決定控制項狀態和 ViewState 各儲存什麼內容時,請記住行為狀態與內容或者 UI 狀態應該有分別。諸如屬性和資料集一樣的內容一般應儲存在 ViewState 中,而不是遷移到控制項狀態。觸發伺服器端事件的狀態是儲存在控制項狀態中最典型的一類狀態。
返回頁首
聲明性資料來源和 ViewState
應用我們的 ViewState 最佳化原則有一個小問題 — 您經常不知道每次請求時是否正在填充控制項內容。在 ASP.NET 2.0 中採用聲明性資料來源意味著將資料繫結到一個控制項不再需要將資料來源屬性顯式串連至 DataReader 或 DataSet,並調用 DataBind。而是在頁面上聲明性地放置一個資料來源,並將控制項指向該資料來源。例如,使用綁定到 pubs 資料庫中 authors 表格的新 GridView 類和與之相關的 SqlDataSource(參見 圖 8)。如果運行這個頁面,您將發現它正好有效。GridView 及其相應的資料來源能夠瞭解何時進行互動。在頁面呈現之前,GridView 充滿資料來源的資料,並且很可能在用戶端轉譯整個 authors 表格。
在這個簡單的情況中,我們還未禁用 GridView 的 ViewState ,因此您可能會認為,我們再一次僅僅使用 ViewState 儲存了從未使用的資料。幸運的是,ASP.NET 2.0 引擎作了正確的事情,當控制項上的 ViewState 可用時,它將不厭其煩地返回到資料庫。
同樣地,如果禁用 GridView 上的 ViewState ,資料繫結到資料來源將發生在每次請求發生的時候,包括 POST back 請求。DataBoundControl 基類中置入了這一功能,其中的 AdRotator、BulletedList、CheckBoxList、DropDownList、ListBox、RadioButtonList、GridView、DetailsView 和 FormView 控制項繼承了該功能。這些控制項展示了 ViewState 在綁定到聲明性資料來源時表現的智能使用。
返回頁首
小結
ASP.NET 的下一個版本對 Web 開發人員承諾了許多改進,不只是更有可能晚上好好休息,不會做 Base 64 編碼資料淹沒了 Web Form的噩夢。使用更緊湊的序列化格式、行為狀態和 UI 狀態的分離以及資料繫結控制項和聲明性資料來源的智能互動,那些 ViewState 的噩夢僅可能變成美夢。