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所示。
图 18-9 MyNewBook控件的示例运行结果