ASP.Net ViewState的實(shí)現 收藏
選擇自 timmy3310 的 Blog
ViewState是.Net中提出的狀態(tài)保存的一種新途徑(實(shí)際上也是老瓶裝新酒);我們知道,傳統的Web程序保存狀態(tài)的方式有這樣幾種:
1、Application 這是Web應用程序生命期中的全局保存區,保存在A(yíng)pplication中的數據是全局有效的;在A(yíng)sp.Net中,有一個(gè)應用程序池,其中保存了數個(gè)(或數十個(gè))應用程序實(shí)例,每一次請求都會(huì )從池中取一個(gè)實(shí)例來(lái)處理請求,在請求完畢之前,這個(gè)實(shí)例不會(huì )接受其他請求;這就出現一個(gè)問(wèn)題,同一時(shí)間可能存在多個(gè)應用程序,也就是多個(gè)線(xiàn)程,這些線(xiàn)程都存在訪(fǎng)問(wèn)Application的可能,所以在對Application中的對象進(jìn)行處理的時(shí)候需要考慮線(xiàn)程同步的問(wèn)題;實(shí)際上Application對象內部實(shí)現了一個(gè)線(xiàn)程鎖,調用它本身的Add、Remove等方法的時(shí)候會(huì )自動(dòng)調用加鎖和解鎖的操作,但是出于性能考慮,對于直接通過(guò)索引器或其他方式得到其中的對象并進(jìn)行操作的過(guò)程,Application并沒(méi)有自動(dòng)處理線(xiàn)程同步,需要利用下列類(lèi)似的代碼來(lái)處理:
Application.Lock();
((int)Application["Count"])++;
Application.Unlock();
值得注意的是,調用了Lock之后,如果沒(méi)有顯示的調用Unlock,那么在這個(gè)請求結束的時(shí)候,Application對象會(huì )自動(dòng)解鎖,這樣防止了造成死鎖的問(wèn)題,但是為了代碼的健壯性,調用完Lock并且修改完畢應該立即的調用Unlock方法。
Application對象本質(zhì)上就是一個(gè)Hash表,按照鍵值存放了對象,由于對象是全局并且存放在服務(wù)器,并且存在多線(xiàn)程同時(shí)訪(fǎng)問(wèn),所以,Application里面存放的應該是訪(fǎng)問(wèn)較多,修改較少并且是全局至少大部分功能會(huì )使用的數據,例如計數器或者數據庫連接串等。
2、Session 在A(yíng)sp.Net內部,有一個(gè)StateApplication來(lái)管理Session,實(shí)際上就是一個(gè)輔助進(jìn)程,處理Session到期、創(chuàng )建的特殊請求,在收到每一次請求的時(shí)候,輔助進(jìn)程就會(huì )調用狀態(tài)服務(wù)器(可以通過(guò)Web.config設置不同的狀態(tài)服務(wù)器)來(lái)獲取Session,如果沒(méi)有對應該SessionId的Session,則會(huì )新建一個(gè),然后綁定到上下文中(HttpContext);與Asp不同的是,Session的狀態(tài)服務(wù)器有多種,目前在A(yíng)sp.Net內部實(shí)現了三種:
1) InProcStateClientManager 這是傳統的Session保存方式,但是還是有些細微差別
2) SqlStateClientManager 這是將Session保存到數據庫方式
3) OutOfProcStateClientManager 這是將Session保存到進(jìn)程外的方式
Asp.Net的Session機制有一個(gè)特點(diǎn),就是處理Session的輔助進(jìn)程與保存Session的狀態(tài)服務(wù)器是分開(kāi)的,按照MSDN的說(shuō)法,有下列好處:
“因為用于會(huì )話(huà)狀態(tài)的內存不在 ASP.NET 輔助進(jìn)程中,所以可以實(shí)現從應用程序故障的恢復。”
“因為所有狀態(tài)與輔助進(jìn)程不存儲在一起,您可以干凈地跨多個(gè)進(jìn)程對應用程序進(jìn)行分區。這種分區可以顯著(zhù)地提高多個(gè)進(jìn)程的計算機上應用程序的可用性和可縮放性。”
“因為所有狀態(tài)與輔助進(jìn)程不存儲在一起,所以您可以跨運行于多個(gè)計算機上的多個(gè)輔助進(jìn)程對應用程序進(jìn)行分區。”
Asp.Net的Session機制個(gè)人觀(guān)點(diǎn),感覺(jué)靈活性比較好,內部實(shí)現也比較巧妙,但是實(shí)際上因為沒(méi)有做過(guò)多的測試,所以應用上會(huì )不會(huì )像它說(shuō)的那么美好,不敢打包票。有機會(huì ),我會(huì )單獨寫(xiě)篇文章來(lái)深入的探討Asp.Net 內部的Session機制。
3、Cookie 這個(gè)沒(méi)甚么好說(shuō),實(shí)際上Asp.Net與Asp的Cookie沒(méi)甚么分別,也許這項技術(shù)毀譽(yù)參半,而且比較依賴(lài)客戶(hù)機實(shí)現,MS也沒(méi)什么改進(jìn)的。
4、ViewState 這是我們今天重點(diǎn)討論的;實(shí)際上ViewState并不神秘,就是一個(gè)Hidden字段,但是它是服務(wù)器控件狀態(tài)保存的基礎;不熟悉的朋友可以用IE查看Html源碼,找到一個(gè)名為"__VIEWSTATE"的Hidden字段,其中有一大堆亂七八糟的字符,這就是頁(yè)面的ViewState。
這是用Vs.Net設計出來(lái)的一個(gè)簡(jiǎn)單的頁(yè)面,里面包含了一個(gè)服務(wù)器端的按鈕和一個(gè)CheckBox,然后我們在服務(wù)器端響應按鈕的事件:
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的值打出來(lái),第二行是什么呢?實(shí)際上ViewState保存到客戶(hù)端的一串字符串就是內部的ViewState通過(guò)某種方式序列化之后再經(jīng)過(guò)Base64編碼得來(lái)的,所以我們把Base64編碼的字符串反編碼一次再打出來(lái);至于第四行,我先不說(shuō),先看執行結果:
運行之后,頁(yè)面上什么都沒(méi)有,除了按鈕和CheckBox(廢話(huà) :)),我們點(diǎn)擊按鈕,然后結果如下:
[A] ViewState :dDwxMjU2MDI5MTA3OztsPGNoa1Rlc3Q7Pj6Gg0Qzm+7gacYWcy0hnRCT9toOdA==
[B] ViewState decode:t<1256029107;;l>D3i s-! t
[C] ViewState Object :Triplet
然后我們來(lái)分析這個(gè)結果,A中顯示的就是ViewState傳到客戶(hù)端的值,B中顯示的是通過(guò)Base64反編碼之后的值,從這里面好像還是看不出什么,C中出現了一個(gè):Triplet ?這是什么呢,我們回到上面的代碼:
object viewstate = (new LosFormatter()).Deserialize( Request.Params["__VIEWSTATE"] );
注意我們使用了一個(gè)LosFormatter類(lèi),實(shí)際上這個(gè)類(lèi)就是Asp.Net內部為ViewState提供序列化的類(lèi),它有兩個(gè)方法,一個(gè)是Serialize,就是序列化一個(gè)對象,一個(gè)是Deserialize,是反序列化,我們這里使用了反序列化的方法來(lái)把ViewState直接反序列化成一個(gè)對象,然后把這個(gè)對象的類(lèi)型打出來(lái),這個(gè)對象就是:Triplet類(lèi)型,實(shí)際上Asp.Net中頁(yè)面保存的ViewState就是這個(gè)類(lèi)型,我們先分析一下LosFormater,再來(lái)細說(shuō).
我們再回來(lái)看打出來(lái)的結果B:t<1256029107;;l>D3i s-!
t,實(shí)際上通過(guò)查看LosFormatter反編譯后的代碼,大致上可以看出它序列化的方式是很簡(jiǎn)單的,就是判斷要序列化對象的類(lèi)型,如果不是直接序列化的類(lèi)型,則把它的類(lèi)型記錄下來(lái),然后在遞歸序列化它的屬性,我們看B中的"t"就是表示Triplet這個(gè)類(lèi)型,這個(gè)類(lèi)型有三個(gè)屬性,這三個(gè)屬性包含在"<"和">"之間,用";"分割,而最后面的D3i s-!
t據我分析應該是一個(gè)防止ViewState被改變的Hash值,這個(gè)不是很確定,因為反編譯的代碼實(shí)在是很難看,我只是了解之后就沒(méi)仔細看了。
我們剛剛分析出來(lái)Page中的ViewState反序列化之后是Triplet這個(gè)類(lèi)型,實(shí)際上這個(gè)類(lèi)在MSDN中就查得到,它就是一個(gè)包含了三個(gè)對象的對象,說(shuō)簡(jiǎn)單點(diǎn),它就是一個(gè)能放三個(gè)箱子的大箱子(好像還是說(shuō)的比較糊涂,呵呵),它有三個(gè)屬性:First、Second、Thrid :),分別代表三個(gè)對象。
對應到Page當中,First是Page.GetTypeHashCode()的返回值,這個(gè)方法是System.Web.UI.Page定義的一個(gè)保護的虛擬方法,返回一個(gè)整型,由Aspx文件生成的類(lèi)來(lái)實(shí)現的,因為這個(gè)類(lèi)是有Asp.Net負責在運行期生成源代碼并編譯,它會(huì )計算出一個(gè)大常量作為返回值,這個(gè)返回值在整個(gè)Web應用程序所有的Page中是唯一的。(提一句題外話(huà),Asp.Net自動(dòng)產(chǎn)生的源代碼可以到 系統盤(pán):\WINDOWS\Microsoft.NET\Framework\v1.0.3705\Temporary ASP.NET Files下面去找),這個(gè)唯一的Hash值是為了在ViewState中產(chǎn)生一個(gè)標記,使這個(gè)ViewState只適用與對應的頁(yè)面。
Second則是通Control.SaveViewStateRecursive方法遞歸保存頁(yè)面控件樹(shù)的ViewState返回的對象,也就是真正的ViewState的數據。
Third中保存的是當前頁(yè)面需要PostBack的控件名的列表。
分析了頁(yè)面的ViewState的構成,我們再來(lái)看Control的ViewState的實(shí)現。ViewState是System.Web.UI.Control類(lèi)實(shí)現的一個(gè)屬性,這個(gè)屬性的類(lèi)型是System.Web.UI.StateBag,這個(gè)類(lèi)就包含了ViewState數據結構的實(shí)現,實(shí)際上它的內部也就是個(gè)Hash表,通過(guò)Key值來(lái)保存和檢索數據。
那么服務(wù)器控件是怎么實(shí)現保存狀態(tài)的呢?
我們知道,所有的服務(wù)器控件都是從System.Web.UI.Control派生的,所以都擁有ViewState這個(gè)屬性,在Control內部,定義了兩個(gè)Protected的虛擬方法:
protected virtual object SaveViewState()
和
protected virtual void LoadViewState(object savedState)
這兩個(gè)方法是給子控件派生用來(lái)保存和讀取自己的ViewState的,比如我們有一個(gè)自己寫(xiě)的控件,往ViewState中保存了一個(gè)字符串,那么我們的方法大致像這樣:
protected virtual object SaveViewState()
{
object[] states = new object[2];
states[0] = base.SaveViewState(); //記得保存父控件的ViewState
states[1] = "Hello,I'm timmy!"; //這里保存我們自己的
return states; //返回重新包裝后的保存對象
}
獲取的時(shí)候:
protected override void LoadViewState(object savedState) //這里的savedState就是我們Save的時(shí)候return 的object數組
{
object[] states = (object[])savedState;
base.LoadViewState( states[0] ); //把父類(lèi)的數據給他自己去解析
string myData = (string)states[1]; //獲取我們自己的數據
}
我們可以按照自己的方式來(lái)保存,不一定非要像上面這樣用數組,實(shí)際上我們可以用任何支持序列化的對象都可以,父類(lèi)并不關(guān)心子類(lèi)如何保存,我們只要在Save和Load的時(shí)候使用同樣的方式,并且把正確的數據傳遞給父類(lèi)方法就可以了。
另外,還有一個(gè)問(wèn)題就是我們使用的Control的ViewState是Key-Value這樣的鍵值對,那它是怎么保存的呢?
實(shí)際上很簡(jiǎn)單,System.UI.Web下面有一個(gè)類(lèi)叫Pair,呵呵,這個(gè)和Triplet差不多,只是它里面只有兩個(gè)對象。StateBag保存的時(shí)候,First會(huì )存放所有Key值的數組,Second則存放所有Value的數組。
到現在,我們了解了ViewState是如何序列化并且保存到客戶(hù)端,也了解了控件怎么保存自己的ViewState,那么這二者是怎么結合的呢?
也就是整個(gè)頁(yè)面的控件樹(shù)的ViewState是怎么保存和讀取的呢?
在Control內部有兩個(gè)internal的方法:
internal object SaveViewStateRecursive();
internal void LoadRecursive();
這兩個(gè)方法由System.Web.UI.Page來(lái)調用,Page在Render結束后就會(huì )調用SavePageViewState方法,SavePageViewState方法會(huì )調用Control的SaveViewStateRecursive()方法,這個(gè)方法就是通過(guò)遞歸調用每一個(gè)Control.Controls的SaveViewStateRecursive方法來(lái)保存控件樹(shù)中所有控件的ViewState。到這里,可能聰明的朋友要問(wèn)了,既然SaveViewStateRecursive是遞歸調用保存的方法,那么我們上面寫(xiě)的SaveViewState()方法又有什么用呢?
我們知道,Control.Controls可能會(huì )有很多個(gè),而且我們的SaveViewState()只保存了當前控件的數據,而沒(méi)有記錄控件樹(shù)的結構,那么如果我們遞歸SaveViewState()方法來(lái)保存數據的話(huà),那么控件樹(shù)的結構就會(huì )丟失,那么Load的時(shí)候就沒(méi)辦法還原了,實(shí)際上在SaveViewStateRecursive方法中大致的代碼是這樣:
[1] 獲取控件自己的ViewState(調用SaveViewState方法)
[2] 循環(huán)子控件
{
定義兩個(gè)動(dòng)態(tài)數組,一個(gè)保存控件的索引,一個(gè)保存遞歸調用子控件SaveViewStateRecursive方法返回的值
}
[3] 定義一個(gè)Triplet(呵呵,這個(gè)東西又出現了)
[4] First保存本控件的ViewState
[5] Second保存子控件的索引
[6] Third保存遞歸子控件SaveViewStateRecursive方法的返回值
[7] 返回Triplet
這樣就保存了整個(gè)控件樹(shù)的ViewState和控件樹(shù)的結構
Load的方式與Save差不多,只是Load的時(shí)候會(huì )從savedState中獲取子控件的索引來(lái)依次遞歸子控件的LoadRecursive()方法,這樣才能保證正確的把保存的數據傳給子控件。
到這里,ViewState的實(shí)現我們大致了解了一下,最后得出一些結論:
1、ViewState是存放在客戶(hù)端,因此會(huì )減輕服務(wù)器的負擔,是一種比較好的保存數據的方式。
2、因為ViewState本身的限制,只能保存可以序列化的對象,而且最好不要放太多東西,能省則省,以免在減慢傳輸的速度,以及加重服務(wù)器解析的負擔。
3、我們通過(guò)很簡(jiǎn)單的方式就可以把ViewState里面的值獲取出來(lái),我們上面討論了一些,雖然沒(méi)有把解析的代碼寫(xiě)出來(lái),但是利用LosFormatter可以得到ViewState反序列化后的對象,那么要解析出來(lái)簡(jiǎn)直是易如反掌;所以ViewState在安全性上面還是比較差,建議不要
存放比較機密和敏感的信息,盡管ViewState可以加密,但是由于ViewState要保存在客戶(hù)端,天生就有安全性的隱患。
4、實(shí)際從技術(shù)角度,ViewState沒(méi)有任何新意,但是結合服務(wù)器控件的設計還是很巧妙的。
最后,以我個(gè)人的觀(guān)點(diǎn),我覺(jué)得ViewState的出現很大程度上減輕了程序員的負擔,但是要看清的是ViewState的本質(zhì),合理的應用它。
匆忙寫(xiě)就難免有很多問(wèn)題,還希望大家多提意見(jiàn),不足之處請多指教