asp.net web 页面生命周期

每次客户端请求都会创建页面实例,它的执行使自身及其包含的控件经历页面生命周期的各个阶段。页面的执行起始于HTTP运行库调用ProcessRequest时,该方法将启动页面并控制它的生命周期。生命周期由一系列阶段和步骤组成。一些阶段可以通过用户编码的事件进行控制,而一些需要对方法进行重写。其他阶段(更确切地说是子阶段)没有被公开,因而开发者无法控制。

页面的生命周期可以分为三个阶段:建立阶段、回发阶段和终结阶段。每个阶段会有子阶段,分别由若干步骤和事件引发点组成。这里描述的生命周期包括所有可能的路径。注意,具体情况会因返回过程(跨页投递、脚本回调和回发)的不同而略有不同。

3.3.1  页面的建立

当HTTP运行库实例化一个页面类对当前请求进行处理时,页面的构造函数会生成一个控件树。该控件树会关联至实际的类,这些类是由页面解析器在扫描ASPX源代码后创建的。值得一提的是,当请求的处理过程开始时,所有子控件和页面的内部对象(如HTTP上下文对象、请求对象和响应对象等)都会被设置。

页面生命周期的第一个阶段是确定运行库处理当前页面请求的原因。这个原因有很多种:常规请求、回发、跨页投递或回调。基于实际的原因,页面对象会配置其内部状态,如果包含被投递的值,还会根据请求的方法(GET或POST)准备该值的集合。在第一个阶段过后,页面便为引发事件来执行用户代码做好了准备。

PreInit事件

这是ASP.NET 2.0引入的事件,它是页面生命周期的入口点。该事件被引发时,页面尚未与母版页和主题相关联。但页面滚动条位置已被恢复,被投递的数据变为可用,且所有的页面控件已被实例化,其属性也已基于在ASPX源中的默认值进行了设置(注意,如果没有在.aspx源中显式指定,这时的控件是没有ID的)。在这个阶段中,可以对母版页进行调换,或对主题进行编程。该事件仅对页面有效。IsCallback、IsCrossPagePostback和IsPostBack会在这时被设置。      【132】

Init事件

在这个阶段,母版页和主题(如果分别存在)会被设定,不能再被更改。页面处理程序(即Page类的ProcessRequest方法)开始执行,对所有子控件进行迭代,使其有机会在上下文环境下初始化它们的状态。所有子控件都有自己的OnInit方法,后者以递归方式被调用。对于控件集合中的每个控件,都设置有命名容器和特定ID(如果没有在源中分配)。

Init事件首先会处理子控件,然后是页面。在这个阶段,页面和控件通常开始加载其部分状态。此时,视图状态尚未被恢复。

InitComplete事件

该事件是ASP.NET 2.0引入的,页面专有,用于指示初始化子阶段的结束。对于页面来说,在Init和InitComplete事件之间只有一个操作会执行——启用视图状态的变更跟踪功能。视图状态的跟踪是这样的一种操作,它最终使控件能够真正地将所有以编程方式添加到ViewSatae集合中的值,存储在持久性介质中。简而言之,对于没有实施视图跟踪的控件,添加到其ViewState中的值将在回发间丢失。

在控件引发各自的Init事件后,视图状态跟踪会立即启动,页面也不例外(归根结底,页面也是一种控件)。

要点:对于上述说明,有一点需要注意:在InitComplete前,任何写入ViewState集合的                                值,在下一次回发时都不再可用。对于ASP.NET 1.x,必须等待Load事件被引发,才能安全地更改页面和控件的视图状态。

视图状态的恢复

如果页面因回发而被处理(即,IsPostBack属性为true),隐含字段__VIEWSTATE的值会被恢复。隐含字段__VIEWSTATE用于在请求结束时,保存所有控件的视图状态。页面的整体视图状态是一种调用上下文,包含页面每个组成控件上一次发往浏览器的状态信息。

在这个阶段,每个控件会获得更新其当前状态的机会,恢复其上一次请求时的状态。视图状态的恢复过程不会引发任何事件。如果需要对此进行定制,则必须借助于LoadViewState方法的重写(该方法在Control类中为受保护的虚拟成员)。                                                                                                                             【133】

处理被投递的数据

HTTP请求中打包的所有客户端数据(即所有定义在<form>标签中输入字段的内容),会在这时被处理。被投递的数据通常采用下面这种形式:

TextBox1=text&DropDownList1=selectedItem&Button1=Submit

该字符串用的键/值(name/value)对被“&”分隔。这些值会被加载到一个内部使用的集合当中。页面处理程序会试图寻找被投递集合中的名称与页面中控件ID的匹配项。如果找到匹配项,处理程序会检查该服务器控件是否已实现IPostBackDataHandler接口。如果已实现,该接口的方法会被调用,为使用被投递数据更新该控件状态提供机会。具体来讲,页面处理程序会调用该接口的LoadPostData方法。如果它返回true(即,状态已被更新),该控件将被添加到一个独立的集合中,等待进一步指示。

如果没有找到与被投递的名称对应的服务器控件,它将被搁置于一临时的独立集合中,稍后再试。

PreLoad事件

PreLoad事件是ASP.NET引入的,仅用于指示页面已完成系统级的初始化,即将进入另一个阶段。接下来的阶段会为页面中用户代码的执行提供机会,为执行和呈现对该页面做进一步配置。只有页面会引发该事件。

Load事件

Load事件首先由页面引发,之后以递归方式分别由所有的子控件引发。这时,页面中的控件树会被创建,各控件的状态完全反映之前的状态,并获得了从客户端投递过来的所有数据。对于执行处理逻辑和页面行为的初始化代码,页面已做好准备。这时访问控件属性和视图状态是绝对安全的。

处理动态创建的控件

当页面中所有控件完成了显示前的初始化后,页面处理程序会对那些没有与现有控件对应的被投递数据做再次尝试。该行为(在之前的“处理被投递的数据”一节中进行过说明)会对之前搁置的键/值对再次处理。显然,这一特殊的方式提供了一种不可思议的特殊方          案——使用动态创建的控件。

可以想象,就是将控件动态地添加到页面的树中(例如,为响应某个用户发出的动作)。如前所述,每次回发,页面都要根据原状态重新生成,因而,任何有关动态创建控件的信息会丢失。另一方面,当页面的窗体提交后,动态控件会被填充合法且有效的信息,并以常规方式投递。根据设计,初始的动态控件的ID不可能与某个服务器控件的ID匹配。然而,ASP.NET会识别可能Load事件中被创建的控件。这样,在用户代码运行一段之后,便可以再次尝试寻找可能的匹配项。                                                                                                                    【134~135】

如果动态控件在Load事件中已被创建,就可能找到匹配项,从而该控件可以使用被投递的数据对状态进行刷新。

3.3.2  回发的处理

回发机制是ASP.NET编程的核心。其过程为,将窗体数据投递到原页面,使用视图状态恢复调用上下文(即,上一次投递页面后,在服务器端生成的控件状态)。

在页面被初始化,且被投递值也已就位后,便可以引发服务器端的事件。这些事件主要分为两类。第一类事件标志着特定控件在回发间状态发生了改变。第二类事件是在服务器端对引发投递的客户端动作的响应。

控件状态变化的检测

整个ASP.NET系统隐含了这样第一个假设:在浏览器运行的某些HTML标签与活跃在服务器端的某些ASP.NET控件,必须存在一一对应关系。<input type="text">和TextBox控件间的对应就是一个典型的例子。为从技术上严格执行,该联系通过相同的ID名称表示。当用户向输入元素键入某些新文本,并将其投递到服务器后,对应的TextBox控件(即,与该输入控件ID相同的服务器控件)会被调用,以处理被投递的值。这个过程已在“处理被投递的数据”一节中做了说明。

对于所有LoadPostData方法返回ture的控件,这时会执行那个IPostBackDataHandler接口的另一个方法——RaisePostDataChangedEvent方法。该方法发出信号给控件,通知ASP.NET应用程序该控件的状态已被更改。该方法的实现取决于具体控件。然而,大多数控件会做这样一件事:引发服务器事件,为页面的设计者提供一个介入的机会,以便对特定情况做处理。例如,如果TextBox的Text属性在回发间被更改,该TextBox会向宿主页面引发TextChanged事件。

服务器端回发事件的执行

任何有意触发服务器端动作的客户端动作会引起回发。例如,单击一个客户端按钮会将当前显示的窗体内容投递到服务器,从而需要某个动作和新的、已刷新的页面输出。客户端按钮控件(一般为超链接或提交按钮)与实现IPostBackEventHandler接口的服务器控件相                        关联。                                    【135~136】

页面处理程序会分析被投递的数据,确定引起回发的控件。如果该控件实现了IPostBackEventHandler接口,处理程序会调用该接口的RaisePostBackEvent方法。该方法的实现由每个控件本身来完成,具体方式对于不同控件会所不同(至少在理论上如此)。然而,实际上任何主动投递(posting)控件都会引发服务器事件,以便页面的设计者能够编写代码,对回发做出响应(例如,Button控件会引发onclick事件)。

页面回发到服务器有两种方式。一是通过提交按钮(即,<input type="submit">),二是通过脚本。HTML提交按钮会由服务器控件Button生成。LinkButton控件,以及其他的一些可回发控件,会将一些脚本插入到客户端页面中,并将自身的某个HTML事件(如onclick)绑定到浏览器HTML对象模型中的窗体submit方法上。第4章将介绍这一主题。

提示:在窗体提交方面,自ASP.NET 2.0版开始,Button类获得了一个新属性UseSubmit- Behavior,使页面开发者能够控制客户端相应HTML元素的行为。在ASP.NET 1.x中,Button控件总会输出<input type="submit">元素。而在ASP.NET 2.0和更高版本中,通过将UseSubmitBehavior设置为false,我们能将输出更改为<input type="button">,同时,客户端元素的onclick属性会被绑定到预定义的脚本代码(仅用于回发)上。

LoadComplete事件

LoadComplete事件是ASP.NET 2.0版引入的,仅适用于页面,用于通知页面准备阶段的结束。值得一提的是,子控件不会引发该事件。在引发LoadComplete后,页面便进入了呈现阶段。

3.3.3  页面的终结阶段

处理过回发事件之后,页面便为浏览器生成输出做好了准备。呈现阶段分为两部分:预呈现和标记生成。预呈现这个子阶段由两个事件表征,分别为:预处理和投递处理。

PreRender事件

通过处理该事件,页面和控件可以进行生成输出前的任何更改。页面会首先引发自己的PreRender事件,然后按递归方式引发所有子控件的。注意,这时的页面已确保其所有子控件都被创建。对于组合控件来说,该阶段尤为重要。    【136~137】

PreRenderComplete事件

由于所有子控件的PreRender事件会以递归方式逐一被调用,因而页面设计者无法知道预呈现阶段是否已经完成。为此,ASP.NET 2.0版引入了一个只针对页面的新事件,即PreRenderComplete事件。

SaveStateComplete事件

在每个控件被呈现输出,为页面生成标记前的一段时间里,要将当前页面的状态存储在视图状态介质中。值得一提的是,在该点之后对状态做的任何修改可能会被呈现,但不会被存储,且在下一次回发时无法获得恢复。页面状态存储是一个递归的过程,页面处理程序会遍历整个页面树,逐一调用每个控件的SaveViewState方法,包括页面自身的。SaveViewState是受保护的虚拟方法(可以重写),负责存储当前控件的ViewState字典内容。(第14章将讨论ViewState字典。)

从2.0版开始,ASP.NET为控件提供了另一种状态类型,即所谓的“控件状态”(control state)。控件状态是一种私有的状态,不受应用程序控件支配。换句话说,控件的控件状态不能够像视图状态一样,以编程方式被禁用。控件状态在此时也会被保存。它是另一种状态存储机制,与视图状态类似,其数据也用于维护页间回发,但控件状态的目的是,维护控件的必要信息,以便使其工作正常。也就是说,具有状态行为的属性数据应保存在控件状态中,而用户界面(UI)属性数据(如控件的内容)应保存在视图状态中。

SaveStateComplete事件是ASP.NET 2.0引入的,当页面中的所有控件都已完全存储在持久性介质中后,该事件就会被引发。

提示:页面和独立控件的视图状态会在一个特殊的内存结构中聚集,然后再保存于存储介质中。在默认情况下,该持久性介质是一个名为__VIEWSTATE的隐含字段。对存储介质中数据的序列化和反序列化会由Page的两个可重写方法处理:SavePageStateToPersistenceMedium和LoadPageStateFromPersistenceMedium。例如,通过重写这两个方法,我们可以在服务器端,将页面的状态存储在数据库或会话状态中,从而极大地减小了发送给用户的页面大小。这并不是一个简单的话题,第15章将对此做详细解释。                                                                               【137】

标记的生成

针对浏览器的标记生成,会分别由每个子控件自己完成,生成的标记会被存储到一个缓冲中。有几个可重写方法会帮助开发者分别对标记生成的阶段进行控制——开始标签、主体和结束标签。没有与该阶段项关联的用户事件。

Unload事件

呈现阶段之后会进行一次递归调用,引发每个控件的Unload事件,最后是页面自身的。Unload事件用于在页面对象被释放前,执行最终的清理工作。一般的操作为文件和数据库连接的关闭。

注意,卸载通知会在页面或控件正要被卸载且尚未被释放前到达。对于实际的页面,重写Page类的Dispose方法,或只处理页面的Disposed事件,是在它从内存中被释放前做清理工作的最后机会。页面处理程序会调用Dispose方法,释放页面对象。在对Unload事件处理程序的递归调用完毕之后,会被立即引发。

 

public partial class _Default : System.Web.UI.Page 
 2 {
 3     protected void Page_PreInit(object sender, EventArgs e)
 4     {
 5         Response.Write("Page_PreInit<br>");
 6     }
 7     protected void Page_Init(object sender, EventArgs e)
 8     {
 9         Response.Write("Page_Init<br>");
10     }
11     protected void Page_InitComplete(object sender, EventArgs e)
12     {
13         Response.Write("Page_InitComplete<br>");
14     }
15     protected void Page_PreLoad(object sender, EventArgs e)
16     {
17         Response.Write("Page_PreLoad<br>");
18     }
19     protected void Page_Load(object sender, EventArgs e)
20     {
21         Response.Write("Page_Load<br>");
22     }
23     protected void Page_LoadComplete(object sender, EventArgs e)
24     {
25         Response.Write("Page_LoadComplete<br>");
26     }
27     protected void Page_PreRender(object sender, EventArgs e)
28     {
29         Response.Write("Page_PreRender<br>");
30     }
31     protected void Page_PreRenderComplete(object sender, EventArgs e)
32     {
33         Response.Write("Page_PreRenderComplete<br>");
34     }
35     protected void Page_SaveStateComplete(object sender, EventArgs e)
36     {
37         Response.Write("Page_SaveStateComplete<br>");
38     }
39     protected void Page_Unload(object sender, EventArgs e)
40     {
41         //Response.Write("Page_Unload<br>");
42         int i = 0;
43         i++;//这里不能用Response.Write
44     }
45     protected void Button1_Click(object sender, EventArgs e)
46     {
47         Response.Write("Button事件触发!<br>");
48     }
49 }
50 

输出结果如下:
Page_PreInit
Page_Init
Page_InitComplete
Page_PreLoad
Page_Load
Page_LoadComplete
Page_PreRender
Page_PreRenderComplete
Page_SaveStateComplete
单击Button1按钮后,输出结果如下:

Page_PreInit
Page_Init
Page_InitComplete
Page_PreLoad
Page_Load
Button事件触发!
Page_LoadComplete
Page_PreRender
Page_PreRenderComplete
Page_SaveStateComplete

阅读更多
换一批

没有更多推荐了,返回首页