首先,我不確定是不是只有ASP.NET由ViewState,也不確認它有多特有,只是覺得這個東西對於Web開發MVC分離的進步很有協助。
所謂的ViewState,就是用來存放關於View的State的地方。以前的儲存容器包括Cookies, Session, Application, Cache, Hidden,有時候連傳遞變數用的QueryString也用作儲存容器,但都不是專門用來儲存View相關資訊的地方,然而由於沒有專門存放View相關資訊的地方,所以人們只好亂放。不怕到期失效的變數,多數人會選擇放在Session裡,而且跨頁面不會丟失,使用者訪問幾個別的頁面回來還能通過Session恢複本頁的View。如果需要延長一些時間,而資料又不是很多的話,可以放Cookies,和Session類似。而資料真的很短,而且頁面總是提交給自己的情況下,用QueryString作為一些跨頁面生命週期的變數的儲存方式也可以。而如果頁面可以只用Post不用Get傳遞的話,那麼Hidden也是一個很好的選擇,因為Hidden容量大,不在地址留下資訊。在ASP.NET當中,就是設計到大多數情況都是Post(除了直接連結),所以用Hidden存放和View相關的資訊是非常適合的。
既然用Hidden存放就可以了,為什麼需要ViewState這樣的統一管理呢?最顯然的理由就是加密。ViewState不是給使用者看或者修改的資訊,僅僅是因為View的狀態會在頁面生命週期之間丟失,所以我們要將這些資訊輸出到HTML再等Post的時候取回來,對ViewState加密(至少校正)能夠確保View正確無誤的恢複。ASP.NET內建的ViewState可以方便的設定校正和加密,只要你把可以序列化的對象存進去,它就能夠自動序列化並輸出到HTML,同時該對象的儲存與恢複是跟控制項名以及在控制項樹中的位置相關的,就如ASP.NET控制項的其他特性一樣,確保了樹中不同位置的同ID控制項的資料不會被混淆。簡而言之,ViewState是儲存跨頁面生命週期有關變數的最好容器,但它又不能夠跨出頁面範圍稱為會話變數的容器(那應該是Session負責的哦),所以是真正符合其名稱用來儲存View的State的。
有很多ASP.NET的新手不知道ViewState的用途,認為它是ASP.NET的內部對象,平時還是僅用ASP那套公開的對象好了,那就會帶來很多麻煩。例如有GridA和GridB兩個頁面,點擊一個條目查看明細都迴轉到Details頁面,同時Details頁面要提供返回原來頁面的途徑(包括原本GridView所瀏覽到的分頁)。如何儲存原本頁面的狀態呢,包括它來自GridA還是GridB以及原來的GridView所在的分頁?有人選擇用QueryString傳遞給Details頁面,讓Details頁面構造返回連結時再通過QueryString把狀態傳回去。顯然,原來頁面的狀態不是對Details的Query(查詢),那麼用QueryString傳遞給Details頁面是不合適的。也有人選擇用Session傳遞,但是這個狀態僅僅做一次傳遞,難道我對你說一句話也算是Session(會話)?當然不算。用Hidden是一個方法,但是基於我上面所說的ViewState對Hidden的改良,在這種情況下就應該用ViewState作為原本GridA或GirdB的View狀態的儲存方式,並且在經過Details頁面返回之後再還原。需要說明的是,只有ASP.NET 2.0才在內部支援跨頁面PostBack,ASP.NET 1.x不支援跨頁面PostBack,也無法跨頁面接受和保持ViewState。
最後就是使用ViewState需要注意的地方。過量使用ViewState當然有損效率,但這對於網站的資料正確性來說還不至於造成漏洞,真正會造成漏洞的就是ViewState重名。上面不是說了ViewState是根據控制項樹位置和控制項ID儲存所以不會重名的嗎?在樹是靜態情況下確實是這樣,但是如果樹是動態建立的,而ID則是可能在不同的語義下重複使用的,那就可能破壞資料的正確性了。例如一個DataControl的子控制項自動命名為Control_1, Control_2, Control_3等,如果在PostBack後該DataControl被再次DataBind,那麼DataSource可能已經改變了,所以需要清除所有的子控制項並重新根據DataSource建立它們,此時別忘記了調用ClearChildState方法把子控制項的ViewState和ControlState都清除掉,否則當你重新建立子控制項而它們的名稱還是Control_1, Control_2, Control_3等的時候,就可能會把原來的ViewState載入回去。