Page类与Control类的生命周期(life cycle)比较总结[转]
最近一段时间在使用Microsoft SharePoint2007,进行开发一个Document管理系统,里面有使用SharePoint Designer设计Page布局,需要使用自定义开发的Webpart控件,由于本来就没有进行搞控件开发了,再加上有句名言“知识就是力量”,那没得知识就没得力量了,所以没得办法,就利用Baidu + Google,googling了半下午。结果再次验证了Google的搜索实力确实比Baidu强悍啊,在此为Google最近的决定感到失望,牢骚就不多发了,还是进正题。以下是自己的一些新的总结和找到的一些学习资源。希望可以share下.
首先了解到,Page类继承自模板控件类TemplateControl 和Http处理程序接口IHttpHandler,
1.TemplateControl类是个abstract类,继承自Control类,ID命名容器控件INamingContainer,
和设备筛选器接口IFilterResolutionService。
2. IHttpHandler接口是为了使用Http处理程序同步处理Http Web请求而实现的协定。有一个只Set的IsReusable属性和一个方法ProcessRequest(HttpContext context)方法,用来处理Http Web请求。
既然Page间接的继承自Control类,那下面就先从介绍Control类说起。
Control类的生命周期有七个大的事件阶段,分别为Init事件,Load事件,DataBinding事件,PreRender事件,Render方法,Unload事件,Disposed事件。下面简单介绍下每个阶段;
1.Init阶段,引发Init事件,执行OnInit(EventArgs e)虚方法。详细请参见处理继承的事件。
2.然后执行TrackViewState(object savedState)虚方法,会根据页面IsPostBack属性,来确定是否LoadViewState(objcet savedState)来加载视图状态,以及如果控件继承自接口IPostBackDataHandler,还会实现LoadPostData(string postDataKey,System.Collections.Specialized.NameValueCollection postCollection),来处理回发数据。详细请参见处理回发数据和维护控件中的状态
3.Load阶段,引发Load事件,执行OnLoad(EventArgs e)虚方法。此时,Load事件树中的服务器控件已创建并初始化、状态已还原并且窗体控件反映了客户端的数据。详细参见处理继承的事件。
4.之后根据页面IsPostBack属性,如果实现了接口IPostBackDataHandler,则调用RaisePostDataChangedEvent() 方法,来处理引发更改事件以响应当前和以前回发之间的状态更改。如果实现如果已实现 IPostBackEventHandler接口,则引发RaisePostBackEvent(string eventArgument)方法的实现来处理,来处理引起回发的客户端事件,并在服务器上引发相应的事件。详细参见处理回发数据和捕获回发事件。
5.PreRender阶段,引发PreRender 事件,执行OnPreRender(EventArgs e)方法,在呈现输出之前执行任何更新。可以保存在预呈现阶段对控件状态所做的更改,而在呈现阶段所对的更改则会丢失。此阶段是对控件做任何更新的最后机会。详细请参见处理继承的事件。
6.SaveViewState阶段,在此阶段后,自动将控件的 View State 属性保持到字符串对象中。此字符串对象被发送到客户端并作为隐藏变量发送回来。为了提高效率,控件可以重写 SaveViewState() 方法以修改 View State属性。详细请参见维护控件中的状态。
7.呈现Render阶段,此阶段没有不适事件,执行Render()方法,来生成呈现给客户端的输出。详细参见呈现 ASP.NET 服务器控件。
8.Dispose阶段,执行Disponse()方法,执行销毁控件前的所有最终清理操作。在此阶段必须释放对昂贵资源的引用,如数据库链接。详细参见ASP.NET 服务器控件中的方法。
9.卸载Unload阶段,引发Unload事件,执行OnUnload()方法,来执行销毁控件前的所有最终清理操作。控件作者通常在 Dispose 中执行清除,而不处理此事件。
以下2点要有所注意:
1.要重写 Event Name 事件,请重写 OnEventName 方法(并调用 base.OnEventName)。
2.之上的方法和事件基本在自己创建控件时都可以重写 System.Web.UI.Control的abstract方法,但可以看出以下几个除外:LoadPostData 和 RaisePostDataChangedEvent 是 IPostBackDataHandler 接口的方法,而 RaisePostBackEvent 属于 IPostBackEventHandler 接口。如果控件参与回发数据处理,则必须实现 IPostBackDataHandler。如果控件收到回发事件,则必须实现 IPostBackEventHandler。
3.以上没有列出 CreateChildControls 方法,这是由于每当 ASP.NET 页框架需要创建控件树时就会调用该方法,且该方法调用并不限于控件生命周期的特定阶段。例如,可以在加载页时、在绑定数据过程中或者在呈现过程中调用 CreateChildControls,以及在动态创建控件的时候也可以调用的。
下面给个自己创建个WebPart控件示范,功能比较简单,在页面上面实现Ifame实现内嵌页面的效果。
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Web;
- using System.Web.UI;
- using System.Web.UI.HtmlControls;
- using System.Web.UI.WebControls.WebParts;
- namespace IframeWebpart
- {
- public class Iframe:WebPart,INamingContainer
- {
- private int _width = 100;
- private int _height = 100;
- private HtmlTableCell _htc = new HtmlTableCell();
- private string _url = @"http://www.baidu.com";
- [WebBrowsable(true),Personalizable(true)]
- public int IWidth
- {
- get { return _width; }
- set { _width = value; }
- }
- [WebBrowsable(true),Personalizable(true)]
- public int IHeight
- {
- get { return _height; }
- set { _height = value; }
- }
- [WebBrowsable(true), Personalizable(true)]
- public string Url
- {
- get { return _url; }
- set { _url = value; }
- }
- protected override void CreateChildControls()
- {
- //base.CreateChildControls();
- this.Controls.Add(new LiteralControl("<table>"+"\n"));
- this.Controls.Add(new LiteralControl("<td>"+"\n"));
- this.Controls.Add(this._htc);
- this.Controls.Add(new LiteralControl("</td>"+"\n"));
- this.Controls.Add(new LiteralControl("</table>"+"\n"));
- if (!this.Page.IsPostBack)
- {
- this.AddControls();
- }
- }
- protected override void OnLoad(EventArgs e)
- {
- base.OnLoad(e);
- //if (!this.Page.IsPostBack)
- //{
- // this.AddControls();
- //}
- }
- protected override void Render(HtmlTextWriter writer)
- {
- base.Render(writer);
- }
- private void AddControls()
- {
- this._htc.Controls.Add(new LiteralControl("<iframe id='iframe1' src=""+this.Url+"" mce_src=""+this.Url+"" width='"+this.IWidth+"' height='"+this.IHeight+"' marginheight='0' frameborder='0' scrolling='no' vspace='0' hspace='0' marginwidth='0' >"+"\n"));
- this._htc.Controls.Add(new LiteralControl("</iframe>"+"\n"));
- }
- }
- }
Control类的比较简单,下面介绍Page类,Page类的生命周期相对control要稍微复杂一些。(下面介绍参考了MSDN上面的介绍)
普通的常规页面生命周期有:页请求,开始阶段,初始化,加载,验证,回发事件处理,呈现,卸载,除了页生命周期阶段以外,在请求前后还存在应用程序阶段,但是这些阶段并不特定于页。以下详细介绍;
1、页请求:发生在页面生命周期之前,用户请求页时,ASP.NET将确定是否需要分析和编译页,从而确定是否开始页面的生命周期,或者是否可以在不运行页的情况下发送页面缓存以进行响应。
2、开始:设置页属性,如:HttpContext以及其他属性;在此阶段,页面需要确定是回发请求还是新请求,并设置IsPostBack属性;设置页面的UICulture属性。
3、页面初始化:加载所有主题;控件生成,并设置UniqueID;
注:ViewState、ControlState中的值还未加载至控件;如果页面是回发,则回发数据也还未加载;故此时控件可以访问,但值可能出错。
4、加载:如果当前请求是回发请求,则为控件加载ViewState和ControlState中的值。
5、验证:调用所有验证程序控件的Validate方法,此方法将设置验证程序控件和页的IsValid属性。
6、回发事件处理:如果请求是回发请求,则调用所有事件处理程序。
7、呈现:首先对该页和所有控件进行保存视图状态,然后对每个控件调用Render方法,它会提供一个文本编写器,用于将控件的输入写入页的Response属性的OutputStream中。
8、卸载:完成呈现,并已将页发送至客户端、准备丢弃该页后,调用卸载。将卸载属性如:Response和Request等等,故之后如果在调用这些对象,都将出错异常的。
在介绍Asp.net之前先来看两张关于页面的请求过程图片:
第一张是个普通的页面请求过程
第二张是个是个添加的Button按钮控件PostBack后的页面请求过程。
比较这两张图片可以很清晰的得出以下两点内容。
一. Asp.Net的页面生命周期,基本都有如下几个阶段;
PreInit,
Init,
InitComplete,
PreLoad,
Load,
LoadComplete,
PreRender,
Render,
RenderComplete,
SaveState,
SaveStateComplete,
Render,
UnLoad,
二.在页面的响应生命周期阶段,控件的生命周期阶段也在相应同步进行着。
下面也来个粗略的介绍;
1. PreInit阶段
此阶段引发PreInit事件,执行OnPreInit()方法,完成的操作,有比如查IsPostBack属性来确定是
不是第一次处理该页;创建或重新创建动态控件,动态设置主控页,动态设置Theme属性,读取或设置配置文件属性。注:如果请求是回发请求,则控件的值尚未从视图状态恢复,即:不应该在此事件中设置控件属性。
2. Init阶段
在所有控件都已初始化,且应用了外观后由里到外引发Init事件,执行由自Control类继承得OnInit()方法,使用该事件来读取或初始化控件属性。
3. InitComplete阶段
由Page对象引发。使用该事件来处理要求先完成所有初始化工作的任务。执行OnInitComplete()方法。
4. PreLoad
在 Page 引发该事件后,执行OnPreLoad(),它会为自身和所有控件加载视图状态,然后会处理 Request 实例包括的任何回发数据,如果需要在 Load 事件之前对页或控件执行处理,请使用该事件。
5. Load
Page在Page上调用OnLoad事件方法,然后以递归方式对每个子控件执行相同操作,如此循环往复,直到加载完本页和所有控件为止。使用OnLoad()事件方法来设置控件中的属性并建立数据库连接。
6. 控件事件
使用这些事件来处理特定控件事件,如 Button 控件的 Click 事件等。注:在回发请求中,如果页包含验证程序控件,请在执行任何处理之前检查Page和各个验证控件的IsValid属性。
7. LoadComplete
引发LoadComplete事件,执行OnLoadComplete()方法,对需要加载页上的所有其他控件的任务使用该事件。
8. PreRender
引发PreRender事件,执行由Control类继承来的OnPreRender()方法,使用该事件对页或其控件的内容进行最后更改。注:在该事件发生前的操作:Page对所有控件递归进行EnsureChildControl操作设置了DataSourceID属性的数据绑定控件会调用DataBind方法。
9. PreRenderComplete
引发PreRenderComplete事件,执行的On PreRenderComplete方法。
10. SaveStateComplete
引发SaveStateComplete事件,执行由OnSaveStateComplete(EventArgs e)方法,在该事件发生前,
已针对页和所有控件保存了ViewState。将忽略此时对页或控件进行的任何更改。。
11.Render
这不是事件;在处理的这个阶段,Page 对象会在每个控件上调用此方法。所有 ASP.NET Web 服务
器控件都有一个用于写出发送给浏览器的控件标记的 Render 方法。如果创建自定义控件,通常要重写此
方法以输出控件的标记。不过,如果自定义控件只合并标准的 ASP.NET Web 服务器控件,不合并自定
义标记,则不需要重写 Render 方法。有关更多信息,请参见开发自定义 ASP.NET 服务器控件。用户
控件(.ascx 文件)自动合并呈现,因此不需要在代码中显式呈现该控件
12.Unload
引发Unload事件,执行由Control类继承来的OnUnLoad()方法,对子控件由里向外执行此方法。
注意点。当从 Page 类继承类时,除了可以处理由页引发的事件以外,还可以重写页的基类中的方法。例如,可以重写页的 InitializeCulture 方法,以便动态设置区域性信息。注意,在使用 Page_事件语法创建事件处理程序时,将隐式调用基实现,因此无需在方法中调用它。例如,无论是否创建 Page_Load 方法,始终都会调用页基类的 OnLoad 方法。但是,如果使用 override 关键字(在 Visual Basic 中为 Overrides)重写页的 OnLoad 方法,则必须显式调用基方法。例如,如果在页中重写 OnLoad 方法,则必须调用 base.Load(在 Visual Basic 中为 MyBase.Load)以运行基实现
以上就是本人的理解和总结,比较简单,可以说只有的理解了简单Control类和Page类的通用生命周期,以后才可以分析复杂的问题,比如动态创建控件时的数据丢失问题等。再比如目前有不少人喜欢在开发Web时候,比较喜欢直接让新建的Page页面继承自Page类,在这个继承来里面来写一些实现用户身份验证功能,但是如果对页面的生命周期比较了解后,可以这样来做,首先写个继承自Page类的类,在这个类里面根据需要来重写abstract方法和写一些通用的用户身份验证方法,之后让自己的页面后台代码类来实现这个类,从而达到通用的效果。这样做对代码复用性的提高,大家觉得是否有帮助呢?
以上拙见,如有错误之处,请帮忙指教。
参考资料
2. 《庖丁解牛:纵向切入ASP.NET 3.5控件和组件开发技术》
3. 《Programming ASP.NET》学习笔记(控件)
4. SnowQuery的专栏