深入理解 ViewState

控件 ViewState 屬性的值保存去哪裏了

看過MSDN的都知道,存取ViewState有兩種方法:

  • 直接操作控件的ViewState屬性,通過this.ViewState[key]就可以直接進行讀寫。
  • 重寫控件的LoadViewState和SaveViewState方法。在LoadViewState中系統會將此控件以ViewState保存的信息作爲一個object類型參數傳入,控件需要自己將信息unboxing出來。在SaveViewState中,控件需要自己將想通過ViewState保存的信息boxing到一個object裏面,然後return給系統。

那麼到底這兩種方法讀寫ViewState有什麼不同呢?

使用Reflector看看Control的LoadViewState與SaveViewState方法你就會發現,其實控件的ViewState屬性也就是一個特殊的控件屬性,類型爲StateBag,由於Control已經爲你寫好了將StateBag存取到真正的ViewState的方法,所以只要你繼承Control控件你就可以放心地把值存到StateBag裏面去,而這些值最終會保存到真正的ViewState中。

就這麼簡單?還差一點,就是StateBag這個字典的每一個項目類型爲StateItem,而StateItem有一個IsDirty的屬性。只有這個屬性爲true的StateItem纔會被保存到ViewState中。我們在OnInit之後使用this.ViewState[key]讀寫時該屬性都爲true,所以StateItem都會被保存。但如果你想要某個StateItem臨時不保存到ViewState,那就可以執行this.ViewState.SetItemDirty(key, false)。例如我們熟悉的TextBox,在它的TextBoxMode爲Password時它就會通過上述方式讓this.ViewState["Text"]值不保存到真正的ViewState中,也就確保了你無法通過ViewState竊取密碼,同時也導致了該TextBox的OnTextChanged事件無法正常觸發。

那還有什麼要說的嗎?要說明的就是StateBag的存取所受到的限制。StateBag的存取,與你手動重寫LoadViewState/SaveViewState保存的其它值一樣,在LoadViewState之前StateBag並沒有任何值,在SaveViewState後的任何更新不保存到ViewState中。

 

深入理解 ViewState

上個星期寫了一篇《控件 ViewState 屬性的值保存去哪裏了》,解釋了Control.ViewState最終還是通過Control.SaveViewState和Control.LoadViewState這兩個方法存取的。文章中有一句話可能會讓大家感到疑惑的:“我們在OnInit之後使用this.ViewState[key]讀寫時該屬性都爲true”,其中“該屬性”指StateItem.IsDirty。到底爲什麼IsDirty屬性在OnInit之後總是爲true呢?參考了TRULY Understanding ViewState,我終於明白到其實它並非總是爲true,詳細原因請聽我慢慢說。

首先要讓大家來看的是StateBag.TrackViewState方法,這個方法在控件OnInit時就會被調用,而它的作用就是讓StateBag開始跟蹤StateItem的變化,任何變化都將導致該StateItem的IsDirty屬性變爲true。也就是說,在OnInit之前,IsDirty屬性是false的,並且無論你如何設置Value屬性的值都不會改變IsDirty屬性。在OnInit之後,IsDirty屬性也保持着false,直到你第一次改變Value屬性的值(指通過this.ViewState[key]的方法改變)。到了SaveViewState的階段,只有IsDirty屬性爲true的StateItem纔會被保存下來。

爲什麼要如此設計呢?例如一個通過聲明性定義的Label的Text屬性,在ASPX中它被賦了初值,然後該初值自然通過ViewState["Text"]來持久。在下一個頁面生命週期,首先OnInit時這個Label的Text屬性會加載ASPX中聲明性定義的初值,然後LoadViewState時會用ViewState中讀取到的ViewState["Text"]來覆蓋它。然而除非你在上一個頁面生命週期以編程的方式改變了Text屬性,否則ViewState["Text"]還是初值,那麼你就是用ViewState["Text"]保存初值去覆蓋聲明性定義的初值,同一個值這樣覆蓋完全沒意義,而且還浪費了ViewState的空間。爲了解決這個資源浪費的問題,凡是聲明性定義之後沒改變到的值就不應該使用ViewState來持久,而詳細的實現就是上面說的TrackViewState機制了。

說到這裏,Control.ViewState已經解釋完畢,如果你是控件設計者你可以放心地按以下方式把控件屬性存放到ViewState中:
public string Text
{
  get {return this.ViewState["Text"] as string;}
  set {this.ViewState["Text"] = value;}
}
它的內部機制會懂得區分你存進去的值是不是ASPX上聲明性定義的初值,然後決定是否持久該值。同時,如果你在任何階段想改變一個ViewState值是否持久的決定,可以通過ViewState.SetItemDirty(key, dirty)來改變,這基本上已經滿足了所有控件開發人員的需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章