前言
本篇记录的是Asp.net页面生命周期,也就是管道模型的最末端HttpHandler的生命周期。(Page继承了IHttpHandler接口。想了解管道模型,请参考asp.net管道模型(管线模型)之一发不可收拾)。如有不足请大家指出^_^!!
本篇主要参考:ASP.NET编程模型之页面生命周期十一步详解
《亮剑.net 深入体验与实战精要》
正文
1.页面实例化之前:Asp.net工作进程会确定是否需要分析和编译页面从而开始生命周期,或是否从缓存中读取已生成好的html页面而不开始生命周期;
2.页面实例化:这个阶段会检查该请求是否为回传,并且设置IsPostBack和UICulture属性等。(2012/9/27补充:此时HttpContext.Current.Session对象未实例化,所以无法引用)
3.页面预初始化(OnPreInit):此阶段a.将初始化在aspx文件声明的服务器控件和页面,当然也可以在这里生成动态服务器控件,并生成
页面的控件树;b.动态设置Theme属性。注意此时只是初始化了服务器控件和页面的框架和声明时设置的属性,而viewstate等还没有恢复,也不存
在回传值(但可以通过Request.Form来获取有效控件的回传值,只是还没复制到控件实例中。因为Request对象不是在HttpHandler
中实例化的,具体请参考:asp.net管道模型(管线模型)之一发不可收拾)。
4.页面初始化(OnInit):读取页面和控件的值,生成动态服务器控件。(2012/5/25补充:初始化后:Begin Tracking View State,此后的viewstate才会保存起来 )
(2012/9/27补充:此时HttpContext.Current.Session已经实例化了)
5.页面初始化完成(OnInitComplete):处理要求先完成所有初始化工作。(暂时不清楚哪些功能点要用到它)
6.加载页面状态(LoadPageStateFromPersistenceMedium):该事件只在IsPostBack为True时触发
(所以IsPostBack等属性要在实例化时就设置好了!)。注意该事件加载的是页面状态而不单单是ViewState,页面状态
(PageState)包含ViewState和ControlState。其中ViewState又有页面的ViewState和控件的
ViewState,而这里加载的ViewState中包含了这两种。该事件是管加载,不管恢复,所以执行该方法后控件和页面依然没有回传值和
ViewState值。(这里的ControlState具体用法有待研究:2012/5
/25补充:ControlState是一种特殊的ViewState,即使页面或网站禁用了ViewState,ControlState依然起作用。
在自定义控件时,涉及到的方法有LoadControlState、SaveControlState,要使用ControlState必须向页面注册
ControlState,注册方法为:Page.RegisterRequiresControlState(this),这样就可以用
ControlState保存控件的信息了!)
7.恢复页面ViewState(LoadViewState):如果上一步中加载的ViewState中含有页面ViewState那么该事件将
会被触发,否则跳过。什么是页面ViewState呢?其实就是直接以ViewState[key]=value形式设置的ViewState。恢复后调
用ViewState[key]就得到上次请求设置的值了!
8.恢复控件ViewState(控件的LoadViewState):每个服务器控件的祖父均为Control类,服务器控件就是通过继承
Control类的LoadViewState方法来恢复ViewState的。同样如果在第6步中加载的ViewState含有该控件的
ViewState,那么就执行该方法;
9.获取控件的回传值并设置控件到相应的属性上(控件的LoadPostData):存在回传值的服务器控件均继承了
IPostBackDataHandler接口,并实现了LoadPostData方法和RaisePostDataChangedEvent事
件。 protected override bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection
postCollection),其中参数postDataKey为服务器控件的服务器端ID,用于标识取哪个控件的回传值;参数
postCollection是所有回传值的集合,就是Request.Form里面有的这里都有。该方法会在postCollection中获取
postDataKey对应的回传值,然后跟第8步中恢复的ViewState值作对比(如果没执行第8步,则与控件的默认值作对比),如果不同则返回
true,否则返回false。2012/09/04新增:对于返回true的对象,将会保存其RaisePostDataChangedEvent事件到一个数组中,供后期使用。
好了,现在在aspx文件上声明的控件的状态和回传值都已经恢复和设置到控件实例中了。让我们继续探讨吧!
10.页面加载(OnLoad==Page_Load):我想大家都十分熟悉这个事件了,这里我们可以随心所欲地操作aspx文件上声明的控件了,
但除了在该事件中实例化的服务器控件。在该事件中可以实例化服务器控件并将其加入到页面的控件树中,就是form1.Controls.Add(服务器控
件实例)。如果IsPostBack为True,因为此时实例化的控件没有参与步骤8到9,所以当该控件加入到页面控件树时就会进入步骤8,执行完继续执
行Page_Load的其余代码,但这些控件还没获得回传值;
注意点1:以下情况实例化控件的话,将无法直接通过“控件变量.属性”的形式获取实例化控件的回传值,要通过this.form1.FindControl等形式获取。
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 TextBox tbx = new TextBox();
4 this.form1.Controls.Add(tbx);
5 }
可以改成这样来直接获取
public partial class _Default : System.Web.UI.Page
{
TextBox tbx = null;
protected void Page_Load(object sender, EventArgs e)
{
tbx = new TextBox();
this.form1.Controls.Add(tbx);
}
}
注意点2:对于DropDownList控件
当在aspx声明DropDownList控件,在Page_Load事件中添加option项可以这样写
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
ddl.Items.Add(new ListItem("1","1"));
ddl.Items.Add(new ListItem("2", "2"));
}
}
回传时会发现这两个option项均保存在控件ViewState中。
当在Page_Load事件中实例化DropDownList并加入页面控件树时(如下面法)
protected void Page_Load(object sender, EventArgs e)
{
DropDownList ddl = new DropDownList();
if (!IsPostBack)
{
ddl.Items.Add(new ListItem("1", "1"));
ddl.Items.Add(new ListItem("2", "2"));
}
this.form1.Controls.Add(ddl);
}
回传时发现这两个option项均没有保存在控件ViewState中。因此导致无法恢复option项。因此必须每次Page_Load都完全重新生成一次,如下:
protected void Page_Load(object sender, EventArgs e)
{
DropDownList ddl = new DropDownList();
ddl.Items.Add(new ListItem("1", "1"));
ddl.Items.Add(new ListItem("2", "2"));
this.form1.Controls.Add(ddl);
}
至于ms为什么这样设计还有待研究,求解答!
2012/5/25更新:
对于上面的问题在上阵子学习自定义控件时找到了答案,现在补充一下吧!这里涉及到
容易犯的误区——只要开启的ViewState,一切服务器控件的数据都将保存在ViewState中。其实不是这样,只有该控件执行了
TrackViewState后,在该控件上设置/修改的数据才会保存到ViewState中。那什么是TrackViewState呢?那么我们要认识
一个接口IStateManager,asp.net规定每个需要使用ViewState的类必须继承IStateManager接口,而
TrackViewState就是这个接口里面的方法,而该接口中还有一个只读属性
IsTrackingViewState,TrackViewState就是用来修改IsTrackingViewState返回的值的,只有IsTrackingViewState返回true,那么该控件的值才会在SaveViewState中保存到ViewState中(当然我们可以重写的时候让IsTrackingViewState永远返回false,那么控件的数据就无法保存到ViewState了)。
而TrackViewState的是在控件初始化的末期执行的,而上面的情况ddl先经历实例化,然后就添加列表项,在添加到页面控件树里面,当加入页面
控件树时ddl会马上追赶页面的生命周期到达“加载”这个阶段,当然ddl的TrackViewState在这时已经执行了,所有后面对ddl的修改将保
存到ViewState中,但应添加列表项的操作时再TrackViewState执行前的,所以列表项就不会保存到ViewState中。如果先把
ddl添加到页面控件树再添加列表项,那么列表项将会保存到ViewState当中去。
再补充——生命周期追赶:在后台代码中动态生成控件时,控件会处于其生命周期中的“实例化”阶段,当加入到页面控件树时就会同步到页面当前的生命周期阶段,而两个阶段之间的各个阶段控件都会经历
11.获取在Page_Load中实例化的控件的回传值并设置控件到相应的属性上(控件的LoadPostData):过程跟步骤9一样,只是给在Page_Load中实例化的控件一个得到回传值的机会,要好好珍惜哦!
12.控件回传值变化事件(RaisePostDataChangedEvent):2012/09/04修改:这里会遍历第9步和第11步中的保存RaisePostDataChangedEvent事件的数组,并逐一执行事件的处理函数对于第9步和第11步返回true的控件就会触发该事件,注意这里是一堆控件一起触发事件。(具体用途有待研究)
13.RaisePostBackEvent:但点击Button和ImageButton时会触发;(具体作用有待研究)
2012/5/25补充:
RaisePostBackEvent不单单是点击Button和
ImageButton时会触发,其实只要回传操作都会触发。Asp.net规定能实现通过点击、值变更等操作而触发回传操作的控件必须继承
IPostBackEventHandler接口,而RaisePostBackEvent就是该接口的方法。控件的所有上述回传操作都会触发RaisePostBackEvent方法,然后根据实际情况分配给不同的函数去处理。对于页面,页面上所有控件的上述回传操作均会触发RaisePostBackEvent方法,然后根据参数的不同由不同的函数去处理,而我们平常习惯在Asp:Button上OnClick写事件处理函数,其实该事件处理函数就是通过RaisePostBackEvent根据不同的参数来指定该函数来处理回传的。
14.页面验证(Validate):在IsPostBack为True并且页面有验证web服务器控件时触发。
15.回发事件处理:如Button的Click事件处理程序;注意——若在这里添加服务器控件,那么将不会触发loadviewstate和loadpostback。
16.页面加载完成(OnLoadComplete):此时页面加载完成了,服务器控件均完整并可用;
17.页面预呈现(OnPreRender):这里是设置控件属性并该设置能保存到ViewState的最后地方,当然也可以在第18步中设置;在
该方法执行前会先执行页面和控件的EnsureChildControl方法和执行设置了DataSourceID属性的控件的DataBind事件。
18.页面预呈现完成(OnPreRenderComplete);
17.保存页面状态(SavePageStateToPersistenceMedium):这里默认会将页面ViewState、控件
ViewState和控件ControlState等按base64编码序列化,保存到一个隐藏控件中。经过该事件后,再设置控件的属性(如
TextBox的Text、CssClass属性等,ViewState会保存控件的所有属性),结果能呈现到客户端,但回传时控件的ViewState依然为旧值;页面ViewState道理一样。如果设置了ViewState分块保存的话,会将ViewState分块保存在多个隐藏控件中。如下
如果隐藏域中的数据量过大,某些代理和防火墙将阻止对包含这些数据的页的访问。由于最大数量会随所采用的防火墙和代理的不同而不同,较大的隐藏域可能会出现偶发性问题。如果您需要存储大量的数据项,可以打开视图状态分块,这样会自动将数据分割到多个隐藏域。
ASP.NET框架提供了MaxPageStateFieldLength属性,用来获取或设置页状态字段的最大长度。其属性值表示页面状态字段的最大长度,以字节为单位。
微软官方网站以及很多文章介绍说,通过设置Page.MaxPageStateFieldLength属性可以指定块的最大字节数,且MSDN明确
说明此属性是公有的,笔者在VS 2005和VS
2008下测试结果是Page下面根本没有这个属性,所以采用在配置文件Web.Config中实现,如下:
- <system.web>
- <pages maxPageStateFieldLength="100" />
- <system.web>
当MaxPageStateFieldLength属性设置为正数时,发送到客户端浏览器的视图状态将分为多个隐藏字段,并且每个字段的值都小于在
MaxPageStateFieldLength属性中指定的大小;而如果MaxPageStateFieldLength属性设置为负数(默认值),则
表示不应将视图状态字段分成多个块区。另外,如果将MaxPageStateFieldLength设置非常小,会导致性能降低。
18.呈现(Render):此时对页面请求的处理算是告一段落,这里会将整个页面转换成html页面并保存到一个HtmlTextWriter对
象中,该对象会传递到Response.OutputStream中返回给客户端;(可以在这事件中截取转换后的html进行加工,然后将结果html字
符串写到Response.OutputStream中。后置ViewState就是这样做哦!具体请参考:网页优化系列三:使用压缩后置viewstate)
19.释放资源(Dispose):执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如内存的退出、数据库的连接等。
20.卸载(OnUnload):页面生命周期正式结束。
结束语
Asp.net页面生命周期中还有很多地方值得深入学习,这里只是作个小结和介绍,以后慢慢完善吧!!