ViewState是.Net中提出的狀态儲存的一種新途徑(實際上也是老瓶裝新酒);我們知道,傳統的Web程式儲存狀态的方式有這樣幾種:
1、Application 這是Web應用程式生命期中的全局儲存區,儲存在Application中的資料是全局有效的;在Asp.Net中,有一個應用程式池,其中儲存了數個 (或數十個)應用程式執行個體,每一次請求都會從池中取一個執行個體來處理請求,在請求完畢之前,這個執行個體不會接受其他請求;這就出現一個問題,同一時間可能存在 多個應用程式,也就是多個線程,這些線程都存在通路Application的可能,是以在對Application中的對象進行處理的時候需要考慮線程同 步的問題;實際上Application對象内部實作了一個線程鎖,調用它本身的Add、Remove等方法的時候會自動調用加鎖和解鎖的操作,但是出于 性能考慮,對于直接通過索引器或其他方式得到其中的對象并進行操作的過程,Application并沒有自動處理線程同步,需要利用下列類似的代碼來處 理:
Application.Lock();
((int)Application["Count"])++;
Application.Unlock();
值得注意的是,調用了Lock之後,如果沒有顯示的調用Unlock,那麼在這個請求結束的時候,Application對象會自動解鎖,這樣防止了造成死鎖的問題,但是為了代碼的健壯性,調用完Lock并且修改完畢應該立即的調用Unlock方法。
Application 對象本質上就是一個Hash表,按照鍵值存放了對象,由于對象是全局并且存放在伺服器,并且存在多線程同時通路,是以,Application裡面存放的 應該是通路較多,修改較少并且是全局至少大部分功能會使用的資料,例如計數器或者資料庫連接配接串等。
2、Session 在Asp.Net内部,有一個StateApplication來管理Session,實際上就是一個輔助程序,處理Session到期、建立的特殊請 求,在收到每一次請求的時候,輔助程序就會調用狀态伺服器(可以通過Web.config設定不同的狀态伺服器)來擷取Session,如果沒有對應該 SessionId的Session,則會建立一個,然後綁定到上下文中(HttpContext);與Asp不同的是,Session的狀态伺服器有多 種,目前在Asp.Net内部實作了三種:
1) InProcStateClientManager 這是傳統的Session儲存方式,但是還是有些細微差别
2) SqlStateClientManager 這是将Session儲存到資料庫方式
3) OutOfProcStateClientManager 這是将Session儲存到程序外的方式
Asp.Net的Session機制有一個特點,就是處理Session的輔助程序與儲存Session的狀态伺服器是分開的,按照MSDN的說法,有下列好處:
“因為用于會話狀态的記憶體不在 ASP.NET 輔助程序中,是以可以實作從應用程式故障的恢複。”
“因為所有狀态與輔助程序不存儲在一起,您可以幹淨地跨多個程序對應用程式進行分區。這種分區可以顯著地提高多個程序的計算機上應用程式的可用性和可縮放性。”
“因為所有狀态與輔助程序不存儲在一起,是以您可以跨運作于多個計算機上的多個輔助程序對應用程式進行分區。”
Asp.Net的Session機制個人觀點,感覺靈活性比較好,内部實作也比較巧妙,但是實際上因為沒有做過多的測試,是以應用上會不會像它說的那麼美好,不敢打包票。有機會,我會單獨寫篇文章來深入的探讨Asp.Net 内部的Session機制。
3、Cookie 這個沒甚麼好說,實際上Asp.Net與Asp的Cookie沒甚麼分别,也許這項技術毀譽參半,而且比較依賴客戶機實作,MS也沒什麼改進的。
4、ViewState 這是我們今天重點讨論的;實際上ViewState并不神秘,就是一個Hidden字段,但是它是伺服器控件狀态儲存的基礎;不熟悉的朋友可以用IE檢視 Html源碼,找到一個名為"__VIEWSTATE"的Hidden字段,其中有一大堆亂七八糟的字元,這就是頁面的ViewState。
做過Web程式的人可能都有這種痛苦的體會,有時候為了處理頁面上面比較複雜的功能,常常會加很多Hidden,然後在伺服器端用一大堆判斷來分析目前的 狀态,寫起來煩人,寫完了代碼更是難看;實際上,ViewState就是幫我們系統的實作了儲存控件狀态的功能,伺服器端控件能夠在多次請求間儲存狀态也 全靠它。
好,介紹就到這裡,今天我們不是讨論ViewState的使用,而是從内部來探探這個東西的本質。
我們首先建一個測試的頁面:
<%@ Page language="c#" Codebehind="ViewStateTest.aspx.cs" AutoEventWireup="false" Inherits="CsdnTest.ViewStateTest" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>ViewStateTest</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body>
<form id="ViewStateTest" method="post" runat="server">
<asp:Button ID="btnPostBack" Runat="server" Text="Post Back" Width="85px"></asp:Button>
<br/>
<asp:CheckBox ID="chkTest" Runat="server" Text="This is a check box"></asp:CheckBox>
</form>
</body>
</html>
這是用Vs.Net設計出來的一個簡單的頁面,裡面包含了一個伺服器端的按鈕和一個CheckBox,然後我們在伺服器端響應按鈕的事件:
private void btnPostBack_Click(object sender, System.EventArgs e)
{
[1] Response.Write( "ViewState :"+Request.Params["__VIEWSTATE"]+"<br/>" );
[2] string decodeValue = Encoding.UTF8.GetString( Convert.FromBase64String( Request.Params["__VIEWSTATE"] ) );
[3] Response.Write( "ViewState decode :"+decodeValue+"<br/>" );
[4] object viewstate = (new LosFormatter()).Deserialize( Request.Params["__VIEWSTATE"] );
[5] Response.Write( "ViewState Object :"+viewstate.GetType().Name );
}
為了友善看,我加上了行号;第一行我們把ViewState的值打出來,第二行是什麼呢?實際上ViewState儲存到用戶端的一串字元串就是内部的 ViewState通過某種方式序列化之後再經過Base64編碼得來的,是以我們把Base64編碼的字元串反編碼一次再打出來;至于第四行,我先不 說,先看執行結果:
運作之後,頁面上什麼都沒有,除了按鈕和CheckBox(廢話 :)),我們點選按鈕,然後結果如下:
[A] ViewState :dDwxMjU2MDI5MTA3OztsPGNoa1Rlc3Q7Pj6Gg0Qzm+7gacYWcy0hnRCT9toOdA==
[B] ViewState decode:t<1256029107;;l>D3i s-! t
[C] ViewState Object :Triplet
然後我們來分析這個結果,A中顯示的就是ViewState傳到用戶端的值,B中顯示的是通過Base64反編碼之後的值,從這裡面好像還是看不出什麼,C中出現了一個:Triplet ?這是什麼呢,我們回到上面的代碼:
object viewstate = (new LosFormatter()).Deserialize( Request.Params["__VIEWSTATE"] );
注意我們使用了一個LosFormatter類,實際上這個類就是Asp.Net内部為ViewState提供序列化的類,它有兩個方法,一個是 Serialize,就是序列化一個對象,一個是Deserialize,是反序列化,我們這裡使用了反序列化的方法來把ViewState直接反序列化 成一個對象,然後把這個對象的類型打出來,這個對象就是:Triplet類型,實際上Asp.Net中頁面儲存的ViewState就是這個類型,我們先 分析一下LosFormater,再來細說.
我們再回來看打出來的結果B:t<1256029107;;l>D3i s-!
t, 實際上通過檢視LosFormatter反編譯後的代碼,大緻上可以看出它序列化的方式是很簡單的,就是判斷要序列化對象的類型,如果不是直接序列化的類 型,則把它的類型記錄下來,然後在遞歸序列化它的屬性,我們看B中的"t"就是表示Triplet這個類型,這個類型有三個屬性,這三個屬性包含在 "<"和">"之間,用";"分割,而最後面的D3i s-!
t據我分析應該是一個防止ViewState被改變的Hash值,這個不是很确定,因為反編譯的代碼實在是很難看,我隻是了解之後就沒仔細看了。
我們剛剛分析出來Page中的ViewState反序列化之後是Triplet這個類型,實際上這個類在MSDN中就查得到,它就是一個包含了三個對象的 對象,說簡單點,它就是一個能放三個箱子的大箱子(好像還是說的比較糊塗,呵呵),它有三個屬性:First、Second、Thrid :),分别代表三個對象。
對應到Page當中,First是Page.GetTypeHashCode()的傳回值,這個方法是System.Web.UI.Page定義的一個保 護的虛拟方法,傳回一個整型,由Aspx檔案生成的類來實作的,因為這個類是有Asp.Net負責在運作期生成源代碼并編譯,它會計算出一個大常量作為返 回值,這個傳回值在整個Web應用程式所有的Page中是唯一的。(提一句題外話,Asp.Net自動産生的源代碼可以到 系統盤:/WINDOWS/Microsoft.NET/Framework/v1.0.3705/Temporary ASP.NET Files下面去找),這個唯一的Hash值是為了在ViewState中産生一個标記,使這個ViewState隻适用與對應的頁面。
Second則是通Control.SaveViewStateRecursive方法遞歸儲存頁面控件樹的ViewState傳回的對象,也就是真正的ViewState的資料。
Third中儲存的是目前頁面需要PostBack的控件名的清單。
分析了頁面的ViewState的構成,我們再來看Control的ViewState的實作。ViewState是 System.Web.UI.Control類實作的一個屬性,這個屬性的類型是System.Web.UI.StateBag,這個類就包含了 ViewState資料結構的實作,實際上它的内部也就是個Hash表,通過Key值來儲存和檢索資料。
那麼伺服器控件是怎麼實作儲存狀态的呢?
我們知道,所有的伺服器控件都是從System.Web.UI.Control派生的,是以都擁有ViewState這個屬性,在Control内部,定義了兩個Protected的虛拟方法:
protected virtual object SaveViewState()
和
protected virtual void LoadViewState(object savedState)
這兩個方法是給子控件派生用來儲存和讀取自己的ViewState的,比如我們有一個自己寫的控件,往ViewState中儲存了一個字元串,那麼我們的方法大緻像這樣:
protected virtual object SaveViewState()
{
object[] states = new object[2];
states[0] = base.SaveViewState(); //記得儲存父控件的ViewState
states[1] = "Hello,I'm timmy!"; //這裡儲存我們自己的
return states; //傳回重新包裝後的儲存對象
}
擷取的時候:
protected override void LoadViewState(object savedState) //這裡的savedState就是我們Save的時候return 的object數組
{
object[] states = (object[])savedState;
base.LoadViewState( states[0] ); //把父類的資料給他自己去解析
string myData = (string)states[1]; //擷取我們自己的資料
}
我們可以按照自己的方式來儲存,不一定非要像上面這樣用數組,實際上我們可以用任何支援序列化的對象都可以,父類并不關心子類如何儲存,我們隻要在Save和Load的時候使用同樣的方式,并且把正确的資料傳遞給父類方法就可以了。
另外,還有一個問題就是我們使用的Control的ViewState是Key-Value這樣的鍵值對,那它是怎麼儲存的呢?
實際上很簡單,System.UI.Web下面有一個類叫Pair,呵呵,這個和Triplet差不多,隻是它裡面隻有兩個對象。StateBag儲存的時候,First會存放所有Key值的數組,Second則存放所有Value的數組。
到現在,我們了解了ViewState是如何序列化并且儲存到用戶端,也了解了控件怎麼儲存自己的ViewState,那麼這二者是怎麼結合的呢?
也就是整個頁面的控件樹的ViewState是怎麼儲存和讀取的呢?
在Control内部有兩個internal的方法:
internal object SaveViewStateRecursive();
internal void LoadRecursive();
這兩個方法由System.Web.UI.Page來調用,Page在Render結束後就會調用SavePageViewState方法, SavePageViewState方法會調用Control的SaveViewStateRecursive()方法,這個方法就是通過遞歸調用每一個 Control.Controls的SaveViewStateRecursive方法來儲存控件樹中所有控件的ViewState。到這裡,可能聰明的 朋友要問了,既然SaveViewStateRecursive是遞歸調用儲存的方法,那麼我們上面寫的SaveViewState()方法又有什麼用 呢?
我們知道,Control.Controls可能會有很多個,而且我們的SaveViewState()隻儲存了目前控件的資料,而沒有記錄控件樹的結 構,那麼如果我們遞歸SaveViewState()方法來儲存資料的話,那麼控件樹的結構就會丢失,那麼Load的時候就沒辦法還原了,實際上在 SaveViewStateRecursive方法中大緻的代碼是這樣:
[1] 擷取控件自己的ViewState(調用SaveViewState方法)
[2] 循環子控件
{
定義兩個動态數組,一個儲存控件的索引,一個儲存遞歸調用子控件SaveViewStateRecursive方法傳回的值
}
[3] 定義一個Triplet(呵呵,這個東西又出現了)
[4] First儲存本控件的ViewState
[5] Second儲存子控件的索引
[6] Third儲存遞歸子控件SaveViewStateRecursive方法的傳回值
[7] 傳回Triplet
這樣就儲存了整個控件樹的ViewState和控件樹的結構
Load的方式與Save差不多,隻是Load的時候會從savedState中擷取子控件的索引來依次遞歸子控件的LoadRecursive()方法,這樣才能保證正确的把儲存的資料傳給子控件。
到這裡,ViewState的實作我們大緻了解了一下,最後得出一些結論:
1、ViewState是存放在用戶端,是以會減輕伺服器的負擔,是一種比較好的儲存資料的方式。
2、因為ViewState本身的限制,隻能儲存可以序列化的對象,而且最好不要放太多東西,能省則省,以免在減慢傳輸的速度,以及加重伺服器解析的負擔。
3、我們通過很簡單的方式就可以把ViewState裡面的值擷取出來,我們上面讨論了一些,雖然沒有把解析的代碼寫出來,但是利用 LosFormatter可以得到ViewState反序列化後的對象,那麼要解析出來簡直是易如反掌;是以ViewState在安全性上面還是比較差, 建議不要
存放比較機密和敏感的資訊,盡管ViewState可以加密,但是由于ViewState要儲存在用戶端,天生就有安全性的隐患。
4、實際從技術角度,ViewState沒有任何新意,但是結合伺服器控件的設計還是很巧妙的。
最後,以我個人的觀點,我覺得ViewState的出現很大程度上減輕了程式員的負擔,但是要看清的是ViewState的本質,合理的應用它。
匆忙寫就難免有很多問題,還希望大家多提意見,不足之處請多指教!
轉自:http://blog.csdn.net/timmy3310/archive/2003/03/31/12612.aspx