asp.net控件開發基礎(11) --------自定義視圖狀態管理

  上一篇討論了集合屬性的使用,這一篇我們主要來討論視圖狀態的自定義管理.

         剛開篇的時後在最後把屬性值用視圖狀態來保存時,得以把當前狀態保存下來,關於視圖狀態的概述,這裏不再累贅,沒了解過的朋友可以在MSDN裏輸入視圖狀態概述瞭解一下.以下我們還是以以前講過的內容爲例,一起繼續來改善控件的使用(第五篇和第九篇的例子)


示例一

我們啓用了跟蹤,按下確定按鈕後,控件屬性發生變化,按下無事件按鈕後,控件狀態則恢復到之前的狀態,而且在跟蹤狀態下發現Custom無視圖狀態.

<%@ Page Language="C#" Trace="true" %>
<%@ 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="確定" />
        
&nbsp; &nbsp;&nbsp;
        
<asp:Button ID="Button2" runat="server" Text="無事件" />&nbsp;
    
</div>
    
</form>
</body>
</html>

那麼接下來將修改Custom的屬性更改爲視圖狀態保存,代碼如下

重新編譯一下代碼,再次測試上面代碼Custom的Age和CustomMetier屬性可以保存其狀態,而無法保存CustomAddress這個複雜屬性的狀態值.這個也可以理解,我們沒有爲CustomAddress的子屬性值保存在視圖狀態裏.啓動跟蹤後,還發現Custom控件在更改控件屬性後保存了一部分的視圖狀態.


#region 屬性

        [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的字屬性,把其值保存在視圖狀態下.

代碼如下:


#region 屬性
        [
       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屬性

如下代碼:

private bool _isTrackingViewState;
private StateBag _viewState;

protected StateBag ViewState
        
{
            
get
            
{
                
if (_viewState == null)
                
{
                    _viewState 
= new StateBag(false);
                    
if (_isTrackingViewState) ((IStateManager)_viewState).TrackViewState();
                }

                
return _viewState;
            }

        }


先定義兩個變量,然後定義一個ViewState屬性,ViewState類型本身便是一個StateBag類型.大家一定注意到了 IStateManager接口,下面還有一個TrackViewState方法.先不管他.重新編譯下,編譯通過,重新測試下,發現還是沒有變化.

MSDN上對ViewState能保存的值已經講的很清楚了.你可以保存一些簡單類型,但無法保存自定義類型,而我們定義的Address就是一個自定義類型.爲保存自定義類型數據,所以我們需要自定義類型狀態管理

自定義類型狀態管理,那麼我們就必須接觸到IStateManager這個接口,此接口有一個屬性和三個方法,如下




所以Address要繼承IStateManager接口,並顯示實現接口屬性和方法,注意是顯示實現 .

下面看Address類具體的自定義狀態管理代碼

#region 自定義狀態管理

        
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類型,事實上我們都是這麼做的.


 1private StateBag _viewState;
 2protected virtual StateBag ViewState{
 3 get {
 4  if(_viewState != null
 5  {
 6   return _viewState;
 7  }

 8  _viewState = new StateBag(ViewStateIgnoresCase);
 9  if(IsTrackingViewState) 
10   _viewState.TrackViewState();
11   return _viewState;
12 }

13}

14
15protected virtual void TrackViewState()
16 if(_viewState != null
17  _viewState.TrackViewState();
18 }

19 return null;
20}

21
22protected virtual object SaveViewState(){
23 if(_viewState != null{
24  _viewState.SaveViewState(); 
25 }

26 return null;
27}

28protected virtual void LoadViewState(object savedState){
29 if(savedState != null
30  ViewState.LoadViewState(savedState); 
31 }

32}


下面再看如何在Custom類中自定義屬性狀態管理,當你定義了複雜類型時,你就需要重寫上面的幾個方法.
具體代碼如下:
首先我們對屬性進行視圖狀態的跟蹤,然後重寫了Control類的三個方法.其一方面調用了基類方法,一方面調用了Addres類的顯示接口方法.

Pair類爲一個輔助類,用作存儲兩個相關對象的基本結構,下面根據調試結果來理解.在Custom類中對其三個方法設置斷點.


public Address CustomAddress
        
{
            
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標記.
        internal void SetDirty()
        
{
            _viewState.SetDirty(
true);
        }

如果此StateItem標記爲Dirty的話,則在SaveViewState方法中以鍵/值的方式保存到ArrayList中.

SaveViewState方法和LoadViewState方法執行的是相反的操作.我們在頁面上看到的值,總是LoadViewState方法反序列化視圖狀態.大家可以具體去了解StateBag類默認情況下SaveViewState方法和LoadViewState方法的實現過程.

當控件禁用視圖狀態時將不再執行SaveViewState和LoadViewState,可以去調試一下就知道了.

還需要注意的是,我們瞭解視圖狀態可以保存的類型,其也是同過類型轉換器來轉換此類型,否則的話將以二進制串行化功能來串行化數值得,這樣降低了效率,所以我們還需要爲其定義一個類型轉換器,第九篇的時候已經講過怎麼定義了,這裏就不列代碼了,只是需要注意就是.

此外asp.net2.0中加入了控件狀態,因爲視圖狀態要麼全開,要麼全禁用,控件狀態則是爲彌補這一點,大家可以看MSDN,也可參考相關文章.

asp.net2.0中還可以對視圖狀態進行分塊處理,你需要在web.config裏如下設置

<system.web>
    
<pages maxPageStateFieldLength="1000" >
<system.web>

asp.net2.0還加入了視圖狀態持久性機制,大家可以在博客園參考相關文章,這裏就當瞭解下有這種機制存在.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章