ASP.Net Web Page深入探讨(二)

五、页面生存周期

现在回到第三个标题中讲到的内容,我们讲到了 HttpApplication 的实例接收请求,并创建页面类的实例,实际上这个实例也就是动态编译的 ASPX 的类的一个实例,上一个标题中我们了解到 ASPX 实际上是代码绑定中类的子类,所以它继承了所有的 protected 方法。

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

#region Web Form Designer generated code

override protected void OnInit(EventArgs e)

{

//

// CODEGEN:该调用是 ASP.NET Web 窗体设计器所必需的。

//

InitializeComponent();

base .OnInit(e);

}

///

1<summary>
2
3///  设计器支持所需的方法 - 不要使用代码编辑器修改 
4
5///  此方法的内容。 
6
7///  </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 页框架可使执行状态管理相对容易一些,但是为了获得连续性效果,控件开发人员必须知道控件的执行顺序。控件开发人员需要了解:在控件生命周期的各个阶段,控件可使用哪些信息、保持哪些数据、控件呈现时处于哪种状态。例如,在填充页上的控件树之前控件不能调用其父级。”

“下表提供了控件生命周期中各阶段的高级概述。有关详细信息,请点击表中的链接。”

** 阶段 **

|

** 控件需要执行的操作 **

|

** 要重写的方法或事件 **

---|---|---

初始化

|

初始化在传入 Web 请求生命周期内所需的设置。请参阅 处理继承的事件 。

|

** Init ** 事件( OnInit 方法)

加载视图状态

|

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

|

** LoadViewState ** 方法

处理回发数据

|

处理传入窗体数据,并相应地更新属性。请参阅 处理回发数据 。

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

|

** LoadPostData ** 方法

(如果已实现 ** IPostBackDataHandler ** )

加载

|

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

|

** Load ** 事件

( ** OnLoad ** 方法)

发送回发更改通知

|

引发更改事件以响应当前和以前回发之间的状态更改。请参阅 处理回发数据 。

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

|

** RaisePostDataChangedEvent ** 方法

(如果已实现 ** IPostBackDataHandler ** )

处理回发事件

|

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

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

|

** RaisePostBackEvent ** 方法

(如果已实现 ** IPostBackEventHandler ** )

预呈现

|

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

|

** PreRender ** 事件

( ** OnPreRender ** 方法)

保存状态

|

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

|

** SaveViewState ** 方法

呈现

|

生成呈现给客户端的输出。请参阅 呈现 ASP.NET 服务器控件 。

|

** Render ** 方法

处置

|

执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库链接。请参阅 ASP.NET 服务器控件中的方法 。

|

** Dispose ** 方法

卸载

|

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

|

** UnLoad ** 事件( ** On UnLoad ** 方法)

从这个表里面我们可以清楚的看到一个 Page 从装载到 卸载之间调用的方法和触发的时间,接下来我们就深入的对其进行一些分析。

看了上面的表,细心的朋友可能要问了,既然OnInit是页面生命周期的开始,而我们在上一讲中谈到控件在子类中被创建,那么在这里实际上在InitializeComponent方法中我们已经可以使用父类中声名的字段了,那么就意味着子类的初始化更在这之前?

在第三个标题中我们讲到了页面类的ProcessRequest才是真正意义上的页面声明周期的开始,这个方法是由HttpApplication调用的(其中调用的方式比较复杂,有机会单独撰文来讲解),一个Page对请求的处理就是从这个方法开始,通过反编译.Net类库来查看源代码,我们发现在System.Web.WebControls.Page的基类:System.Web.WebControls.TemplateControl(它是页面和用户控件的基类)中定义了一个“FrameworkInitialize”虚拟方法,然后在Page的ProcessRequest中最先调用了这个方法,在生成器生成的ASPX的源代码中我们发现了这个方法的踪影,所有的控件都在这个方法中被初始化,页面的控件树就在这个时候产生。

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

1、 初始化

初始化对应 Page 的 Init 事件和 OnInit 方法。

如果要重写, MSDN 推荐的方式是重载 OnInti 方法,而不是增加一个 Init 事件的代理,这两者是有差别的,前者可以控制调用父类 OnInit 方法的顺序,而后者只能在父类的 OnInit 后执行(实际上是在 OnInit 里面被调用的)。

2、 加载视图状态

这是个比较重要的方法,我们知道,对于每次请求,实际上是由不同的页面类实例来处理的,为了保证两次请求间的状态, ASP.Net 使用了 ViewState ,关于 ViewState 的描述,请参考本人的另一篇文章:

http://expert.csdn.net/Expert/topic/1558/1558798.xml?temp=.2561609

LoadViewState 方法就是从 ViewState 中获取上一次的状态,并依照页面的控件树的结构,用递归来遍历整个树,将对应的状态恢复到每一个控件上。

3、 处理回发数据

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

public virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection)

postDataKey 是标识控件的关键字(也就是 postCollection 中的 Key ), postCollection 是包含回发数据的集合,我们可以重写这个方法,然后检查回发的数据是否发生了变化,如果是则返回一个 True ,“ 如果控件状态因回发而更改,则 ** LoadPostData ** 返回 ** true ** ;否则返回 ** false ** 。页框架跟踪所有返回 ** true ** 的控件并在这些控件上调用 ** RaisePostDataChangedEvent ** 。”(摘自 MSDN )

这个方法是 System.Web.WebControls.Control 中定义的,也是所有需要处理事件的自定义控件需要处理的方法,对于我们今天讨论的 Page 来说,可以不用管它。

4、 加载

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

Page_Load 方法响应了 Load 事件,这个事件是在 System.Web.WebControl.Control 类中定义的(这个类是 Page 和所有服务器控件的祖宗),并且在 OnLoad 方法中被触发。

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

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

<P

Published At
Categories with Web编程
Tagged with
comments powered by Disqus