18.7 自定义状态管理

前面已经阐述过,在控件中,可以通过重写TrackViewState、SaveViewState和LoadViewState方法来实现自定义状态管理。而在第18.5.1节中,也在MyBook控件中给出了自定义状态管理的实现示例。本节将阐述如何通过实现IStateManager接口执行自己的状态管理。

18.7.1 定义MyNewBook控件

为了便于大家理解,本节同样以第18.5节中MyBook控件为例。为了便于区别,在这里将新控件名命为MyNewBook。

其中,MyNewBook与MyBook控件之间的差异在于,MyNewBook中的StateManagedAuthor属性的类型将StateManagedAuthor属性的状态管理委托给该属性的StateManagedAuthor类型的状态管理方法,即通过MyNewBook的StateManagedAuthor属性以及StateManagedAuthor类的状态管理方法((TackViewState、SaveViewState和LoadViewState)来执行状态管理。相反,MyBook控件则显式地管理其Author属性的状态。MyNewBook类的详细代码如下所示:


[AspNetHostingPermission(SecurityAction.Demand,

Level=AspNetHostingPermissionLevel.Minimal),

AspNetHostingPermission(SecurityAction.InheritanceDemand,

Level=AspNetHostingPermissionLevel.Minimal),

DefaultProperty("Title"),

ToolboxData("<{0}:MyNewBook runat=\"server\">

</{0}:MyNewBook>")]

public class MyNewBook:WebControl

{

private StateManagedAuthor authorValue;

[Bindable(true),

Category("Appearance"),

DefaultValue(""),

Description("The name of the author."),

DesignerSerializationVisibility(

DesignerSerializationVisibility.Content),

PersistenceMode(PersistenceMode.InnerProperty)]

public virtual StateManagedAuthor StateManagedAuthor

{

get

{

if(authorValue==null)

{

authorValue=new StateManagedAuthor();

if(IsTrackingViewState)

{

(((ItateManager)authorValue).TrackViewState();

}

}

return authorValue;

}

}

[Bindable(true),

Category("Appearance"),

DefaultValue(BookType.NotDefined),

Description("Fiction or Not")]

public virtual BookType BookType

{

get

{

object t=ViewState["BookType"];

return(t==null)?BookType.NotDefined:((BokType)t;

}

set

{

ViewState["BookType"]=value;

}

}

[Bindable(true),

Category("Appearance"),

DefaultValue(""),

Description("The symbol for the currency."),

Localizable(true)]

public virtual string CurrencySymbol

{

get

{

string s=((sring)ViewState["CurrencySymbol"];

return(s==null)?String.Empty:s;

}

set

{

ViewState["CurrencySymbol"]=value;

}

}

[Bindable(true),

Category("Appearance"),

DefaultValue("0.00"),

Description("The price of the book."),

Localizable(true)]

public virtual Decimal Price

{

get

{

object price=ViewState["Price"];

return(price==null)?Decimal.Zero:((Dcimal)price;

}

set

{

ViewState["Price"]=value;

}

}

[Bindable(true),

Category("Appearance"),

DefaultValue(""),

Description("The title of the book."),

Localizable(true)]

public virtual string Title

{

get

{

string s=((sring)ViewState["Title"];

return(s==null)?String.Empty:s;

}

set

{

ViewState["Title"]=value;

}

}

protected override void Render(HtmlTextWriter writer)

{

base.AddAttributesToRender(writer);

writer.RenderBeginTag(HtmlTextWriterTag.Table);

writer.RenderBeginTag(HtmlTextWriterTag.Tr);

writer.RenderBeginTag(HtmlTextWriterTag.Td);

writer.WriteEncodedText(Title);

writer.RenderEndTag();

writer.RenderEndTag();

writer.RenderBeginTag(HtmlTextWriterTag.Tr);

writer.RenderBeginTag(HtmlTextWriterTag.Td);

writer.WriteEncodedText(StateManagedAuthor.ToString());

writer.RenderEndTag();

writer.RenderEndTag();

writer.RenderBeginTag(HtmlTextWriterTag.Tr);

writer.RenderBeginTag(HtmlTextWriterTag.Td);

writer.WriteEncodedText(BookType.ToString());

writer.RenderEndTag();

writer.RenderEndTag();

writer.RenderBeginTag(HtmlTextWriterTag.Tr);

writer.RenderBeginTag(HtmlTextWriterTag.Td);

writer.Write(CurrencySymbol);

writer.Write(" ");

writer.Write(String.Format("{0:F2}",Price));

writer.RenderEndTag();

writer.RenderEndTag();

writer.RenderEndTag();

}

region state management

protected override void LoadViewState(object savedState)

{

Pair p=savedState as Pair;

if(p!=null)

{

base.LoadViewState(p.First);

(((ItateManager)StateManagedAuthor

).LoadViewState(p.Second);

return;

}

base.LoadViewState(savedState);

}

protected override object SaveViewState()

{

object baseState=base.SaveViewState();

object thisState=null;

if(authorValue!=null)

{

thisState=

(((ItateManager)authorValue).SaveViewState();

}

if(thisState!=null)

{

return new Pair(baseState, thisState);

}

else

{

return baseState;

}

}

protected override void TrackViewState()

{

if(authorValue!=null)

{

(((ItateManager)authorValue).TrackViewState();

}

base.TrackViewState();

}

endregion

}


在上面的代码中,MyNewBook将StateManagedAuthor属性定义为只读属性,该属性存储于StateManagedAuthor类型的私有字段中(即private StateManagedAuthor authorValue)。在StateManagedAuthor属性访问器中,如果与该属性对应的私有字段为null,则MyNewBook会将一个新的StateManagedAuthor对象分配给该私有字段。如果MyNewBook已启动状态跟踪,则MyNewBook会通过调用新创建的StateManagedAuthor对象的TrackViewState方法对该对象启动状态跟踪。

StateManagedAuthor属性通过从其自己的状态管理方法((TackViewState、SaveViewState和LoadViewState)中调用StateManagedAuthor对象的IStateManager方法来参与状态管理。

在重写的TrackViewState方法中,MyNewBook调用基类的TrackViewState方法以及与StateManagedAuthor属性对应的StateManagedAuthor对象的TrackViewState方法。

在重写的SaveViewState方法中,MyNewBook调用基类的SaveViewState方法以及与StateManagedAuthor属性对应的StateManagedAuthor对象的SaveViewState方法。在这之间,如果StateManagedAuthor属性有要保存的状态,则MyNewBook控件的SaveViewState方法将返回一个Pair对象,该对象包含基类和StateManagedAuthor属性的状态。如果StateManagedAuthor属性没有状态要保存,则该方法仅返回由对基类调用SaveViewState返回的状态。基于提供状态的自定义属性的数量,应从SaveViewState中返回Pair、Triplet或Array类型的对象。这样便可以采用LoadViewState方法更容易地检索具有已保存状态的不同部件。在这种情况下,因为存在两个项,即基类状态和StateManagedAuthor状态,故而将使用Pair类。

在重写的LoadViewState方法中,MyNewBook实现其在LoadViewState方法中实现的操作的反向操作。MyNewBook将状态加载到基类和StateManagedAuthor属性中,如果StateManagedAuthor属性没有状态要保存,则MyNewBook仅会将状态加载到基类中。即使保存的状态为null,也应始终调用基类的LoadViewState方法,因为基类在没有状态要还原时可能已经在此方法中实现了其他逻辑。18.7.2定义子属性StateManagedAuthor

对于StateManagedAuthor类,它实现IStateManager接口,同时还定义了一个名为ViewState且存储在类型StateBag的私有变量(_viewState)中的属性。在这里,ViewState属性模仿了Control类的ViewState属性,正如控件在Control类的ViewState字典中存储其简单属性一样,StateManagedAuthor在其ViewState字典中存储其属性。如下面的代码所示:


[TypeConverter(typeof(StateManagedAuthorConverter))]

public class StateManagedAuthor:IStateManager

{

private bool_isTrackingViewState;

private StateBag_viewState;

public StateManagedAuthor()

:this(String.Empty, String.Empty, String.Empty)

{

}

public StateManagedAuthor(string first, string last)

:this(first, String.Empty, last)

{

}

public StateManagedAuthor(string first, string middle,

string last)

{

FirstName=first;

MiddleName=middle;

LastName=last;

}

[Category("Behavior"),

DefaultValue(""),

Description("First name of author"),

NotifyParentProperty(true)]

public virtual String FirstName

{

get

{

string s=((sring)ViewState["FirstName"];

return(s==null)?String.Empty:s;

}

set

{

ViewState["FirstName"]=value;

}

}

[Category("Behavior"),

DefaultValue(""),

Description("Last name of author"),

NotifyParentProperty(true)]

public virtual String LastName

{

get

{

string s=((sring)ViewState["LastName"];

return(s==null)?String.Empty:s;

}

set

{

ViewState["LastName"]=value;

}

}

[Category("Behavior"),

DefaultValue(""),

Description("Middle name of author"),

NotifyParentProperty(true)]

public virtual String MiddleName

{

get

{

string s=((sring)ViewState["MiddleName"];

return(s==null)?String.Empty:s;

}

set

{

ViewState["MiddleName"]=value;

}

}

protected virtual StateBag ViewState

{

get

{

if(_viewState==null)

{

_viewState=new StateBag(false);

if(_isTrackingViewState)

{

(((ItateManager)_viewState).TrackViewState();

}

}

return_viewState;

}

}

public override string ToString()

{

return ToString(CultureInfo.InvariantCulture);

}

public string ToString(CultureInfo culture)

{

return TypeDescriptor.GetConverter(

GetType()).ConvertToString(null, culture, this);

}

region IStateManager implementation

bool IStateManager.IsTrackingViewState

{

get

{

return_isTrackingViewState;

}

}

void IStateManager.LoadViewState(object savedState)

{

if(savedState!=null)

{

(((ItateManager)ViewState).LoadViewState(savedState);

}

}

object IStateManager.SaveViewState()

{

object savedState=null;

if(_viewState!=null)

{

savedState=

(((ItateManager)_viewState).SaveViewState();

}

return savedState;

}

void IStateManager.TrackViewState()

{

_isTrackingViewState=true;

if(_viewState!=null)

{

(((ItateManager)_viewState).TrackViewState();

}

}

endregion

internal void SetDirty()

{

_viewState.SetDirty(true);

}

}


从上面的代码中可以看出,IStateManager接口具有一个IsTrackingViewState属性和三个方法((TackViewState、SaveViewState和LoadViewState)。

其中,IsTrackingViewState属性使实现IStateManager的类型可与使用了该类型的控件中的状态跟踪协调工作。在StateManagedAuthor类中,使用_isTrackingViewState来存储此属性。

在TrackViewState方法的实现中,StateManagedAuthor将_isTrackingViewState设置为true。它还调用与ViewState属性相对应的私有_viewState字段的IStateManager.TrackViewState方法。TrackViewState方法对ViewState属性中存储的项启动更改跟踪。这意味着,如果在对ViewState调用TrackViewState后设置ViewState字典中的某一项,该项将自动标记为已修改。

在其SaveViewState方法的实现中,StateManagedAuthor只调用其私有_viewState字段的对应方法。类似地,在其LoadViewState方法的实现中,StateManagedAuthor只调用其ViewState属性的对应方法。

18.7.3 定义类型转换器StateManagedAuthorConverter

与MyBook控件一样,最后还需要定义一个类型转换器StateManagedAuthorConverter。它可以完成从String类型到StateManagedAuthor类型之间的转换以及与之相反的转换,如下面的代码所示:


public class StateManagedAuthorConverter:

ExpandableObjectConverter

{

public override bool CanConvertFrom(

ITypeDescriptorContext context, Type sourceType)

{

if(sourceType==typeof(string))

{

return true;

}

return base.CanConvertFrom(context, sourceType);

}

public override bool CanConvertTo(

ITypeDescriptorContext context, Type destinationType)

{

if(destinationType==typeof(string))

{

return true;

}

return base.CanConvertTo(context, destinationType);

}

public override object ConvertFrom(

ITypeDescriptorContext context,

CultureInfo culture, object value)

{

if(value==null)

{

return new StateManagedAuthor();

}

if(value is string)

{

string s=((sring)value;

if(s.Length==0)

{

return new StateManagedAuthor();

}

string[]parts=s.Split('');

if(((prts.Length<2)||((prts.Length>3))

{

throw new ArgumentException(

"Name must have 2 or 3 parts.","value");

}

if(parts.Length==2)

{

return new StateManagedAuthor(parts[0],parts[1]);

}

if(parts.Length==3)

{

return new StateManagedAuthor(parts[0],

parts[1],parts[2]);

}

}

return base.ConvertFrom(context, culture, value);

}

public override object ConvertTo(

ITypeDescriptorContext context,

CultureInfo culture, object value, Type destinationType)

{

if(value!=null)

{

if(!((vlue is StateManagedAuthor))

{

throw new ArgumentException(

"Name must have 2 or 3 parts.","value");

}

}

if(destinationType==typeof(string))

{

if(value==null)

{

return String.Empty;

}

StateManagedAuthor auth=((SateManagedAuthor)value;

if(auth.MiddleName!=String.Empty)

{

return String.Format("{0}{1}{2}",

auth.FirstName,

auth.MiddleName,

auth.LastName);

}

else

{

return String.Format("{0}{1}",

auth.FirstName,

auth.LastName);

}

}

return base.ConvertTo(context, culture,

value, destinationType);

}

}


18.7.4 使用MyNewBook控件

现在,一个完整的MyNewBook控件就创建完成了。下面就可以在页面里来测试这个MyNew-Book控件。如下面的代码所示:


<form id="Form1"runat="server">

<cc1:MyNewBook ID="Book1"runat="server"Title="易学C#"

CurrencySymbol="¥"BackColor="#FFE0C0"

Font-Names="Tahoma"Price="45"BookTyp="NotDefined">

<StateManagedAuthor FirstName="马伟"LastName="马登伟"/>

</cc1:MyNewBook>

<br/>

<asp:Button ID="Button1"OnClick="Button1_Click"

runat="server"Text="切换"/>

<br/>

<asp:HyperLink ID="Hyperlink1"NavigateUrl="WebForm4.aspx"

runat="server">Reload Page</asp:HyperLink>

</form>

然后在Button1控件的Button1_Click事件中对Book1控件做如下处理:

public void Button1_Click(object sender, EventArgs e)

{

Book1.StateManagedAuthor.FirstName="马伟";

Book1.StateManagedAuthor.LastName="马登伟";

Book1.Title="ASP.NET4程序设计";

Book1.Price=100;

Button1.Visible=false;

}


示例运行结果如图18-9所示。

figure_0674_0505

图 18-9 MyNewBook控件的示例运行结果