接下来,
ProcessRequest 使页面经历了各个阶段:初始化、加载视图状态信息和回发数据、加载页面的用户代码以及执行回发服务器端事件。之后,页面进入显示模式:收集更新的视图状态,生成 HTML 代码并随后将代码发送到输出控制台。最后,卸载页面,并认为请求处理完毕。
实例化(Instantiate):控件在页面或其它控件通过调用其构造函数所实例化时。只有当一个控件在加入控件树中后,该步骤后的所有阶段才会发生。
初始化(Initialize):在此阶段中,控件树中的页面与全部控件通过默认的方式调用OnInit方法来触发Init事件,并完成初始化工作。在整个生命周期之前,页面首先建立初始的控件树;在初始阶段之前,为控件赋值。可以实现Page_Init方法来对页面的初始化进行影响,也可以重载控件的 OnInit方法来为控件提供初始化逻辑。在这个阶段,控件可以访问其包含的子控件,但却不能访问其父控件或更高层次的控件。
开始跟踪视图状态(Begin Tracking View State):这个阶段发生在初始化阶段的末属阶段。在该阶段中,页面自动调用控件的TrackViewState方法,从而启用视图跟踪。当控件提供复杂的属性时,可以重载该方法。
加载视图状态(仅用于回传过程)(Load View state):这个过程发生在回传时,而不是初始请求过程中。在此阶段中,控件会将其状态恢复到上一次请求处理完成之后的状态,同时,页面框架自动恢复ViewState字典。
加载回传数据(仅用于回传过程,为可选项)(Load Postback Data):只在控件通过实现IPostBackDataHandler接口参与了回传数据处理时,这个阶段才发生在回传中。在这个阶段中,控件通过从已发送的表单数据中利用IPostBackDataHandler接口的LoadPostData方法更新其状态。
加载(Load):直至该阶段开始,页面中所有控件都已被初始化,回恢复到它们先前周期的最后状态。]
引发修改事件(仅用于回传过程,可选项):只有在控件通过实现IPostBackDataHandler接口参与回传数据处理时,此阶段才会发和在回传中。在些阶段中,控件通过引发事件作为一种信号,即控件状态由于回传而修改。为了参与此阶段,控件必须实现IPostBackDataHandler接口的 RaisePostDataChangedEvent方法。
引发回传事件(仅用于回传过程,可选项):只有在控件通过实现 IPostBackEventHandler接口参与回传事件处理时,此阶段才会发生在回传中。在此阶段,可以通过[实现 IPostBackEventHandler接口的RaisePostBackEvent方法来实现逻辑,以便把客房事件映射到服务器端事件。
预生成(PreRender):在此阶段中,应该通过重载OnPreRender方法,执行在生成控件之前所需要的任何工作。
保存视图状态(Save View State):在此阶段,页面框架自动保存ViewState字典,如果控件需要自定义状态管理,必须通过重载SaveViewState方法来实现自定义状态恢复。这种方法只被EEnableViewState属性为真的控件所调用。
生成(Render)
卸载(Unload)
释放(Dispose)
页面初始化 |
Init |
|
加载视图状态 |
|
LoadViewState |
处理回发数据 |
|
任意实现 IPostBackDataHandler 接口的控件中的 LoadPostData 方法 |
加载页面 |
Load |
|
回发更改通知 |
|
任意实现 IPostBackDataHandler 接口的控件中的 RaisePostDataChangedEvent 方法 |
处理回发事件 |
由控件定义的任意回发事件 |
任意实现 IPostBackDataHandler 接口的控件中的 RaisePostBackEvent 方法 |
页面显示前阶段 |
PreRender |
|
保存视图状态 |
|
SaveViewState |
显示页面 |
|
Render |
卸载页面 |
Unload |
|
(1)
对象初始化Init事件:页面初始化的标志是Init事件。页面中的控件(包括页面本身)都是在它们最初的Form中被首次初始化的。在成功创建页面的控件树后,对应用程序激发这个事件。当Init事件发生时,在.aspx源文件中静态声明的所有控件都以实例化并取其默认值。应该注意到,这是还没有视图状态信息可供使用。虽然可以重载OnInit方法,但是系统并不保证这些控件实例是按照怎样的顺序被创建的。
(2)
加载视图:在初始化之后,页面框架立即加载该页面的视图状态(ViewState)。所谓视图状态就是一些名称/值对的集合,例如可以保存TextBox控件的ID和Text属性值。它一般被用于在一个往返行程中存留信息到服务器,即参与HTTP请求与响应。
页面视图状态被存储在<input type=”hidden”>字段中,做为_VIEWSTAE的值进行记录。该视图状态通过ASP.NE自动维护。通过重写 LoadViewState方法组件,开发人员可控制如何还原视图状态以及如何将其内容影射到内部状态。LoadViewState方法就是从 ViewState中获取上一次的状态,并按照页面的控件树的结构,用递归来遍历整个树,将对应的状态恢复到每一个控件上。
(3)
处理回发数据:还原了视图状态,页面树种的各个控件的状态就与浏览器上次呈现该页面时这些控件所处的状态相同。下一步需要更新这些控件的状态以发送给客户端。
回发数据处理阶段是各个控件有机会更新其状态,以便准确的反映相应的HTML元素在客户端的状态。例如,一个服务器TextBox控件对应的HTML元素是 <input type=text>,在回发数据阶段,TextBox控件将检索<input>标记的当前值并用它刷新其内部状态。每个控件负责从以发送的数据中提取相应值,并更新其某些属性。TextBox控件将更新Text属性,而CheckBox控件将刷新其Checked属性。服务器控件和 HTML元素之间的匹配关系由二者的ID确定。
页框架将在每个提交数据的控件上实现IpostBackDataHandler接口,然后激发LoadPostData事件,通过页面解析发现实现了 IpostBackDataHandle接口的控件,这样就能正确的回传数据更新控件状态。在识别控件时,ASP.NET通过匹配控件的唯一标示符来更新正确的控件,该标识符具有名称值集和中的名称值对。这也就是在所有特定的页中每个控件都需要一个唯一标识符的原因之一。其他的步骤都由框架来完成,例如确定每个标识符在环境中是否唯一以及控件的基本属性等。
LostPostData方法的原型如下:
Public virtual bool LoadPostData(string postDatakey, NameValueCollection postCollection)
PostDataKey 是标识控件的关键字,可以理解为控件的ID,postCollection是包含回发数据的集合,可以理解为视图状态值。该方法返回一个bool值,如果是true,则表示控件状态因回发而更改;否则返回false。页框架会更跟踪所有返回true的控件并在这些控件上调用 RaisePostDataChangeEvent事件。
LoadPostData方法是由System..Web.WebControls.Control定义的,而添加的每一个服务器控件也是从System..Web.WebControls.Control继承的,所以对于数据的回发处理并不需要干预。
(4)
加载页面Load:在回发数据处理阶段结束时,页面中的所有控件都根据客户端上所输入的更改来更新的状态。此时,对页面激发OnLoad事件。对于这个事件,相信大多数朋友都会比较熟悉,用Visual Studio.Net生成的页面中的Page_Load方法就是响应Load事件的方法,对于每一次请求,Load事件都会触发,Page_Load方法也就会执行。可以利用该方法执行一些页面初始化,例如准备好数据库的连接字符串。在事件引用中,为了提高性能,通常使用Page类的IsPostBack 属性判断是不是数据回发。
(5)
回发更改通知RaisePostDataChanged:如(3)所述,在所有实现了IpostBackDataHandler接口的控件被正确的回传数据更新后,每个控件都有一个布尔值的标识,标识其自上一次提交后改控件的数据是被更改还是保持其值。然后ASP.NET通过搜索页来寻找任何显示控件数据被更改的标识并激发RaisePostDataChanged。RaisePostDataChanged事件直到Load事件发生后,所有控件被更新后才激发。这保证了在控件被回传数据更新前,其他控件的数据在RaisePostDataChanged事件中没有被手动更改过。虽然也可以在Page的基础上自己定义数据更改的事件,但通常这个事件由太大用处。
(6)
处理回发事件RaisePostBackEvent:当回传更新导致数据改变而引发服务器端事件后,引发回传的对象会在 RaisePostBackEvent事件中被处理。这种引发回传的对象往往是一个按钮被单击或者其状态改变而引发回传的控件。例如Button触发乐 Onclick事件、客户端修改了某个文本框的文本、同时将AutoPostBack设置为true、触发TextChanged事件等。
很多代码都在这个事件中执行,因为这是控制事件驱动逻辑的理想位置。为了保证呈现到浏览器的数据的正确性,在一系列的回传事件后, RaisePostBackEvent事件最终被激发。基于一致性考虑,会传中改变的控件直到这个函数被执行后才被更新。在实际的ASP.NET开发工作中要做的工作就是在此事件发生前处理代码。
(7)
预呈现PreRender:在处理回发事件后,页面就准备进行呈现。这一阶段的标志是PreRender事件。各个控件可利用这个很好的时机,以便执行任何需要在保存视图状态和呈现输出结果的前一刻完成得最后一些更新操作。最终请求的处理都会转变为发挥服务器的响应,预呈现这个阶段就是执行在最终呈现之前所做的状态的更改,因为在呈现一个控件之前,必须更具它的属性来产生HTML,比如Style属性。这是典型的例子,这预呈现之前,可以更改一个控件的 Style,当执行预呈现时,就可以把Style保存下来,做为呈现阶段显示HTML的样式信息。
(8)
保存状态SaveViewState:下一个状态为SaveViewState,在这一状态中所有控件以及页面本身可以刷新自己的SaveState集合的内容。所得到的视图状态随后得以序列化、进行哈希运算、进行Base64编码并关联到VI-EMSTATE隐藏自端。
(9)
呈现视图Render:到这里,实际上页面对请求的处理基本就告一段落了,在Render事件中,也调用对象是它们呈现为HTML,然后也收集HTML发送给客户。客户接收到HTML标记后进行重组,最终显示给客户。当Render事件被重载时,开发者可以为浏览器创建定值的HTML,此时页面创建的任何 HTML都还没有生效。Render方法用HtmlTextWriter对象做参数并由它产生HTML送给浏览器。这主要用于自定义控件的开发。
(10)
处置Disposed:执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如内存的退出、数据库的连接等。
(11)
卸载Unload:一个页面的最后生存标志就是Unload事件,该事件在页面对象被解除之前发生。在此事件中,可以调用Dispose方法尽可能释放占用的任何关键资源(例如,文件、图形对象以及数据库连接)。
下面是ASP.NET页面周期的过程:
1. Page_Init();
2. Load ViewState;
3. Load Postback data;
4. Page_Load();
5. Handle control events;
6. Page_PreRender();
7. Page_Render();
8. Unload event;
9. Dispose method called;
下面对其中的一些过程作下描述:
1. Page_Init();
这个过程主要是初始化控件,每次页面载入执行这个初始过程,包括第一次和以后的Postback(这里说下Postback,其实就可以简单理解成用户点
击SUBMIT按钮之类的,把表单<Form>提交给服务器,这就是一次postback),在这里面可以访问控件,但是这里面的控件值不是我们期待的控件里面
的值,他只是一个控件的初始值(默认值),举例: 比如一个TextBox1,我们填入了"哈哈",在点击SUBMIT提交了页面后,在Page_Init()里面,我们访
问到的TextBox1.Text不是我们的"哈哈",而是开始的""空字符串,如果TextBox1在我们设计的时候提供了默认值,这里访问到的也就是提供的默
认值,为什么呢,这就要看下一个过程了.
对应的事件Page.Init
2. Load ViewState
这个过程是载入VIEWSTATE和Postback数据,比如我们上面的TextBox1,这时就赋了"哈哈",所以,在Post_Init()对控件赋值是无意义的,它都会
在这个过程里被改写,当然第一次页面载入例外,因为没有VIEWSTATE数据。
没有对应的事件
3.Load Postback data;
上面说了,Postback可以理解成用户提交表单数据,所以这里就是处理表单数据,当然这里要设计到控件的设计,一般情况不会要我们自己处理这
个过程,我们暂且略过. (在以前那篇关于ASP.NET页面生命周期的简单描述中,把这个过程和Load ViewState放在了一起,其实那是微软提供的生命周期过程,这里单独提出来是为
了让大家明白这是一个单独的过程)
没有对应的事件
4. Page_Load();
这个过程也是每次页面载入时一定会执行的,但是注意和Page_Init的区别,上面已经涉及了,这里注意的是一般都会用到Page.IsPostBack,该
值指示该页是否正为响应客户端回发而加载,或者它是否正被首次加载和访问。
private void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{
//第一次执行的CODE HERE
}
else
{
//用户提交FORM(即Postback)CODE HERE
}
//每次这里的都回执行CODE HERE
}
对应的事件Page.Load
5. Handle control events;
这个过程里,相应具体的控件事件,比如private void ListBox1_SelectedIndexChanged(object sender, System.EventArgs e)事件等等
没有对应的事件(我们自己的事件函数都包括在这个过程里比如上面的ListBox1_SelectedIndexChanged)
6. Page_
预先呈递对象,这里是在向用户程序呈现数据的倒数第二步,我估计提供这个过程的意义,也就是在这里能对控件属性等等要呈现给用户的数据进
行修改,这也是最后的修改,以前的修改(比如在Page_Init里面)都可能被覆盖.做完这了还会进行一个操作就是保存状态,即SaveViewState.
对应的事件时Page.PreRender
7. Page_Render();
大家可以在浏缆器里View->Source查看到,每个页面都有一个隐藏的<input>,这里面的"__VIEWSTATE"就是我们服务器写回来的页面状态信息,
在这个之前,服务器要呈现页面(也就是构造HTML格式的文件),就是从这个"__VIEWSTATE"里面获取的数据,当然大家也注意到了,这里有个 Page.Render事件,我们可以添加自己的处理代码,也就是说我们又可以更改数据,不过这里推荐不要在这里修改,既然提供了PreRender,就应该在里面做最后的修改,当然这不是必须的,只是推荐!
对应的事件Page.Render
8. Unload event;
大家应该明白,当想服务器请求一个对象的时候,就会在内存里生成一个继承页面对象,也就是页面的类,它继承自System.Web.UI.Page.
当页面对象从内存中卸载时发生,将触发该事件.
对应的事件Page.Unload
9. Dispose method called;
销毁所有的对象.当从内存释放Page时发生,这是生存期的最后阶段。可能第8和9似乎有些模糊,不过我也没怎么搞清楚,待研究!
对应的事件Dispose
以上就是ASP.NET页面周期的描述。
注意上面灰色背景的文字,如果一个过程中有对应的事件,我们可以自己定义一个函数(当然先在MSDN中找到函数原型),然后在
InitializeComponent中向事件的链表上添加上去,像下面:
private void InitializeComponent()
{
this.Unload += new System.EventHandler(this.MainWebForm_Unload);
this.Load += new System.EventHandler(this.Page_Load);
this.Init += new System.EventHandler(this.Page_Init);
this.PreRender += new System.EventHandler(this.My_PreRender);
}
对于几个没有对应事件的过程,比如2.Load ViewState,我们可以重载Page的虚函数protected override void LoadViewState(object
savedState);来添加自己的控制代码,不过切忌掉用基类的对应方法,比如:
protected override void LoadViewState(object savedState)
{
//自己处理VIEWSTATE
base.LoadViewState (savedState);
}