Aspx 页面生命周期
ASP.NET 页运行时,此页将经历一个生命周期,在生命周期中将执行一系列处理步骤。这些步骤包括初始化、实例化控件、还原和维护状态、运行事件处理程序代码以及进行呈现。了解页的生命周期非常重要,这样就能在合适的生命周期阶段编写代码,以达到预期效果。此外,如果开发自定义控件,则必须熟悉页生命周期,从而正确地初始化控件,使用视图状态数据填充控件属性以及运行所有控件行为逻辑。(控件的生命周期基于页的生命周期,但是页引发的控件事件比单独的 ASP.NET 页中可用的事件多。)
常规页生命周期阶段
一般来说,页要经历下表概述的各个阶段。除了页生命周期阶段以外,还有在请求前后出现的应用程序阶段,但是这些阶段并不特定于页。有关更多信息,请参见 ASP.NET 应用程序生命周期概述。
阶段 | 说明 |
---|---|
页请求 |
页请求发生在页生命周期开始之前。用户请求页时,ASP.NET 将确定是否需要分析和编译页(从而开始页的生命周期),或者是否可以在不运行页的情况下发送页的缓存版本以进行响应。 |
开始 |
在开始阶段,将设置页属性,如 Request 和 Response。在此阶段,页还将确定请求是回发请求还是新请求,并设置 IsPostBack 属性。此外,在开始阶段期间,还将设置页的 UICulture 属性。 |
页初始化 |
页初始化期间,可以使用页中的控件,并将设置每个控件的 UniqueID 属性。此外,任何主题都将应用于页。如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。 |
加载 |
加载期间,如果当前请求是回发请求,则将使用从视图状态和控件状态恢复的信息加载控件属性。 |
验证 |
在验证期间,将调用所有验证程序控件的 Validate 方法,此方法将设置各个验证程序控件和页的 IsValid 属性。 |
回发事件处理 |
如果请求是回发请求,则将调用所有事件处理程序。 |
呈现 |
在呈现期间,视图状态将被保存到页,然后页将调用每个控件,以将其呈现的输出提供给页的 Response 属性的 OutputStream。 |
卸载 |
完全呈现页、将页发送至客户端并准备丢弃时,将调用卸载。此时,将卸载页属性(如 Response 和 Request)并执行清理。 |
生命周期事件
在页生命周期的每个阶段中,页将引发可运行您自己的代码进行处理的事件。对于控件事件,通过以声明方式使用属性(如 onclick)或以使用代码的方式,均可将事件处理程序绑定到事件。
页还支持自动事件连接,即,ASP.NET 将寻找具有特定名称的方法,并在引发特定事件时自动运行这些方法。如果 @ Page 指令的 AutoEventWireup 属性设置为 true(或者如果未定义该属性,因为默认情况下为 true),页事件将自动绑定至使用 Page_event 命名约定的方法,如 Page_Load 和 Page_Init。有关自动事件连接的更多信息,请参见 ASP.NET Web 服务器控件事件模型。
下表列出了最常用的页生命周期事件。实际的事件比列出的事件要多。但是,它们不用于大多数页处理方案。而是主要由 ASP.NET 网页上的服务器控件使用,以初始化和呈现它们本身。如果要编写自己的 ASP.NET 服务器控件,则需要详细了解这些阶段。有关创建自定义控件的信息,请参见开发自定义 ASP.NET 服务器控件。
页事件 | 典型使用 | ||
---|---|---|---|
Page_PreInit |
|
||
Page_Init |
|
||
Page_Load |
|
||
Control events |
执行特定于应用程序的处理: |
||
Page_PreRender |
|
||
Page_Unload |
执行最后的清理工作,可能包括:
|
其他的页生命周期注意事项
请注意有关页生命周期的以下附加信息:
-
各个 ASP.NET 服务器控件都有自己的生命周期,该生命周期与页生命周期类似。例如,在相应的页事件期间将调用控件的 Init 和 Load 方法。如果页上包含控件,则将首先调用控件的 Init 方法,然后再调用页的 Init 方法。但是,将在调用控件的 Load 方法之前先调用页的 Load 方法。
-
通过处理控件的事件,可以自定义控件的外观或内容。例如,所有的控件都将引发 Init、Load 和 Unload 事件,但是页开发人员通常不处理这些事件。而是通常处理特定于控件的事件,如 Button 控件的 Click 事件和 ListBox 控件的 SelectedIndexChanged 事件。在某些情况下,可能也需处理控件的 DataBinding 或 DataBound 事件。有关更多信息,请参见各个控件的类参考主题以及开发自定义 ASP.NET 服务器控件。
-
除了处理由页引发的事件以外,还可以重写页的基类中的方法。例如,可以重写页的 InitializeCulture 方法,以便动态设置区域性信息。注意,在使用 Page_event 语法创建事件处理程序时,将隐式调用基实现,因此无需在方法中调用它。例如,无论是否创建 Page_Load 方法,始终都会调用页基类的 OnLoad 方法。但是,如果使用 override 关键字(在 Visual Basic 中为 Overrides)重写页的 OnLoad 方法,则必须显式调用基方法。例如,如果在页中重写 OnLoad 方法,则必须调用 base.Load(在 Visual Basic 中为 MyBase.Load)以运行基实现。
-
初始化(Initialization)
页面被请求时,第一个被执行的总是构造函数(constructor). 你可以在这里初始化很多自定义属性或对象。不过这里有一些限制,因为 page 还没有被完全初始化。特别地,你必须使用 HttpContext.Current 来访问 QueryString, Form, Cookies 集合,以及 Cache 对象。而 Session 对象在 constructor 里是无法访问的。
下面接着执行的是 AddParsedSubObject 方法,这个方法把组成该 page 的所有子控件添加到控件集合树中。在很多高级的页面模板解决方案中,该方法通常被覆盖,以便把页面的控件添加到一个特殊的页面模板中去。该方法递归的被子控件调用,所有这些子控件都是这时候初始化的,从最里面的开始。
接着是 DeterminePostBackMode 方法。该方法允许你影响 IsPostBack 的值,以及相关事件。如果你想从数据库中加载 ViewState 以便 redirect 时,这个可能对你有用。因为 ViewState 仅仅在 IsPostBack 为 true 的时候被恢复。 你可以通过返回 null 来强制不 postback, 或者返回 Request.Form 来强制 postback. 这个方法是不推荐使用的,除非是在特殊的情况下,因为他还影响其他的事件。
然后是 OnInit 方法。通常这是我们使用到的第一个方法。这时,所有控件已经被初始化,也就是说所有原始值都被设定了。而 ViewState 以及所有其他 post 的值还没有被应用到控件上。也就是说这时候所有通过代码或者用户操作做的更改还没有被恢复。这通常是创建或重新创建动态控件的最佳时机。
恢复和加载(Restore and Load)
接下来的 LoadPageStateFromPersistenceMedium 方法,仅仅在 PostBack 时被执行。当你要改变保存 ViewState 的方法时(使用 Session 或其他自定义的储存方法),覆盖这个方法,以及后面的 SavePageStateToPersistenceMedium 方法。注意:该方法并不真正加载 ViewState 到 page 及其子控件。
ViewState 被取回后,接着 LoadViewState 方法将它们恢复到 page, 并递归的恢复到每一个子控件(只有 PostBack 的那些).这时,每个控件已经被恢复到了它上次执行时的状态,但用户 post 的值还没有被应用。因为这属于 ViewState. 这个方法是恢复所有在事件中创建的动态控件的最好时机。
下一个是 ProcessPostData 方法。仅仅在 PostBack 时被执行。而且这个方法不能被覆盖,因为它是页面基类中实现的一个私有方法。这个方法最终将用户 post 的值,通过匹配控件的名称的方法,恢复到页面。这时,page 已经被完全恢复了。动态控件必须在这个方法之前被创建。这个方法同时也为稍后的 changed 事件记录控件值的改变。
然后才是 OnLoad 方法。大部分的代码中都使用这个方法,因为这是在 page 的生命周期中,第一个所有的值都被恢复了的地方。我们可以通过检查 IsPostBack 属性来避免不必要的重设状态。同时也可以检查 IsValid 属性来进行验证。同时还可以在这里创建动态控件。所有这些控件的方法都会被执行并捕获,包括 ViewState. 但回发的值不可以。
Raised Events
下一个方法,ProcessPostData 方法,实际上是前面那个方法的第二个入口(second pass)。它仅仅处理回发,而且由于是私有方法,所以不能被覆盖。这个方法显得有些奇怪,但又是必要的。因为在 OnLoad 方法中重建的动态控件需要他们回发的值。所有在这个方法之后创建的动态控件,将只能恢复 ViewState, 而不能恢复回发的值,并且不能触发任何更改事件。
下一个方法, RaiseChangedEvents, 同样仅仅用于回发时。它是一个基类实现的私有方法。这时 changed 事件被真正触发。这基于前面 ProcessPostData 方法中标注出回发的值的差异。当有多个 changed 事件被触发时,其先后顺序是没有保证的。
下面是 RaisePostBackEvent 方法。仅用于回发,而且是基类实现的私有方法。这是真正提交 form 的方法,除非是 postback。比如按钮,或者其他通过 javascript 提交的控件被触发。如果使用了 Validators, 如没有手动调用 Validate 方法,这时也已经被调用了。有时候 ie 的 bug 会使得表单被提交,而不引发事件。
接着是 OnPreRender 方法。这通常是在被绘制到浏览器之前,要更改 page 及其子控件的最后机会。你也可以在这里创建动态控件。但这时只能捕获 ViewState, 而不能接受 posted values, 而且没有事件。因为上面提到的 ie 的 bug, 这里可以用来捕获没有触发事件的 post back.
保存和绘制(Save and Render)
下一个是 SaveViewState 方法。不管是否 post back. 递归的应用到每一个子控件。ViewState 一般保存所有和 aspx 页面里不一样的属性,不管是被代码还是用户更改的。注意,由于控件的值是通过他们在控件树中的位置来保存的,所以如果在这之后添加动态控件到错误的位置, ViewState 可能会崩溃。
下面是 SavePageStateToPersistenceMedium 方法。它真正的保存 page 的 ViewState. 这个方法可覆盖。如果重写的话,注意这里由于 asp.net 的 bug, 需要手工设定一下 __VIEWSTATE,哪怕是空值。
接着是 Render 方法。它递归的调用到每个子控件,真正的绘制各自的 html, 发送到浏览器。在一些页面模板方案中,常常在这里添加通用的 header 和 footer. 而不用使用服务器控件。 注意在这里能作的更改必须是纯的 html. 因为这时候控件都已经绘制完了。
最后是 OnUnload 方法。它调用了 Dispose 方法。这个方法可以用来清理页面中使用的非托管资源。特别是类似于关闭打开的文件或数据库连接等。该方法只有当页面已经被发送到客户端浏览器后才发生。所以它只能对服务端的对象起作用。所以他不能在 page 的 trace 中被显示。
上面就是 page 的生命循环。每次有一个新的请求时,以上过程就重复一次。
Listing 1: Page 的事件小结
Method PostBack Controls Constructor Always All AddParsedSubObject Always All DeterminePostBackMode Always Page OnInit Always All LoadPageStateFromPersistenceMedium PostBack Page LoadViewState PostBack All ProcessPostData1 PostBack Page OnLoad Always All ProcessPostData2 PostBack Page RaiseChangedEvents PostBack Page RaisePostBackEvent PostBack Page OnPreRender Always All SaveViewState Always All SavePageStateToPersistenceMedium Always Page Render Always All OnUnload Always All