asp.net控件开发(一)简单属性、视图状态、控件状态
了解控件的生命周期:
其中Init是由内而外,即先子控件后父控件,Load等相反。
在Init之前控件树根据声明语法已经生成
控件状态和视图状态:
控件状态是专门为维护控件的核心行为功能而设计的,而 ViewState 只包含维护控件内容 (UI) 的状态。应该按照这个规则设计控件状态。
先看一个例子:改自asp服务器控件和组件开发一书
PageTracker,用来跟踪页面会话状态和程序状态收到的点击数目,并计算页面往返时间
跟踪方式:
分析视图状态:
视图状态只会把Diry的数据序列化到客户端,并不会序列化所有ViewState中的值,这样就减少了往返的数据量。如果禁用视图状态,就不会执行SaveViewState和LoadViewState。
第一次请求时(非Postback),视图状态从生成到序列化到客户端的过程:
1、在Init事件后,会调用TrackViewState方法,在此方法中,页面会自动调用控件的TrackViewState,并且把变化的属性存储到ViewState,保证了尽量少的值.此例子中ViewState此时只有两个值:FormtString和TrackingMode,其余的值还保持默认状态,并不会被加入ViewState。
2、Onload方法执行时,通过执行:“ ViewState["TimeStamp"] = requestTime;",ViewState中会加入TimeStamp,值变为三个,虽然通过this.Visible = false把Visible设置为了False,可是在此时并不在ViewState中加入值,除非显示调用ViewState方法,否则到SaveViewState时才会加入到视图状态。
3、执行SaveViewState,因为Onload修改了Visible的属性,这时Visible被加入,此时ViewState的值变为四个。这个例子中值是页面框架自动保存到ViewState中的。如果自定义视图状态管理,必须自己实现Save,load。在此后控件状态的改变都不会被加入到视图状态中了,这是可以修改视图状态的最后机会。
4、解码序列化的视图状态:
通过查看生成的Html,解析出它的Viewstate值(部分):
原来,FormtString和TrackingMode没有被改写过,他们是没有必要被序列化到客户端的,从他们的IsItemDiry=fals可以看处来。
回发时(Postback)
在TraceViewState后会调用LoadViewState,LoadViewState会自动反序列化被编码的值(不自定义状态管理的情况下),并且合并TraceViewState生成的ViewState集合,本例中会是(Visible和TimeStamp+FormtString和TrackingMode)。
然后经过Onload,修改了timeStamp,Visible,再到SaveViewState被序列化到客户端。
生成的Html中的视图状态会是:
简单属性:
简单属性是可以映射到文本的属性,他们一般有内置的TypeConvert,我们为他们启用声明性语法,不必做任何工作。因为.net内置支持.本例的TrackingMode等都是简单属性。枚举类型会在页面解析时自动映射到EnumConvert。
控件状态:
现在修改上面的例子,加上ControlState,只是示例作用:
可以用Triplet,和Pair存取值,也可以自己定义数组,象上面一样。
一段代码中的两个问题:
其中Init是由内而外,即先子控件后父控件,Load等相反。
在Init之前控件树根据声明语法已经生成
控件状态和视图状态:
控件状态是专门为维护控件的核心行为功能而设计的,而 ViewState 只包含维护控件内容 (UI) 的状态。应该按照这个规则设计控件状态。
先看一个例子:改自asp服务器控件和组件开发一书
PageTracker,用来跟踪页面会话状态和程序状态收到的点击数目,并计算页面往返时间
跟踪方式:
控件代码:
调用:
<cc1:PageTracker ID="PageTracker3" runat="server" FormatString="{0}" TrackingMode="ByTripTime" />
分析视图状态:
视图状态只会把Diry的数据序列化到客户端,并不会序列化所有ViewState中的值,这样就减少了往返的数据量。如果禁用视图状态,就不会执行SaveViewState和LoadViewState。
第一次请求时(非Postback),视图状态从生成到序列化到客户端的过程:
1、在Init事件后,会调用TrackViewState方法,在此方法中,页面会自动调用控件的TrackViewState,并且把变化的属性存储到ViewState,保证了尽量少的值.此例子中ViewState此时只有两个值:FormtString和TrackingMode,其余的值还保持默认状态,并不会被加入ViewState。
2、Onload方法执行时,通过执行:“ ViewState["TimeStamp"] = requestTime;",ViewState中会加入TimeStamp,值变为三个,虽然通过this.Visible = false把Visible设置为了False,可是在此时并不在ViewState中加入值,除非显示调用ViewState方法,否则到SaveViewState时才会加入到视图状态。
3、执行SaveViewState,因为Onload修改了Visible的属性,这时Visible被加入,此时ViewState的值变为四个。这个例子中值是页面框架自动保存到ViewState中的。如果自定义视图状态管理,必须自己实现Save,load。在此后控件状态的改变都不会被加入到视图状态中了,这是可以修改视图状态的最后机会。
4、解码序列化的视图状态:
通过查看生成的Html,解析出它的Viewstate值(部分):
<Pair>
<ArrayList>
<IndexedString>TimeStamp</IndexedString>
<DateTime>2006-8-7 16:05:19</DateTime>
<IndexedString>Visible</IndexedString>
<Boolean>False</Boolean>
</ArrayList>
</Pair>
其中Pair是存储ViewState的结构,第一个是状态名,第二个是值。从中可以看到视图状态只有两对值,而最后Save到ViewState的却是4对,为什么会如此呢?<ArrayList>
<IndexedString>TimeStamp</IndexedString>
<DateTime>2006-8-7 16:05:19</DateTime>
<IndexedString>Visible</IndexedString>
<Boolean>False</Boolean>
</ArrayList>
</Pair>
原来,FormtString和TrackingMode没有被改写过,他们是没有必要被序列化到客户端的,从他们的IsItemDiry=fals可以看处来。
回发时(Postback)
在TraceViewState后会调用LoadViewState,LoadViewState会自动反序列化被编码的值(不自定义状态管理的情况下),并且合并TraceViewState生成的ViewState集合,本例中会是(Visible和TimeStamp+FormtString和TrackingMode)。
然后经过Onload,修改了timeStamp,Visible,再到SaveViewState被序列化到客户端。
生成的Html中的视图状态会是:
<Pair>
<ArrayList>
<IndexedString>TimeStamp</IndexedString>
<DateTime>2006-8-7 17:05:19</DateTime>
<IndexedString>Visible</IndexedString>
<Boolean>True</Boolean>
</ArrayList>
</Pair>
仍然只有两个,因为另外两个仍没有被修改过,不会被序列化。<ArrayList>
<IndexedString>TimeStamp</IndexedString>
<DateTime>2006-8-7 17:05:19</DateTime>
<IndexedString>Visible</IndexedString>
<Boolean>True</Boolean>
</ArrayList>
</Pair>
简单属性:
简单属性是可以映射到文本的属性,他们一般有内置的TypeConvert,我们为他们启用声明性语法,不必做任何工作。因为.net内置支持.本例的TrackingMode等都是简单属性。枚举类型会在页面解析时自动映射到EnumConvert。
控件状态:
现在修改上面的例子,加上ControlState,只是示例作用:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override object SaveControlState()
{
return (object)this._trackingMode;
}
protected override void LoadControlState(object savedState)
{
if (savedState != null)
{
this._trackingMode = (PageTrackingMode)savedState;
}
}
private PageTrackingMode _trackingMode;
[
Category("Behavior"),
DefaultValue(PageTrackingMode.ByApplication),
Description("The type of tracking to perform.in control state")
]
public virtual PageTrackingMode TrackingMode
{
get
{
return (PageTrackingMode)_trackingMode;
}
set
{
if (value < PageTrackingMode.ByApplication || value > PageTrackingMode.ByTripTime)
{
throw new ArgumentOutOfRangeException("value");
}
_trackingMode = value;
}
}
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override object SaveControlState()
{
return (object)this._trackingMode;
}
protected override void LoadControlState(object savedState)
{
if (savedState != null)
{
this._trackingMode = (PageTrackingMode)savedState;
}
}
private PageTrackingMode _trackingMode;
[
Category("Behavior"),
DefaultValue(PageTrackingMode.ByApplication),
Description("The type of tracking to perform.in control state")
]
public virtual PageTrackingMode TrackingMode
{
get
{
return (PageTrackingMode)_trackingMode;
}
set
{
if (value < PageTrackingMode.ByApplication || value > PageTrackingMode.ByTripTime)
{
throw new ArgumentOutOfRangeException("value");
}
_trackingMode = value;
}
}
控件状态实际上就是自定义的视图状态,现在简单的分析一下:
通过调用RegisterRequiresControlState 告诉容器Page,自定义控件将要使用控件状态。
LoadControlState和SaveContolState和ViewState的方法是一样的,他们会在ViewState的同名方法前被调用。
如果父控件有自己的控件状态,你又想把自己的控件状态加入到父控件。可以调用基类的SaveControlState 和LoadControlState
protected override object SaveControlState()
{
object[] state = new object[3];
state[0] = base.SaveControlState();
state[1] = this._myValue1;
state[2] = this._myValue2;
return state;
}
protected override void LoadControlState(object state)
{
object[] stateTmp = (object[])state;
base.LoadControlState(stateTmp[0]);
this._myValue1 = (string)stateTmp[1];
this._myValue2 = (string)stateTmp[2];
}
{
object[] state = new object[3];
state[0] = base.SaveControlState();
state[1] = this._myValue1;
state[2] = this._myValue2;
return state;
}
protected override void LoadControlState(object state)
{
object[] stateTmp = (object[])state;
base.LoadControlState(stateTmp[0]);
this._myValue1 = (string)stateTmp[1];
this._myValue2 = (string)stateTmp[2];
}
可以用Triplet,和Pair存取值,也可以自己定义数组,象上面一样。
一段代码中的两个问题:
//Perform cleanup of the old storage.
//We have to check that the Page is not null
//because the control could be initialized
//before it is added to the control tree.
//Note that the Application and Session
//objects are not available in the designer.
switch (TrackingMode)
{
case PageTrackingMode.ByApplication:
if (Page != null && Page.Application != null)
{
Page.Application.Remove(HitsKey);
}
break;
case PageTrackingMode.BySession:
if (Page != null && Page.Session != null)
{
Page.Session.Remove(HitsKey);
}
break;
case PageTrackingMode.ByTripTime:
ViewState.Remove("TimeStamp");
break;
}
//We have to check that the Page is not null
//because the control could be initialized
//before it is added to the control tree.
//Note that the Application and Session
//objects are not available in the designer.
switch (TrackingMode)
{
case PageTrackingMode.ByApplication:
if (Page != null && Page.Application != null)
{
Page.Application.Remove(HitsKey);
}
break;
case PageTrackingMode.BySession:
if (Page != null && Page.Session != null)
{
Page.Session.Remove(HitsKey);
}
break;
case PageTrackingMode.ByTripTime:
ViewState.Remove("TimeStamp");
break;
}
1、在Init之前究竟执行了什么?
我认为:会实例化子控件并且把他们加入控件树
上面代码的注释说,控件会在加入控件树之前被初始化,因此我们要保证Page不为Null。
我认为:在根据声明语法生成控件树的过程中,执行到此步骤时,控件树已经生成了一部分,即Page在此时不可能为Null,因此不用判断Null。可是跟踪发现Page确实是Null,原因可能是,控件树已经部分生成,Page已经生成。但在此时子控件是不能访问Page的,所以才会是Null。
下面是编译源:
private System.Web.UI.Control __BuildControlForm1() {
System.Web.UI.HtmlControls.HtmlForm __ctrl;
__ctrl = new System.Web.UI.HtmlControls.HtmlForm();
this.Form1 = __ctrl;
__ctrl.ID = "Form1";
__ctrl.Method = "post";
System.Web.UI.IParserAccessor __parser = ((System.Web.UI.IParserAccessor)(__ctrl));
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t"));
this.__BuildControlPageTracker3();
__parser.AddParsedSubObject(this.PageTracker3);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t\t"));
this.__BuildControlButton1();
__parser.AddParsedSubObject(this.Button1);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\t"));
return __ctrl;
}
private void __BuildControlTree(System.Web.UI.Control __ctrl) {
System.Web.UI.IParserAccessor __parser = ((System.Web.UI.IParserAccessor)(__ctrl));
__parser.AddParsedSubObject(this.CreateResourceBasedLiteralControl(0, 377, true));
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t</HEAD>\r\n\t<body>\r\n\t\t"));
this.__BuildControlForm1();
__parser.AddParsedSubObject(this.Form1);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t</body>\r\n</HTML>\r\n"));
}
protected override void FrameworkInitialize() {
SetStringResourcePointer(ASP.WebForm1_aspx.__stringResource, 377);
this.__BuildControlTree(this);
this.FileDependencies = ASP.WebForm1_aspx.__fileDependencies;
this.EnableViewStateMac = true;
this.Request.ValidateInput();
}
System.Web.UI.HtmlControls.HtmlForm __ctrl;
__ctrl = new System.Web.UI.HtmlControls.HtmlForm();
this.Form1 = __ctrl;
__ctrl.ID = "Form1";
__ctrl.Method = "post";
System.Web.UI.IParserAccessor __parser = ((System.Web.UI.IParserAccessor)(__ctrl));
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t"));
this.__BuildControlPageTracker3();
__parser.AddParsedSubObject(this.PageTracker3);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t\t"));
this.__BuildControlButton1();
__parser.AddParsedSubObject(this.Button1);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\t"));
return __ctrl;
}
private void __BuildControlTree(System.Web.UI.Control __ctrl) {
System.Web.UI.IParserAccessor __parser = ((System.Web.UI.IParserAccessor)(__ctrl));
__parser.AddParsedSubObject(this.CreateResourceBasedLiteralControl(0, 377, true));
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t</HEAD>\r\n\t<body>\r\n\t\t"));
this.__BuildControlForm1();
__parser.AddParsedSubObject(this.Form1);
__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\t</body>\r\n</HTML>\r\n"));
}
protected override void FrameworkInitialize() {
SetStringResourcePointer(ASP.WebForm1_aspx.__stringResource, 377);
this.__BuildControlTree(this);
this.FileDependencies = ASP.WebForm1_aspx.__fileDependencies;
this.EnableViewStateMac = true;
this.Request.ValidateInput();
}
protected virtual void AddParsedSubObject(object obj)
{
Control control1 = obj as Control;
if (control1 != null)
{
this.Controls.Add(control1);
}
}
{
Control control1 = obj as Control;
if (control1 != null)
{
this.Controls.Add(control1);
}
}
可以看到在执行Init前,会编译运行上面自动生成的编译源,这样以来第一个问题就彻底清楚了,也证明我上面的判断是正确的。