ASP.NET页面生命周期(包含页面的回传和事件触发的执行顺序)

 
ASP.NET页面生命周期(包含页面的回传和事件触发的执行顺序)
Paul Wilson
www.WilsonDotNet.com
www.ASPAlliance.com/PaulWilson
 
概述:
  页面的生命周期从大体上区分可以分为以下四个阶段:
 1. 初始化阶段 (Initialization);
 2. 恢复数据阶段 (Restore and Load);
 3. 触发事件阶段 (Raised Events);
 4. 保存信息和绘制界面阶段 (Save and Render)
初始化 (Initialization)
当一个页面被请求的时候第一个触发是页面的构造函数 (Constructor) 。在这个阶段内你可以对一些个性化的设置进行处理。由于在这个阶段页面还没有被初始化完毕所以在一些属性的操作上可能会有些限制,比如这个时候还不能直接使用 Page.Response, Page.Request, Page.Cache ( 因为这个时候 Page 类还在运行构造函数,所以还未初始化完毕,自然 Page. 方法 / 属性的做法是会抛出异常的 ) ,这个时候这些属性还是未初始化的状态 (null, Nothing) 。对于这些属性需要通过诸如 HttpContext.Current 的方式得到。但是值得注意的是在这个阶段 Session 属性是无法使用的,即便是通过 HttpContext.Current.Session 的方式也不能进行操作。
然后执行的方法是 AddParsedSubObject 。在 Temporary ASP.NET Files 中页面对应生成源代码文件中 (.cs/.vb) 可以看到在创建页面控件树的时候 ( 如: __BuildControlTree) 调用了这个方法将已经解析完成的子空间添加进入控件树。这个方法可以被重写 (override) ,比如将一些控件添加到指定页面模板的特定位置。这个方法将页面上所有控件 ( 静态控件 ) 以树的形式添加进来,而且树的建立是从最底层开始的。
接着执行的是 DeterminePostBackMode 方法。这个方法的返回值会对 IsPostBack 的值和所有与页面回传 (Post Back) 相关的事件都产生影响。由于 ViewState 仅仅在页面回传 (PostBack) 的时候才会被恢复到对应的控件,所以我们可以通过重写 DeterminePostBackMode 方法使其返回 null(Nothing) 来强制不回传,也可以通过返回 Request.Form 来强制表示页面回传。如在页面中添加如下代码: 
Protected Overrides Function DeterminePostBackMode()Function DeterminePostBackMode() As System.Collections.Specialized.NameValueCollection
    
Return (Request.Form)
End Function
并且在 Page_Load 中添加如下代码:
 Response.Write(Page.IsPostBack.toString())
你会发现此时即便是第一次请求页面,但是 Page.IsPostBack = True, 页面认为此次请求是回传页面。
接着执行的是 OnInit 方法,这个方法通常是编程中最先使用的方法 ( 对应的函数签名为: Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init) 。在这个方法执行前页面所有定义的控件 ( 静态控件 ) 都已经初始化完毕。这个时候所有静态控件都已经被赋了初始值 ( 如:页面上有一个 TextBox1, 并在属性中将其 Text 属性设置为“ Hello World ”,那么无论页面回传了多少次,无论 TextBox1 中的 Text 被修改了多少次,那么在这个时候 TextBox1.Text 一定为“ Hello World ”。这一点可以通过在 Page_Init 方法中取 TextBox1.Text 属性来验证。 ) 所以在 OnInit 阶段, ViewState 的值和回传的值 (Post Back Data) 都还没有被赋值到对应的控件上。 OnInit 阶段是创建 / 重新创建动态控件 (Dynamic Control) 的最好时机。
恢复数据阶段 (Restore and Load)
下一个被执行的方法是 LoadPageStateFromPersistenceMedium ,这个方法仅仅只有在页面回传的时候才会执行。从这个方法的名称可以看出这个方法的作用是从一个永久保持数据的媒介将页面状态载入。如果是使用 Session 或者其他用户自定义的方式来存储页面状态 ( 要实现这种自定义存储的方式需要重写稍后会被触发的函数 SavePageStateToPersistenceMedium) ,那么在载入的时候可以重写这个函数以便进行对应的操作。 ASP.NET 中默认的页面状态存储方式是 ViewState( 默认是一段通过 Base64 编码的字符串,放置在页面的一个隐藏变量中。 ) 值得注意的是,这个方法 (LoadPageStateFromPersistenceMedium) 并不是将 ViewState 中的值赋值到对应的控件,而仅仅是将 ViewState 载入,准备更新对应控件的状态值。
在得到了页面状态以后 ( 我们这里仅仅考虑默认保存页面状态为 ViewState 的情况 ) ,通过调用 LoadViewState 方法,通过递归的方法将页面和其所有控件载入 ViewState 中记录的状态值。当然这个方法也仅仅是在页面回传 (Post Back) 的时候才会执行。在执行完成 LoadViewState 以后所有控件都已经是最后一次的状态 ( 如:页面有一个 TextBox1 控件, TextBox1.Text 为“ Hello World. ”,然后手动修改为“ Hello Ya ”,点击 Button 回传页面。那么在这个阶段 TextBox1.Text = Hello World. ) 。但是页面最后一次回传的值在这个时候还没有被赋值到对应的控件上,因为这些值是 Request.Form 或者是 Request.QueryString 的一部分 ( 通过名值对的方式进行回传 ) ,这些值并不是 ViewState 的一部分。如果页面的某些控件是在事件中动态生成的,而且其对应的状态值你必须以编码的形式保存到 ViewState 中,那么这个阶段你可以获得你存入的 ViewState 值并对控件的状态进行初始化。
接下来执行的是 ProcessPostData 方法,这个方法也仅仅是在页面回传的时候 (Post Back) 执行。由于此方法是 Page 类中的一个私有方法,所以不能被派生类所重写。从方法的名称可以看出这个方法是用来处理页面最后一次回传的数据的,就是前面所提到的名值对信息。在这个方法中,通过将控件的 ID( 实际应该是控件的名称属性 [name] ,这里是针对服务器端的控件属性,在将服务器端控件解析成对应的客户端 HTML 控件时,对于需要回传值的控件, ASP.NET 会自动为其生成一个 name 属性,其值和服务器端控件的 ID 值相等 [ 这里不考虑作为另外一个控件子控件的情况,因为如果作为子控件那么其 name 属性前面有时会增加父控件的 name 值作为前缀 ]) 和回传服务器名值对中的 Key 相匹配,然后将对应的控件状态和值更新为最新值。到此为止,这个页面就已经完成了数据载入的全部工作 ( 已经是最新的值了 ) 。值得注意的是动态生成的控件必须在这个方法调用前生成,因为只有执行了这个方法,通过比较 ViewState 的值 ( 最后一次回传以前的值 ) 和当前回传的值 (Post Back Data) 之间的不同才可以触发对应控件的变更事件 (changed events ,如: TextBox.TextChanged 事件 )
然后执行的就是大名鼎鼎的 OnLoad 事件。 Page_Load 函数写 ASP.NET Coder 都知道。由于在前面的各个阶段完成以后页面上各个控件的值已经完全被恢复了,所以很多代码都通过 Page.IsPostBack 的值来判断页面是否是回传页面,从而减少不必要的操作 ( 或者说仅仅只需要在第一次页面执行或者仅仅对页面回传的时候才能进行的操作 ) 。同时你也可以在此时验证控件数据的有效性。同样的你也可以在这个阶段创建动态控件,事实上我们常常会这样做。在动态控件被创建 (New) 了以后,这个控件将会重新执行前面提到了的除了 ProcessPostData 方法之外的所有方法,以便和其他控件的状态保持一致 (executed to catch up, 赶上控件的执行进度 ) 。因为不执行 ProcessPostData 方法那么此时动态创建的控件仍旧是 ViewState 的值而不会是最新值,此时它们也不会触发更改事件 (Changed events)
触发事件 (Raised Events)
接着执行的方法是 ProcessPostData, 实际上这是第二次执行这个函数 ( 第一次执行是在 OnLoad 方法执行前 ) 。同样这个方法也是仅仅在页面回传的时候 (Post Back) 才会执行。这个方法在页面周期中执行两次往往让人觉得奇怪。这个方法再次被调用执行主要是出于对 OnLoad 事件中动态生成的控件赋回传值 (Post Back Data) 的目的,并且使其可以触发对应的变化事件 (Changed events) 。如果在这个方法以后再动态创建的控件 ( 最后的机会 ) ,那么动态生成的控件由于生成进度的追赶 (executed to catch up, 赶上控件的执行进度 ) 仍然会被赋予 ViewState 中的值,但是将不会被得到页面回传的值 (Post Back Data) 同时也不会触发其所有的变化事件 (Changed events) 这里需要提一下的是,在设计页面生命周期的时候在 OnLoad 事件的前面和后面执行 ProcessPostData 是必要。第一次执行是为了保证在此之前所有控件的状态已经更新为最新值,因为这些控件的可能会在 OnLoad 事件中被使用。而第二次调用仅仅是为了保证在 OnLoad 中生成的动态控件可以被赋予回传的值,并能正确的触发变化事件 (changed evetns) 。实际上个人感觉这样的设计也是一种补救的方法,因为毕竟从页面的后台代码直观的看 Page_Load 是我们能操作的第一个事件方法,也是用的最频繁的方法。我们在后续的阶段中依然可以动态的创建控件,但是这些控件就会存在一些先天性的缺陷了。 :)
接着被调用的方法是 RaiseChangedEvents ,这个方法也仅仅在页面回传以后才会被触发执行,并且由于它是一个 Page 类的一个私有方法所以也无法被重写。这个时候控件的变化事件 (Changed events) 将被触发。通过 Reflector 查看 ProcessPostData 的源代码可以发现在函数执行的过程中,系统会比照控件的 ViewState 值和页面最后一次的回传值 (Post Back Data) ,如果两个值之间存在不同那么此控件将会被加入到一个需要触发变化事件的控件列表中。在这里系统再去读取这个控件列表然后一个个的触发控件的变化事件 (Changed events) 。但是控件的触发顺序是没有保证的,所以如果某个变化事件响应函数中的代码中需要使用其他控件变化事件响应函数中产生的结果,这样的代码是不能保证被正常执行的。但是可以肯定的是 Changed events 要比其他类型的事件先执行,如: Button.Click 事件。
接着被执行的方法是 RaisePostBackEvent ,这个方法也仅仅在页面回传后才会被触发。 同样由于它是 Page 类中定义的私有方法所以也无法被重写。这个方法通常是由于 submit …> 的控件触发的页面提交,当然也可以通过调用 Form.submit(); 方法来进行提交操作。实际上如果对 DropDownList 控件设置了 AutoPostBack = True 的时候,在 HTML 页面中系统会自动生成一段 JavaScript 代码:
< script language = " javascript " >

script >
。在 __doPostBack 函数最后一句为: theform.submit() ;这个实际就是下拉菜单选项变化后 (onchange) 触发页面回传的原因。如果在此之前页面验证控件的 Validate 方法没有被触发的话那么这里也会自动调用验证控件的 Validate 方法对数据的合法性进行验证操作。值得注意的是在 IE 浏览器中可能由于回车 (Enter) 的原因造成一次无效的回传,这是 IE 的一个 Bug
下一个执行的是 OnPreRender 方法,这个方法是最后可以对页面和其中的所有控件进行操作的地方。你在这个阶段依然可以动态的生成页面控件,所有控件生成周期都会被执行 ( 追赶加载 ) ,包括 ViewState 也会被赋值到对应的动态控件中,但是早期的那些私有方法 (ProcessPostData, RaiseChangedEvents, RaisePostBackEvent) 将不会被执行。这就意味着页面最后一次回传的值将不会影响页面上的控件,并且所有控件的事件 ( 变化类事件 Changed events 回传事件 PostBackEvents) 都不会被触发。 (This is a good place to catch a PostBack without an event due to the bug noted in IE)
保存 ViewState 值和页面渲染 (Save and Render)
接着被执行的方法是 SaveViewState ,这个方法将在每次页面的生命周期都被调用执行 ( 无论页面是否是回传还是第一次被请求 ) SaveViewState 方法通过递归的方法遍历每个控件自身和其所有子控件,并将它们的 ViewState 进行保存。一般地, (ViewState basically stores any property values that are different from the original values that are defined in the aspx page) ViewState 会保存页面 (.aspx) 中定义的所有控件属性中和上一次回传值不一致的值,无论这个值是通过代码进行修改的还是通过用户在页面上修改的。值得注意的是 ViewState 的值是根据控件在页面整个控件树中的位置进行保存的,所以如果动态控件如果被加入到了页面控件树的错误位置可能会导致页面 ViewState 在保存的时候出现异常情况。
接着 SavePageStateToPersistenceMedium 方法被调用,这个方法将 SaveViewState 方法得到的 ViewState 值保存到一个持久的数据保存介质中, ASP.NET 默认的保存方式是保存在页面的一个隐藏域 (hidden field) 中。如果你需要将 ViewState 保存到 Session 或者其他自定义的数据保存媒介中,那么和 LoadPageStateFromPersistenceMedium 相对应的,你需要重写 SavePageStateFromPersistenceMedium 方法。比如 Session 方法是移动页面中保存 ViewState 的方法,这样的保存方式将对那些如同移动设备 (e.g. PDA, 智能手机、拨号上网等 ) 低带宽的接入设备很有效。
然后执行的是 Render 方法,它递归的遍历页面中所有控件和其子控件并创建其对应的 HTML 代码,并将这些 HTML 发送到客户端的浏览器中。这个方法有时候也会被改写,比如通过在回传客户端浏览器的 HTML 代码中增加网站统一的 Title/Banner 和页脚信息,当然我们通过服务器控件的方式也可以实现同样的效果 (MasterPage) 。值得注意的是这里所有的改动应该都是基于纯 HTML 代码的 (pure html) ,应为这个时候所有控件都已经被转化成了其对应的 HTML 代码。你你可以通过使用 StringBuilder, StringWriter, HtmlTextWriter 等类和其包含的方法来取得页面生成的 HTML 代码结果。
页面生命周期的最后一个被调用的方法为 OnUnload 方法,在这个方法中调用了 Dispose 方法。在这个方法中你可以释放一些代码中使用的非托管资源 (unmanaged resources) ,比如关闭那些被打开的文件或者数据库的连接。需要注意的是这个方法是在 Render 方法之后被调用的,所以此时客户端需要接收的 HTML 代码已经被发送出去了。这里将无法再对客户端浏览器中展现的页面进行任何的修改,可以操作的仅仅是对服务器对象进行操作,并且在页面的跟踪信息中这个阶段也不会被显示出来。到此整个页面生命周期就结束了。在下一个请求到来后整个页面周期将重新按照上面的流程执行一次。如此循环。
Listing 1: Page Events Summary
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
 
作者简介 (Author Bio)
Paul Wilson is a software architect in Atlanta, currently with a medical device company. He specializes in Microsoft technologies, including .NET, C#, ASP, SQL, COM+, and VB. His WilsonWebForm Control allows Multiple Forms and Non-PostBack Forms in ASP.NET. He is a Microsoft MVP in ASP.NET and is also recognized as an ASPFriend's ASPAce/ASPElite. He is a moderator on Microsoft's ASP.NET Forums, as well as one of the top posters. He is certified in .NET (MCAD), as well as also holding the MCSD, MCDBA, and MCSE. Please visit his website, www.WilsonDotNet.com, or email him at [email protected]

你可能感兴趣的:(.NET,Framework,.NET,开发)