asp.net控件開發基礎(11)
剛開篇的時后在最后把屬性值用視圖狀態來保存時,得以把當前狀態保存下來,關于視圖狀態的概述,這里不再累贅,沒了解過的朋友可以在MSDN里輸入視圖狀態概述了解一下.以下我們還是以以前講過的內容為例,一起繼續來改善控件的使用(第五篇和第九篇的例子)
示例一
我們啟用了跟蹤,按下確定按鈕后,控件屬性發生變化,按下無事件按鈕后,控件狀態則恢復到之前的狀態,而且在跟蹤狀態下發現Custom無視圖狀態.
<%@ Register Assembly="CustomComponents" Namespace="CustomComponents" TagPrefix="custom" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Button1_Click(object sender, EventArgs e)
{
Custom1.Age = 21;
Custom1.CustomMetier = Metier.教師;
Custom1.CustomAddress.City = "杭州";
Custom1.CustomAddress.State = "中國";
Custom1.CustomAddress.Street = "街道";
Custom1.CustomAddress.Zip = "310000";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>無標題頁</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<custom:Custom ID="Custom1" runat="server">
</custom:Custom>
<br />
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="確定" />
<asp:Button ID="Button2" runat="server" Text="無事件" />
</div>
</form>
</body>
</html>
那么接下來將修改Custom的屬性更改為視圖狀態保存,代碼如下
重新編譯一下代碼,再次測試上面代碼Custom的Age和CustomMetier屬性可以保存其狀態,而無法保存CustomAddress這個復雜屬性的狀態值.這個也可以理解,我們沒有為CustomAddress的子屬性值保存在視圖狀態里.啟動跟蹤后,還發現Custom控件在更改控件屬性后保存了一部分的視圖狀態.
[Description("年齡")]
public int Age
{
get { return ViewState["Age"] != null ? (int)ViewState["Age"] : 0; }
set { ViewState["Age"] = value; }
}
[Description("姓名")]
public String Name
{
get { return ViewState["Name"] != null ? (string)ViewState["Name"] : string.Empty; }
set { ViewState["Name"] = value; }
}
[TypeConverter(typeof(GameConverter))]
[Description("喜歡的游戲")]
public String Game
{
get { return ViewState["Game"] != null ? (string)ViewState["Game"] : string.Empty; }
set { ViewState["Game"] = value; }
}
[Description("職業")]
public Metier CustomMetier
{
get { return ViewState["CustomMetier"] != null ? (Metier)ViewState["CustomMetier"] : Metier.程序員; }
set { ViewState["CustomMetier"] = value; }
}
#endregion
接下來我們更改Address的字屬性,把其值保存在視圖狀態下,代碼如下:
[
Category("Behavior"),
DefaultValue(""),
Description("街道"),
NotifyParentProperty(true),
]
public String Street
{
get { return ViewState["Street"] != null ? (string)ViewState["Street"] : String.Empty; }
set { ViewState["Street"] = value; }
}
[
Category("Behavior"),
DefaultValue(""),
Description("城市"),
NotifyParentProperty(true),
]
public String City
{
get { return ViewState["City"] != null ? (string)ViewState["City"] : String.Empty; }
set { ViewState["City"] = value; }
}
[
Category("Behavior"),
DefaultValue(""),
Description("國籍"),
NotifyParentProperty(true),
]
public String State
{
get { return ViewState["State"] != null ? (string)ViewState["State"] : String.Empty; }
set { ViewState["State"] = value; }
}
[
Category("Behavior"),
DefaultValue(""),
Description("郵編"),
NotifyParentProperty(true)
]
public String Zip
{
get { return ViewState["Zip"] != null ? (string)ViewState["Zip"] : String.Empty; }
set { ViewState["Zip"] = value; }
}
#endregion
重新編譯后,發現問題了,編譯不通過,當前上下文不存在名稱ViewState.如果這些屬性直接定義在Custom控件下則一點問題也沒有,但下面定義的是Address復雜屬性的子屬性.而Address屬性又不能繼承Control類,所以我們需要自定義一個ViewState屬性 。如下代碼:















先定義兩個變量,然后定義一個ViewState屬性,ViewState類型本身便是一個StateBag類型.大家一定注意到了 IStateManager接口,下面還有一個TrackViewState方法.先不管他.重新編譯下,編譯通過,重新測試下,發現還是沒有變化。MSDN上對ViewState能保存的值已經講的很清楚了.你可以保存一些簡單類型,但無法保存自定義類型,而我們定義的Address就是一個自定義類型。為保存自定義類型數據,所以我們需要自定義類型狀態管理
自定義類型狀態管理,那么我們就必須接觸到IStateManager這個接口,此接口有一個屬性和三個方法,如下
所以Address要繼承IStateManager接口,并顯示實現接口屬性和方法,注意是顯示實現 。下面看Address類具體的自定義狀態管理代碼
bool IStateManager.IsTrackingViewState
{
get
{
return _isTrackingViewState;
}
}
void IStateManager.LoadViewState(object savedState)
{
if (savedState != null)
{
((IStateManager)ViewState).LoadViewState(savedState);
}
}
object IStateManager.SaveViewState()
{
object savedState = null;
if (_viewState != null)
{
savedState =
((IStateManager)_viewState).SaveViewState();
}
return savedState;
}
void IStateManager.TrackViewState()
{
_isTrackingViewState = true;
if (_viewState != null)
{
((IStateManager)_viewState).TrackViewState();
}
}
#endregion
理解控件自定義的狀態管理,你有必要了解控件的生命周期,了解控件生命周期,那問題就迎刃而解了。大家可以翻閱MSND的控件執行生命周期。我個人認為最好的理解方法就是為上面代碼設置三個斷點, 如下圖
好了,下面把我們測試的那個aspx頁面設置為起始頁,然后按F5,開始測試。本該啟動后跳到TrackViewState方法里,但沒跳進來,好怪,而且自定義類型狀態管理后頁面并未保存其值。讓我們回到Custom類里,我們還需要為屬性(復雜屬性)定義狀態管理。本身Control也有一套默認的狀態管理機制,而沒有實現IStateManager接口 ,其實現如下:
對下面代碼我認為是錯誤的,因為書上全是這么寫的,我認為因先把_viewState顯示轉換為IStateManager類型,因為StateBag本身是繼承IStateManager接口,但MSDN中,我并沒看到其實現IStateManager的方法,而是顯示的實現,當我用反射機制查看其方法時,卻又發現是有其方法的,但當你不把StateBag顯示轉換為IStateManager類型,而直接調用下面方法時,將會出錯.如果書上是對的,還請看到此文的人指點一下,對此我已經疑惑很長時間了. 如果我是對的,那下面的_viewState因先顯示轉換為IStateManager類型,事實上我們都是這么做的。
protected virtual StateBag ViewState{
get {
if(_viewState != null)
{
return _viewState;
}
_viewState = new StateBag(ViewStateIgnoresCase);
if(IsTrackingViewState)
_viewState.TrackViewState();
return _viewState;
}
}
protected virtual void TrackViewState(){
if(_viewState != null) {
_viewState.TrackViewState();
}
return null;
}
protected virtual object SaveViewState(){
if(_viewState != null) {
_viewState.SaveViewState();
}
return null;
}
protected virtual void LoadViewState(object savedState){
if(savedState != null) {
ViewState.LoadViewState(savedState);
}
}
下面再看如何在Custom類中自定義屬性狀態管理,當你定義了復雜類型時,你就需要重寫上面的幾個方法。具體代碼如下:
首先我們對屬性進行視圖狀態的跟蹤,然后重寫了Control類的三個方法.其一方面調用了基類方法,一方面調用了Addres類的顯示接口方法。Pair類為一個輔助類,用作存儲兩個相關對象的基本結構,下面根據調試結果來理解.在Custom類中對其三個方法設置斷點。
{
get
{
if (address == null)
{
address = new Address();
if (IsTrackingViewState)
{
((IStateManager)address).TrackViewState();
}
}
return address;
}
}
#region 自定義視圖狀態
protected override void LoadViewState(object savedState)
{
Pair p = savedState as Pair;
if (p != null)
{
base.LoadViewState(p.First);
((IStateManager)CustomAddress).LoadViewState(p.Second);
return;
}
base.LoadViewState(savedState);
}
protected override object SaveViewState()
{
object baseState = base.SaveViewState();
object thisState = null;
if (address != null)
{
thisState = ((IStateManager)address).SaveViewState();
}
if (thisState != null)
{
return new Pair(baseState, thisState);
}
else
{
return baseState;
}
}
protected override void TrackViewState()
{
if (address != null)
{
((IStateManager)address).TrackViewState();
}
base.TrackViewState();
}
#endregion
設置斷點以后,啟動起始頁開始測試。啟動后第一步將會跳到Custom類的TrackViewState方法里面,執行完此方法后IsTrackingViewState將設置為true,從而可以繼續調用address的TrackViewState方法,另外可以看到address屬性為空值,然后按F5,通過此方法繼續
第二步將會跳到Custom類的SaveViewState方法里,發現baseState和thisState均為空,直接執行基類方法.按F5繼續
第三步將會跳到Address類的TrackViewState方法里,_isTrackingViewState初始化時為false,執行此方法后將賦值為ture,然后調用_viewState的TrackViewState方法.
初始化的工作就完成了,然后我們點擊確定按鈕,重新執行.
重新跳到Custom類的TrackViewState方法里,步驟跟上面第一步一樣,按F5,繼續
跳到Address類的TrackViewState方法里,步驟跟上面第二步一樣,按F5繼續
跳到Custom類的SaveViewState方法里,此時address不再為null,此時會返回Pair構造函數.
然后會跳到Address類SaveViewState方法里,接著會跳回來,再執行Custom類的SaveViewState方法
以上調試方法不一定正確,但多調用會理解的更深刻。
我們還發現并未跳到LoadViewState方法里,以前的主要工作就是保存視圖狀態更改,接下來再次調試的話,就會跳到LoadViewState方法方法里面,這時你會發現savedState就是SaveViewState方法中保存下來的視圖狀態,可以看到其first和second值分別為Custom的頁面屬性和Address這個復雜屬性的值。
視圖狀態以鍵/值的方式保存,有一個屬性為Dirty,表示StateItem是否被修改過,可以通過SetDirty方法和SetItemDirty方法給StateItem添加Dirty標記.




當控件禁用視圖狀態時將不再執行SaveViewState和LoadViewState,可以去調試一下就知道了。還需要注意的是,我們了解視圖狀態可以保存的類型,其也是同過類型轉換器來轉換此類型,否則的話將以二進制串行化功能來串行化數值得,這樣降低了效率,所以我們還需要為其定義一個類型轉換器,第九篇的時候已經講過怎么定義了,這里就不列代碼了,只是需要注意就是.
此外asp.net2.0中加入了控件狀態,因為視圖狀態要么全開,要么全禁用,控件狀態則是為彌補這一點,大家可以看MSDN,也可參考相關文章。asp.net2.0中還可以對視圖狀態進行分塊處理,你需要在web.config里如下設置



asp.net2.0還加入了視圖狀態持久性機制,大家可以在博客園參考相關文章,這里就當了解下有這種機制存在。好了,就寫到這里,個人認為視圖狀態是很重要的,下面很多東西都要涉及到它,所以要好好理解這個東西。寫的比較亂,對視圖狀態我真的比較敏感,很難理解,也難表達,可能很多地方寫錯,還請指出,這樣才能提高
下一篇:asp.net控件開發基礎(12)