【原创】ASP.NET中页和控件的生命周期与事件

一、生命周期事件的引言

  我们来看看VS.Net自动生成的CodeBehind类的代码,以此来开始我们对页面生命周期的探讨:

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN:该调用是 ASP.NET Web 窗体设计器所必需的。
//
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.DataGrid1.ItemDataBound += new System.Web.UI.WebControls.DataGridItemEventHandler(this.DataGrid1_ItemDataBound);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion

  这个就是使用VS.Net产生的Page的代码,我们来看,这里面有两个方法,一个是OnInit,一个是InitializeComponent,后者被前者调用。实际上这就是页面初始化的开始,在InitializeComponent中我们看到了控件的事件声明和Page的Load声明。

  下面是从MSDN中摘录的一段描述和一个页面生命周期方法和事件触发的顺序表:“每次请求 ASP.NET 页时,服务器就会加载一个 ASP.NET 页,并在请求完成时卸载该页。页及其包含的服务器控件负责执行请求并将 HTML 呈现给客户端。虽然客户端和服务器之间的通讯是无状态的和断续的,但是必须使客户感觉到这是一个连续执行的过程。” “这种连续性假象是由 ASP.NET 页框架、页及其控件实现的。回发后控件的行为必须看起来是从上次 Web 请求结束的地方开始的。虽然 ASP.NET 页框架可使执行状态管理相对容易一些,但是为了获得连续性效果,控件开发人员必须知道控件的执行顺序。控件开发人员需要了解:在控件生命周期的各个阶段,控件可使用哪些信息、保持哪些数据、控件呈现时处于哪种状态。例如在填充页上的控件树之前控件不能调用其父级。”

二、生命周期事件的概述

在页生命周期的每个阶段中,页将引发可运行您自己代码进行处理的事件。对于控件事件通过以声明方式使用属性(如 onclick)或以使用代码的方式,均可将事件处理程序绑定到事件。

页还支持自动事件连接,即ASP.NET 将查找具有特定名称的方法,并在引发了特定事件时自动运行这些方法。如果 @ Page 指令的 AutoEventWireup 属性设置为 true(或者未定义该属性,因为该属性默认为 true),页事件将自动绑定至使用 Page_事件的命名约定的方法(如 Page_Load 和 Page_Init)。

下表提供了控件生命周期中主要阶段的高级概述。

阶段

控件需要执行的操作

要重写的方法或事件

初始化

初始化在传入 Web 请求生命周期内所需的设置。

Init 事件(OnInit 方法)

加载视图状态

在此阶段结束时,就会自动填充控件的 ViewState 属性。控件可以重写LoadViewState 方法的默认实现,以自定义状态还原。

LoadViewState 方法

处理回发数据

处理传入窗体数据,并相应地更新属性。

注意 只有处理回发数据的控件参与此阶段。

LoadPostData 方法 (如果已实现IPostBackDataHandler)

加载

执行所有请求共有的操作,如设置数据库查询。此时,树中的服务器控件已创建并初始化、状态已还原并且窗体控件反映了客户端的数据。

Load 事件(OnLoad 方法)

发送回发更改通知

引发更改事件以响应当前和以前回发之间的状态更改。

注意 只有引发回发更改事件的控件参与此阶段。

RaisePostDataChangedEvent 方法 (如果已实现IPostBackDataHandler)

处理回发事件

处理引起回发的客户端事件,并在服务器上引发相应的事件。

注意 只有处理回发事件的控件参与此阶段。

RaisePostBackEvent 方法 (如果已实现IPostBackEventHandler)

预呈现

在呈现输出之前执行任何更新。可以保存在预呈现阶段对控件状态所做的更改,而在呈现阶段所做的更改则会丢失。

PreRender 事件 (OnPreRender 方法)

保存状态

在此阶段后,自动将控件的 ViewState 属性保持到字符串对象中。此字符串对象被发送到客户端并作为隐藏变量发送回来。为了提高效率,控件可以重写 SaveViewState 方法以修改 ViewState 属性。

SaveViewState 方法

呈现

生成呈现给客户端的输出。

Render 方法

处置

执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库链接。

Dispose 方法

卸载

执行销毁控件前的所有最终清理操作。控件通常在 Dispose 中执行清除,而不处理此事件。

UnLoad 事件(OnUnLoad 方法)

下表列出了最常用的页生命周期事件的典型应用。除了列出的事件外还有其他事件,不过大多数页处理方案不使用这些事件,而是主要由 ASP.NET 网页上的服务器控件使用,以初始化和呈现它们本身。如果要编写自己的 ASP.NET 服务器控件,则需要详细了解这些阶段。

页事件

典型使用

PreInit

使用该事件来执行下列操作:

·检查 IsPostBack 属性来确定是不是第一次处理该页。

·创建或重新创建动态控件。

·动态设置主控页。

·动态设置 Theme 属性。

·读取或设置配置文件属性值。

注意

如果请求是回发请求,则控件的值尚未从视图状态还原。如果在此阶段设置控件属性,则其值可能会在下一事件中被重写。

Init

在所有控件都已初始化且已应用所有外观设置后引发。使用该事件来读取或初始化控件属性。

InitComplete

由 Page 对象引发。使用该事件来处理要求先完成所有初始化工作的任务。

PreLoad

如果需要在 Load 事件之前对页或控件执行处理,请使用该事件。

Page 引发该事件后,它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据。

Load

PagePage 上调用 OnLoad 事件方法,然后以递归方式对每个子控件执行相同操作,如此循环往复,直到加载完本页和所有控件为止。

使用 OnLoad 事件方法来设置控件中的属性并建立数据库连接。

控件事件

使用这些事件来处理特定控件事件,如 Button 控件的 Click 事件或 TextBox 控件的TextChanged 事件。

注意

在回发请求中,如果页包含验证程序控件,请在执行任何处理之前检查 Page 和各个验证控件的 IsValid 属性。

LoadComplete

对需要加载页上的所有其他控件的任务使用该事件。

PreRender

在该事件发生前:

·Page 对象会针对每个控件和页EnsureChildControls

·设置了 DataSourceID 属性的每个数据绑定控件会调用 DataBind 方法。

页上的每个控件都会发生 PreRender 事件。使用该事件对页或其控件的内容进行最后更改。

SaveStateComplete

在该事件发生前,已针对页和所有控件保存了 ViewState。将忽略此时对页或控件进行的任何更改。

使用该事件执行满足以下条件的任务:要求已经保存了视图状态,但未对控件进行任何更改。

Render

这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。

如果创建自定义控件,通常要重写此方法以输出控件的标记。不过如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定义标记,则不需要重写 Render 方法。

用户控件(.ascx 文件)自动合并呈现,因此不需要在代码中显式呈现该控件。

Unload

该事件首先针对每个控件发生,继而针对该页发生。在控件中,使用该事件对特定控件执行最后清理,如关闭控件特定数据库连接。

对于页自身,使用该事件来执行最后清理工作,如:关闭打开的文件和数据库连接或完成日志记录或其他请求特定任务。

注意

在卸载阶段,页及其控件已被呈现,因此无法对响应流做进一步更改。如果尝试调用方法(如 Response.Write 方法),则该页将引发异常。

  通过上述两个表我们基本了解了页面类及其所含控件的生命周期和事件响应流程。事实上,页面类的ProcessRequest才是真正意义上的页面声明周期的开始,这个方法是由HttpApplication调用的(其中调用的方式比较复杂,这里不做详述),一个Page对请求的处理就是从这个方法开始,通过反编译.Net类库来查看源代码,我们发现在System.Web.WebControls.Page的基类:System.Web.WebControls.TemplateControl(它是页面和用户控件的基类)中定义了一个“FrameworkInitialize”虚方法,然后在Page的ProcessRequest中最先调用了这个方法,在生成器生成的ASPX的源代码中我们发现了这个方法的踪影,所有的控件都在这个方法中被初始化,页面的控件树就在这个时候产生。

三、生命周期事件的本质

1.对象初始化

页面中的控件(包括页面本身)都是在它们最初的Form中被首次初始化的。通过在ASPX页面的后台代码文件的构造器中声明你的对象,页面将知道对象的类型和数量。一旦你在构造器中声明了你的控件,你就可以在它的任何子类,方法,事件或者属性中访问到它们。但是,如果你的任何对象是在设计期指定的控件,这样的控件是没有属性的。而且这样做对从代码中访问它们是危险的,因为无法保证这些控件实例是按照怎样的顺序被创建的(假定它们都能完全被创建)。初始化事件可以通过OnInit方法重载。

2.加载视图状态数据

初始化以后,控件仅能通过ID引用(还没有建立用于相对引用的文档对象模型)。 在LoadViewState事件中,已初始化的控件获得第一个属性:上一次提交存留到服务器的视图状态信息。页视图状态通过ASP.NET维护,它被用于在一个往返行程中存留信息到服务器。视图状态信息被保存为一个名称/值对,它包含控件的如Text和Value一类的信息。视图信息被保存在隐藏<input>控件的值属性中在页请求中传递。正如你所了解的,这是旧的ASP3.0状态维护技术的一个巨大飞跃。这个事件可以通过LoadViewState方法重载,往往用来在控件被填充时定制它所接受的数据。

3.LoadPostData处理回传数据

在创建页的阶段,被发送到服务器端的Form数据(ASP.NET中的术语为回传数据)依照每个控件的数据需求进行处理。当页面提交Form时,框架将在每个提交数据的控件上实现IPostBackDataHandler接口。页面然后激发LoadPostData事件,通过页面解析发现实现了IPostBackDataHandler接口的控件,并用正确的回传数据更新控件状态。ASP.NET通过匹配控件的唯一标示符来更新正确的控件,该标示符具有名称/值集合中的名称/值对。这也就是在所有特定的页中每个控件都需要一个唯一标示符的原因之一。其它的步骤都由框架来完成,以确定每个标示符在环境中是唯一的,例如存在于单页面中的自定义用户控件。LoadPostData事件被激发后,RaisePostDataChanged事件就可以随时被执行了(步骤5)

4.对象加载

对象在Load事件中获得正确的Form。所有的对象首先都被组织在页DOM(ASP.NET中称为控件树)中,并且很容易通过代码或者相对位置(crawling the DOM)来引用。然后对象就可以自由的访问HTML中的客户端属性集,例如width,value,或者visibility。加载时,控件逻辑如算法、以编程方式设置控件属性、用StringBuilder装配输出字符串都同时被执行。大部分的工作都是在这一阶段完成的。Load 事件能够通过调用OnLoad 来重载。

5.激发RaisePostDataChanged 事件

如前所述,这发生在所有实现了IPostBackDataHandler接口的控件被正确的回传数据更新以后。在这个过程中,每个控件都有一个布尔值的标识,标识其自上一次提交后该控件的数据是被更改还是保持原值。然后ASP.NET通过搜索页来寻找任何显示控件数据被更改的标识并激发RaisePostDataChanged。RaisePostDataChanged事件直到Load事件发生后,所有控件被更新后才激发。这保证了在控件被回传数据更新前,其它控件的数据在RaisePostDataChanged事件中没有被手动更改过。

6.处理客户端回传事件

当回传更新导致数据改变而引发服务器端事件后,引发回传的对象会在RaisePostBackEvent事件中被处理。这种激发回传的对象往往是其状态改变而引发回传的控件(其autopostback被启用)或者是一个被点击的提交按钮。很多代码都在这个事件中执行,因为这是控制事件驱动逻辑的理想位置。为了保证呈现到浏览器的数据的正确性,在一系列的回传事件后RaisePostBackEvent事件最终被激发。基于一致性的考虑,回传中改变的控件直到这个函数被执行后才被更新。也就是说,被预期事件改变的数据总是在结果页反映出来。RaisePostBackEvent事件可以通过RaisePostBackEvent来捕捉。

7.对象预呈现

对象被预呈现的地方对于那些能够保存到视图或者维持其视图状态的对象来说是最后一次改变的机会。这使得预呈现步骤成为做最后修改的理想位置,例如改变控件属性或改变控件树结构,不用担心因为数据库请求或者视图状态更新而导致对象的变化。预呈现阶段之后,对象改变被锁定并且不能再被保存到页视图状态中。预呈现阶段可以通过重载OnPreRender实现。

8.保存视图状态

只有在所有的页面对象的改变都发生后视图状态才被保存。对象状态数据被保存在隐藏<input>对象中,这也是对象状态数据准备呈现到HTML的地方。在SaveViewState事件中,值能够被保存到视图状态对象中,但页面控件的改变并不能保存到其中。可以通过重载SaveViewState实现这个步骤。

9.呈现HTML

Render事件通过装配用于浏览器输出的HTML来着手页的创建。在Render事件中,页调用对象使它们呈现为HTML,然后页收集HTML来发送。当Render事件被重载的时候,开发者可以为浏览器创建定制的HTML,此时页面创建的任何HTML都还没有生效。Render 方法用HtmlTextWriter对象作参数并由它产生HTML给浏览器。这里仍然可以作修改,但是这样的修改只会反映到客户端(译者注:即改变只会在HTML呈现中反映而视图状态并无法被改变)。Render 事件可以被重载。

10.释放

当页面的HTML呈现后,对象被释放。在Dispose事件中,你可以清除任何在页面创建中构造的对象或者引用。在这里,所有的处理都已经被执行,你可以安全的释放任何还存在的对象,包括Page对象。Dispose能被重载。

四、生命周期事件的详解

  接下来的事情就简单了,我们来逐步分析页面生命周期的每一项:

(写在前面的特殊说明:如果要重写事件的处理过程,MSDN推荐的方式是重载对应事件的方法,而不是增加一个事件代理。这两者是有差别的,前者可以控制调用父类事件方法的顺序,而后者只能在父类的事件方法后执行(实际上是在父类事件方法里面被调用)。后面会分别给出实例)

  1、预初始化

  预初始化对应Page的PreInit事件和OnPreInit方法。Page_PreInit发生在页面初始化之前,页面上的控件还尚未创建。此时可以:1)创建或重新创建动态控件;2)动态设置主控页。

  具体的重载代码如下,并且加注了本步骤Page所执行工作和可处理的任务(后同)

    #region OnPreInit 第一步
protected override void OnPreInit(EventArgs e)
{
//检查 IsPostBack 属性来确定是不是第一次处理该页。
//创建或重新创建动态控件。
//动态设置主控页。
//动态设置 Theme 属性。
//读取或设置配置文件属性值。
//注意!如果请求是回发请求,则控件的值尚未从视图状态还原。如果在此阶段设置控件属性,则其值可能会在下一事件中被重写。
base.OnPreInit(e);
}
#endregion

  2、初始化

  初始化对应Page的Init事件和OnInit方法。此时将执行页面初始化,实例化页面上的控件对象,可以读取或初始化控件属性。

   #region OnInit 第二步
protected override void OnInit(EventArgs e)
{
//在所有控件都已初始化且已应用所有外观设置后引发。使用该事件来读取或初始化控件属性。
base.OnInit(e);
}
#endregion

  3、初始化完成

  初始化完成对应Page的InitComplete事件和OnInitComplete方法。由 Page 对象触发,使用该事件来处理要求预先完成所有初始化工作的任务。

    #region OnInitComplete 第三步
protected override void OnInitComplete(EventArgs e)
{
//由 Page 对象引发。使用该事件来处理要求先完成所有初始化工作的任务。
base.OnInitComplete(e);
}
#endregion

 4、预加载

  预加载对应Page的PreLoad事件和OnPreLoad方法。使用该事件在 Load 事件之前对页或控件执行处理。

    #region PreLoad 第四步
protected override void OnPreLoad(EventArgs e)
{
//如果需要在 Load 事件之前对页或控件执行处理,请使用该事件。
//在 Page 引发该事件后,它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据。
base.OnPreLoad(e);
}
#endregion

读者可能会注意到上面代码的注释中有如下内容“在 Page 引发该事件后,它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据。”。不需要过多解释,在OnPreLoad方法的基类中最终会引发如下两个步骤:

  5、 加载视图状态

  这是个比较重要的方法,我们知道,对于每次请求,实际上是由不同的页面类实例来处理的,为了保证两次请求间的状态,ASP.Net使用了ViewState。LoadViewState方法就是从ViewState中获取上一次的状态,并依照页面的控件树的结构,用递归来遍历整个树,将对应的状态恢复到每一个控件上。

  6、 处理回发数据

  这个方法是用来检查客户端发回的控件数据的状态是否发生了改变。方法的原型:

public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)

  postDataKey是标识控件的关键字(也就是postCollection中的Key),postCollection是包含回发数据的集合,我们可以重写这个方法,然后检查回发的数据是否发生了变化,如果是则返回一个True。摘自MSDN 的原文如是:“如果控件状态因回发而更改,则 LoadPostData 返回 true;否则返回 false。页框架跟踪所有返回 true 的控件并在这些控件上调用RaisePostDataChangedEvent。”(这个方法是System.Web.WebControls.Control中定义的,也是所有需要处理事件的自定义控件需要处理的方法,在这里我们不详细展开,读者可自行查阅MSDN。)

  7、 加载

  加载对应Load事件和OnLoad方法,此时页面上的控件都已生成完毕,可以处理如下任务:1)使用IsPostBack 属性确定是否是第一次处理该页;2)读取和更新控件属性;3)在控件上绑定动态的数据。Page_Load方法响应了Load事件,这个事件是在System.Web.WebControl.Control类中定义的(这个类是Page和所有服务器控件的鼻祖),并且在OnLoad方法中被触发。

对于这个事件,相信大多数读者都会比较熟悉,用VS.Net生成的页面中的Page_Load方法就是响应Load事件的方法,对于每一次请求,Load事件都会触发,Page_Load方法也就会执行,相信这也是大多数开发者了解ASP.Net的第一步。

  很多人可能碰到过这样的事情,写了一个PageBase类,然后在Page_Load中来验证用户信息,结果发现不管验证是否成功,子类页面的Page_Load总是会先执行,这个时候很可能留下一些安全性的隐患,用户可能在没有得到验证的情况下就执行了子类中的Page_Load方法。

  出现这个问题的原因很简单,因为Page_Load方法是在OnInit中被添加到Load事件中的,而子类的OnInit方法中是先添加了Load事件,然后再调用base.OnInit,这样就造成了子类的Page_Load被先执行了。

  要解决这个问题也很简单,有两种方法:

  1) 在PageBase中重载OnLoad方法,然后在OnLoad中验证用户,然后调用base.OnLoad,因为Load事件是在OnLoad中触发,这样我们就可以保证在触发Load事件之前验证用户。

  2) 在子类的OnInit方法中先调用base.OnInit,这样来保证父类先执行Page_Load。

重载代码如下:

    #region OnLoad 第七步
protected override void OnLoad(EventArgs e)
{
//Page 在 Page 上调用 OnLoad 事件方法,然后以递归方式对每个子控件执行相同操作,如此循环往复,直到加载完本页和所有控件为止。
//使用 OnLoad 事件方法来设置控件中的属性并建立数据库连接。
base.OnLoad(e);
}
#endregion

  8、 发送回发更改通知

  这个方法对应第6步的处理回发数据,如果处理回发数据返回True,页面框架就会调用此方法来触发数据更改的事件,所以自定义控件的回发数据更改事件需要在此方法中触发。同样这个方法对于Page来说没有太大的用处,当然你也可以在Page的基础上自己定义数据更改的事件。

  9、 处理回发事件

  由用户操纵页面引发,具体事件和引发的次数、顺序依具体页面和用户的操作而定。这个方法是大多数服务器控件事件引发的地方,当请求中包含控件事件触发的信息时,页面控件会调用相应控件的RaisePostBackEvent方法来引发服务器端的事件。

代码示例如下:

    #region 控件事件 第九步
protected void Button1_Click(object sender, EventArgs e)
{
//用这些事件来处理特定控件事件,如 Button 控件的 Click 事件或 TextBox 控件的 TextChanged 事件。
//注意!在回发请求中,如果页包含验证程序控件,请在执行任何处理之前检查 Page 和各个验证控件的 IsValid 属性。
}
#endregion

  这里又引出另一个常见的问题:经常有初级开发者疑惑,为什么修改提交后的数据并没有更新?

  多数的情况都是他们没有理解服务器事件的触发流程,我们可以看出,触发服务器事件是在Page的Load之后,也就是说页面会先执行Page_Load,然后才会执行按钮(这里以按钮为例)的点击事件,很多朋友都是在Page_Load中绑定数据,然后在按钮事件中处理更改,这样做有一个毛病,Page_Load永远都是在按钮事件之前执行,那么意味着数据还没来得及更改,Page_Load中的数据绑定的代码就先执行了,原有的数据又赋给了控件,那么执行按钮事件的时候,实际上获得的是原有的数据,那么更新当然就没有效果了。

  更改这个问题也非常简单,比较合理的做法是把数据绑定的代码写成一个方法,我们假设为BindData:

private void BindData()
{
//绑定数据
}

  然后修改PageLoad:

private void Page_Load( object sender,EventArgs e )
{
if( !IsPostBack )
{
BindData(); //在页面第一次访问的时候绑定数据
}
}

  最后在按钮事件中:

private Button1_Click( object sender,EventArgs e )
{
//更新数据
BindData();//重新绑定数据
}

  10、加载完成

  加载完成对应Page的LoadComplete事件和OnLoadComplete方法。由 Page 对象触发,使用该事件来处理需要加载页上的所有其他控件的任务。

    #region OnLoadComplete 第十步
protected override void OnLoadComplete(EventArgs e)
{
//对需要加载页上的所有其他控件的任务使用该事件。
base.OnLoadComplete(e);
}
#endregion

  11、预呈现

  预呈现对应Page的PreRender事件和OnPreRender方法。最终请求的处理都会转变为发回服务器的响应,预呈现这个阶段就是执行在最终呈现之前所作的状态的更改,因为在呈现一个控件之前,我们必须根据它的属性来产生Html,比如Style属性,这是最典型的例子。在预呈现之前,我们可以更改一个控件的Style,当执行预呈现的时候,我们就可以把Style保存下来,作为呈现阶段显示Html的样式信息。简要地讲:HTML页面生成完毕,接下来就要发送给客户端了,此时可以在HTML页面上追加一些附加信息。对页的内容进行最后更改。

  #region OnPreRender 第十一步
protected override void OnPreRender(EventArgs e)
{
//在该事件发生前Page 对象会针对每个控件和页调用EnsureChildControls。
//设置了 DataSourceID 属性的每个数据绑定控件会调用 DataBind 方法。有关更多信息,请参见下面的数据绑定控件的数据绑定事件。
//页上的每个控件都会发生 PreRender 事件。使用该事件对页或其控件的内容进行最后更改。
base.OnPreRender(e);
}
#endregion

  12、保存状态

  这个阶段是针对加载状态的。我们多次提到请求之间是不同的实例在处理,所以我们需要把本次的页面和控件的状态保存起来,这个阶段就是把状态写入ViewState的阶段。

    #region SaveStateComplete 第十二步
protected override void OnSaveStateComplete(EventArgs e)
{
//在该事件发生前,已针对页和所有控件保存了 ViewState。将忽略此时对页或控件进行的任何更改。
//使用该事件执行满足以下条件的任务:要求已经保存了视图状态,但未对控件进行任何更改。
base.OnSaveStateComplete(e);
}
#endregion

  13、呈现

  到这里实际上页面对请求的处理基本就告一段落了,在Render方法中会递归整个页面的控件树,依次调用Render方法,把对应的Html代码写入最终响应的流中。

    #region Render 第十三步
//Render
//这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。
//如果创建自定义控件,通常要重写此方法以输出控件的标记。不过,如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定义标记,则不需要重写 Render 方法。有关更多信息,请参见开发自定义 ASP.NET 服务器控件。
//用户控件(.ascx 文件)自动合并呈现,因此不需要在代码中显式呈现该控件。
#endregion

  14、处置

  实际上就是Dispose方法,在这个阶段会释放占用的资源,例如数据库连接。

  15、卸载

  卸载对应Page的Unload事件和OnUnload方法。执行最后的清理工作,一般我们无需干涉。最后页面会执行OnUnLoad方法触发UnLoad事件,处理在页面对象被销毁之前的最后任务。实际上ASP.Net提供这个事件只是设计上的考虑,通常资源的释放都会在Dispose方法中完成,所以这个方法也变成鸡肋了。

   #region OnUnload 第十五步       
protected override void OnUnload(EventArgs e)
{
//该事件首先针对每个控件发生,继而针对该页发生。在控件中,使用该事件对特定控件执行最后清理,如关闭控件特定数据库连接。
//对于页自身,使用该事件来执行最后清理工作,如:关闭打开的文件和数据库连接,或完成日志记录或其他请求特定任务。
//注意!在卸载阶段,页及其控件已被呈现,因此无法对响应流做进一步更改。如果尝试调用方法(如 Response.Write 方法),则该页将引发异常。
base.OnUnload(e);
}
#endregion

四、生命周期事件的注意事项

各个 ASP.NET 服务器控件都有自己的生命周期,该生命周期与页生命周期类似。例如控件的 Init 和 Load 事件在相应的页事件期间发生。虽然 Init 和 Load 都在每个控件上以递归方式发生,但它们的发生顺序相反。每个子控件的 Init 事件(还有 Unload 事件)在为其容器引发相应的事件之前发生(由下到上)。但是容器的 Load 事件是在其子控件的 Load 事件之前发生(由上到下)。

可以通过处理控件的事件(如 Button 控件的 Click 事件和 ListBox 控件的SelectedIndexChanged 事件)来自定义控件的外观或内容。在某些情况下,可能也需处理控件的 DataBinding 或 DataBound 事件。

当从 Page 类继承类时,除了可以处理由页引发的事件以外,还可以重写页的基类中的方法。例如可以重写页的 InitializeCulture 方法,以便动态设置区域性信息。注意,在使用 Page_事件语法创建事件处理程序时,将隐式调用基实现,因此无需在方法中调用它。例如无论是否创建 Page_Load 方法,始终都会调用页基类的 OnLoad 方法。但是如果使用 override 关键字重写页的 OnLoad 方法,则必须显式调用基方法。例如如果在页中重写 OnLoad 方法,则必须调用 base.Load以运行基类事件。

添加的控件的追赶事件

如果控件是在运行时动态创建的,或者是以声明方式在数据绑定控件的模板中创建的,它们的事件最初与页上的其他控件的事件并不同步。例如,对于运行时添加的控件,Init 和 Load 事件在页生命周期中的发生时间可能要比在设计期创建的控件的相同事件晚得多。因此从实例化那一刻起,动态添加的控件事件就一直是在设计期控件事件之后发生,直到赶上该控件加入 控件集合时所对应事件为止。

一般来说,除非存在嵌套数据绑定控件,否则您不必担心这种情况。如果子控件已执行数据绑定,但其容器控件尚未执行数据绑定,则子控件中的数据与其容器控件中的数据可能不同步。如果子控件中的数据根据容器控件中的数据绑定值执行了处理,这种情况则尤其显著。

假定有一个 GridView,它的每一行显示一条公司记录,此外有一个ListBox 控件包含公司管理者列表。若要填充管理者列表,则需要将 ListBox 控件绑定到一个数据源控件(如 SqlDataSource),后者在查询中使用CompanyID来检索公司管理者数据。如果设计期设置了 ListBox 控件的数据绑定属性(如 DataSourceID 和DataMember),ListBox 控件将尝试在包含行的 DataBinding 事件期间绑定到其数据源。不过行的 CompanyID 字段直到 GridView 控件的 RowDataBound事件发生后才包含值。这种情况下先绑定子控件(ListBox 控件),后绑定包含控件(GridView 控件),因此它们的数据绑定阶段并不同步。

若要避免此种情况,需要将 ListBox 控件的数据源控件与 ListBox 控件自身放在同一模板项中,并且不要在设计期设置 ListBox 的数据绑定属性,而应在运行期的RowDataBound 事件中以代码方式设置它们。这样到 CompanyID信息可用时 ListBox 控件才会绑定到其数据。