陈斌彬的技术博客

Stay foolish,stay hungry

ASP.NET页面揭秘之页面生命周期

首先简单介绍下Page类。

在.NET Framework中,Page类提供了ASP.NET应用程序从.aspx文件开始创建的所有对象的基本行为。Page类在System.Web.UI命名空间中定义,它派生于TemplateControl类并实现了IHttpHandler接口。TemplateControl类是一个抽象类,它为 Page 类和 UserControl 类提供通用属性和方法。

由于Page类派生于一个是实现了INamingContainer接口的类,Page类还充当它的所有组成控件的容器。一个控件命名容器实现了INamingContainer接口的第一个父控件。对于任何一个实现了命名容器接口的类,ASP.NET创建一个新的虚拟命名空间,其中保证所有的子控件在整个控件树有唯一的名称。通俗点INamingContainer接口无任何方法,只是保证其子控件有唯一的名称。

Page类实现IHttpHandler接口的方法,从而可以充当一种特殊类型的HTTP请求(.aspx文件的请求)的处理程序。ASP.NET运行库调用ProcessRequest方法用来处理请求。

在ASP.NET页面中,runat属性设置为server的任何控件都不能放在<form>标签外。那么是怎么做到确定所有控件不在form外的呢?可以用Page类的VerifyRederingInSeverForm()方法。控件在生成时调用该方法,以确保服务器窗体的主体包含它们。该方法不返回一个值,但是出错的情况下会抛出一个异常。

页面生命期分三个阶段:建立阶段,回发阶段,结束阶段。每个阶段有一个或多个子阶段,并且由一个或多个事件和步骤引发组成。

一 页面的建立

当HTTP运行库实例化页面类以便为当前提供服务时,页面构造器创建一个控件树。该控件树连接到页面解析器在查看ASPX源文件后所创建的实际类。处理请求开始时,应设置所有的子控件和页面本征特征(如HTTP上下文,请求对象和响应对象)。

页面生命期第一步是确定为什么运行库正在处理页面请求。其中原因有多样:正常请求,回发,跨页回发或者回叫。页面对象根据具体原因配置内部状态,并根据请求方法(GET或POST)准备投递的值集合(post value)。这部完成后,页面就准备激发用户代码事件。

1.PreIntit事件

它是页面生命周期进入点。该事件激发时,页面还没有关联任何的母版页和主题。页面滚动位置已经恢复,投递的数据可以用,并且所有的页面控件已经被实例化且默认地取ASPX源文件中定义的属性值(注意这时控件没有ID,除非在.aspx源文件中显式地设置它)只有这时才可以通过编程方式修改母版页或主题。该事件只有在页面上可用。IsCallBack(该值指示页请求是否是回调的结果),IsCrossPagePostback(获取一个值,该值指示跨页回发中是否涉及该页)和IsPostback(获取一个值,该值指示该页是否正为响应客户端回发而加载,或者它是否正被首次加载和访问)都在这时设置其值。

2.Init 事件

这时已经设置母版页和主题,并且不能修改。页面处理器,即Page类上的ProcessRequest方法,遍历所有的子控件,使他们有机会以一种上下文相关的方法初始化它们的状态。所有的子控件递归地调用它们的OnInit方法。为控件集合中的每个控件设置命名容器和一个具体的ID。Init事件首先到达子控件,然后到达页面。这个阶段,页面和控件通常开始加载他们的部分状态。然而这时没有恢复视图状态。

3.IntiComplete事件

这个只有页面才有的事件,表明初始化子阶段的结束。在Init 事件和IntiComplete事件之间只发生一个操作:打开视图状态变更的跟踪功能。它始终使控件能够真正地把所有以编程方式添加到ViewState结合中的数值持久地存储在存储介质中。即没有打开跟踪状态的控件,任何添加到ViewState的值将会在下一次回发时丢失。所有控件在发出Inti事件后立即打开视图状态跟踪,页面也不例外。

4.视图状态恢复

如果页面由于回发处理,则恢复VIEWSTATE隐藏字段内容。_VIEWSTATE隐藏字段保存着一个请求结束时所有控件视图状态。页面的总体视图状态是一种调用上下文,包含页面最后一次服务器时每个组成控件的状态。(ViewState个可以留到控件相关的知识里介绍)

5.处理投递的数据

HTTP请求中包装的所有用户数据,即用<form>标签定义的所有输入字段的内容,都在这时进行处理。投递的数据通常采取如下格式:

  TextBox1=text&DropDownList1=selectedItem&Button1=Submit

这是一串用&隔开的名称/值对。这些值装入一个内部使用的集合。页面处理器视图将投递的数据集合中的名称与页面中的控件的ID进行匹配。每找到一个匹配,处理器检查该服务器控件有没有实现 IPostBackDataHanlder接口。如果实现了,则调用该接口的LoadPostData方法,该控件有机会根据投递的数据刷新它的状态。具体说,如果LoadPostData()方法返回true,即状态已被更新,则把该控件添加到一个不同的集合中,以便以后引起关注。

(注:LoadPostData实现IPostBackDataHandler(实现控件数据回传必须要继承该接口)的一个方法,该方法参数NameValueCollection类型的对象装载了客户端提交的数据。另外该方法还会比较控件的旧值和新值返回一个bool类型值,以决定是否执行RaisePostDatachangedEvent方法,即跟新控件里的值,详细请看深入理解.net服务器控件)

如果一个发送的名称与任何一个服务器控件都不匹配,则留下它并临时放在一个不同的集合中,稍后再试。

6.PreLoad事件

它指页面已经结束系统初级初始化阶段,并且准备进入下一个阶段,即还有机会在该页面中的用户代码中配置页面的执行和呈现。

7.Load事件

首先它针对页面的引发,然后递归针对所有的子控件引发。页面中的控件在此时创建,并且他们的状态完全反映前一个状态以及从客户端发送的任何数据。页面准备执行所有与页面逻辑和行为有关的初始化代码。这时,访问控件属性和视图状态是绝对安全的。

8.处理动态创建的控件

如果在显示前,页面中的所有控件都有机会完成初始化,页面处理器将再次尝试匹配那些尚未与现有控件匹配的投递值。这主要是应对动态的创建的控件

设想把一个控件动态地添加到页面树上,例如为了响应某个用户动作。每次回发以后重新创建页面,因此与动态创建的控件有关的任何信息会丢失。另一方面,提交页面的窗体时,其中的动态控件定期发送的合法而有效地信息填充。根据设计,第一次投递的数据时,不可能有什么服务器控件匹配动态控件ID。然而,ASP.NET Fromework知道Load事件会创建某些控件。因此有了再次匹配这一说。如果动态控件已经在Load事件中被重建了,现在就可以找到一种匹配,并且该控件可以用投递的数据刷新状态。

二 处理回发

回发机制它涉及到把窗体数据投递到相同页面,然后使用视图状态恢复调用上下文,在服务器上最后一次生成投递页时存在的控件的同一种状态。

在页面初始化已经完成并已经考虑投递的值以后,会发生某些服务器端事件。事件类型主要有两种。

1.检测控件状态变化

整个ASP.NET工作机制围绕一个隐含的假设:在浏览器中操作一些HTML输入标签与服务器控件具有一一对应关系。例:<input type=”text”><asp:TextBox>。当用户把一些信息输入到元素中后,调用对应的TextBox 控件来处理该投递值。

对于让LoadPostData方法返回true的所有控件,现在要执行RaisePostDataChangeEvent方法,该方法也属于IPostBackDataHandler接口。该方法通知ASP.NET应用程序控件的状态已经发生变化。

2.执行服务器端回发事件

任何回发都从某个打算触发一个服务器端动作的客户端动作开始。相信大家对回发事件应经很理解了这里不再多介绍。通俗的说就是能触发服务器端的事件代码的执行。

3.LoadComplete事件

表明页面准备阶段的结束。任何子控件都不会接受该事件。激发LoadComplete页面进入呈现阶段。

三 页面结束

处理回发事件以后,页面准备为浏览器生成输出。

1.PreRender事件

通过处理该事件,页面和控件可以在呈现输出之前任何更新。PreRender事件首先为页面激发,然后递归地为控件激发。

2.PreRenderConmplete事件

因为PreRender事件是递归地为所有子控件激发的,所以编程者无法知道何时完成预呈现阶段。而该事件的激发代表上面的事件结束了。

3.SaveStateComple事件

在每个控件生成页面的标记之前,把页面的当前状态保存到视图状态存储介质。在页面上的控件状态已经全部保存到持久性质时,会触发该事件。

4.生成标记

为浏览器生成标记是通过调用各组成控件来生成自己的标记来完成的,这些标记将存储到一个缓冲区中。这个阶段没有任何用户事件。

5.Unload事件

生成阶段之后是一个递归调用,为每个控件引发Unload事件,并最终为页面引发Unload事件。Unload事件存在的目的是在释放页面对象之前执行所有最后的清除工作。典型操作是关闭文件和数据库连接。

Resource Reference