了解控件的生命周期:
其中Init是由内而外,即先子控件后父控件,Load等相反。
在Init之前控件树根据声明语法已经生成
控件状态和视图状态:
控件状态是专门为维护控件的核心行为功能而设计的,而 ViewState 只包含维护控件内容 (UI) 的状态。应该按照这个规则设计控件状态。
先看一个例子:改自asp服务器控件和组件开发一书
PageTracker,用来跟踪页面会话状态和程序状态收到的点击数目,并计算页面往返时间
跟踪方式:
public enum PageTrackingMode
{
ByApplication,
BySession,
ByTripTime
}
控件代码:
[
DefaultProperty("TrackingMode")
]
public class PageTracker : WebControl {
private TimeSpan _tripTime;
[
Category("Appearance"),
DefaultValue("{0}"),
Description("The formatting string used to display the value being tracked.")
]
public virtual string FormatString {
get {
string s = (string)ViewState["FormatString"];
return ((s == null) ? "{0}" : s);
}
set {
ViewState["FormatString"] = value;
}
}
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public int Hits {
get {
PageTrackingMode mode = TrackingMode;
object o = null;
if (mode == PageTrackingMode.ByApplication) {
o = Page.Application[HitsKey];
}
else if (mode == PageTrackingMode.BySession) {
o = Page.Session[HitsKey];
}
else {
throw new NotSupportedException("Hits is only supported when TrackingMode is PageTrackingMode.ByApplication or PageTrackingMode.BySession");
}
return ((o == null) ? 0 : (int)o);
}
}
[
Category("Behavior"),
DefaultValue(PageTrackingMode.ByApplication),
Description("The type of tracking to perform.")
]
public virtual PageTrackingMode TrackingMode {
get {
object mode = ViewState["TrackingMode"];
return ((mode == null) ? PageTrackingMode.ByApplication : (PageTrackingMode)mode);
}
set {
if (value < PageTrackingMode.ByApplication || value > PageTrackingMode.ByTripTime) {
throw new ArgumentOutOfRangeException("value");
}
ViewState["TrackingMode"] = value;
// 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.
// 因为 Application and Session
// 在设计时不可用,所以会出现异常
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;
}
}
}
[
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public TimeSpan TripTime {
get {
if (TrackingMode != PageTrackingMode.ByTripTime) {
throw new NotSupportedException("TripTime is only supported when TrackingMode is PageTrackingMode.ByTripTime");
}
return _tripTime;
}
}
protected override HtmlTextWriterTag TagKey {
get {
return HtmlTextWriterTag.Div;
}
}
private string HitsKey {
get {
// Create a unique HitsKey for the page for keying
// in hits per page in the Application and Session objects.
return Page.GetType().FullName;
}
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
switch (TrackingMode){
case PageTrackingMode.ByApplication:
// Update the page hits in the Application
// object. This operation needs a lock because
// multiple users could access a page at the same
// time.
lock(Page.GetType()){
Page.Application[HitsKey] = Hits + 1;
}
break;
case PageTrackingMode.BySession:
Page.Session[HitsKey] = Hits + 1;
break;
case PageTrackingMode.ByTripTime:
// Get the timestamp for the previous request
// from ViewState and compute the difference
// between the previous
// and current timestamp.
object timeStamp = ViewState["TimeStamp"];
DateTime requestTime = Context.Timestamp;
if (timeStamp == null) {
_tripTime = TimeSpan.Zero;
this.Visible = false;
}
else {
this.Visible = true;
_tripTime = (requestTime - (DateTime)timeStamp);
}
ViewState["TimeStamp"] = requestTime;
break;
}
}
protected override void RenderContents(HtmlTextWriter writer) {
switch (TrackingMode){
case PageTrackingMode.ByApplication:
case PageTrackingMode.BySession:
writer.Write(FormatString, Hits);
break;
case PageTrackingMode.ByTripTime:
writer.Write(FormatString, TripTime.TotalSeconds);
break;
}
}
}
调用:
<
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对,为什么会如此呢?
原来,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
>
仍然只有两个,因为另外两个仍没有被修改过,不会被序列化。
简单属性:
简单属性是可以映射到文本的属性,他们一般有内置的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;
}
}
控件状态实际上就是自定义的视图状态,现在简单的分析一下:
通过调用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];
}
可以用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;
}
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();
}
protected
virtual
void
AddParsedSubObject(
object
obj)
{
Control control1 = obj as Control;
if (control1 != null)
{
this.Controls.Add(control1);
}
}
可以看到在执行Init前,会编译运行上面自动生成的编译源,这样以来第一个问题就彻底清楚了,也证明我上面的判断是正确的。