ASP.NET页面生命周期与优化
作为一个ASP.NET开发,特别是进行ASP.NET控件模型的开发,了解个掌握ASP.NET页面生命周期是非常必要的,这有助于我们更加灵活的控制页面,以我们需要的方式编程开发。ASP.NET页运行时,此页会经历一个又一个的事件链,每个事件链中执行不同的行为,这所有的行为共同组成我们所需要的页面。
ASP.NET控件模型
在此模型中,微软将基于ASP.NET控件模型的开发看做是一个又一个的控件,如同我们在绘画时,不是拿着笔在一张白纸上作画,而更像是在玩拼图游戏,我们只需要经各个部分拼接好,让这些拼图共同来构成一张画。我们的Page就像个被抽象了的容器,我们需要在玩拼图的过程中将需要的版块拿进来,并放在合适的地方。然后一切的一切控件什么TextBox,Button,DropDownList,DataList再如同什么MasterPage,UserControl均是我们玩拼图游戏时将可能运用到得“图片”。
ASP.NET控件模式是微软的一个创举,他们提出了一种全新的变成思想,从而大大降低了web开发的门槛,使得入门更加容易
然而进一步,微软为我们做了更多,他甚至让我们根本不需要关心着些“图片”什么时候放入容器,也不需要我们关心如何来维护这些“图片”,我们要做的就是选择“图片”和为图片添加“行为”,但是为了开发的灵活性,我们需要理解这些“图片”如何加载,如何维护等知识。
ASP.NET页面生命周期
引用MSDN上的一张表:
一般来说,页要经历下表概述的各个阶段。
阶段 |
说明 |
页请求 |
页请求发生在页生命周期开始之前。用户请求页时,ASP.NET 将确定是否需要分析和编译页(从而开始页的生命周期),或者是否可以在不运行页的情况下发送页的缓存版本以进行响应。 |
开始 |
在开始阶段,将设置页属性,如 Request和 Response在此阶段,页还将确定请求是回发请求还是新请求,并设置 IsPostBack 属性。此外,在开始阶段期间,还将设置页的 UICulture 属性。 |
页初始化 |
页初始化期间,可以使用页中的控件,并将设置每个控件的 UniqueID 属性。此外,任何主题都将应用于页。如果当前请求是回发请求,则回发数据尚未加载,并且控件属性值尚未还原为视图状态中的值。 |
加载 |
加载期间,如果当前请求是回发请求,则将使用从视图状态和控件状态恢复的信息加载控件属性。 |
验证 |
在验证期间,将调用所有验证程序控件的 Validate 方法,此方法将设置各个验证程序控件和页的 IsValid 属性。 |
回发事件处理 |
如果请求是回发请求,则将调用所有事件处理程序。 |
呈现 |
在呈现之前,会针对该页和所有控件保存视图状态。在呈现阶段中,页会针对每个控件调用 Render 方法,它会提供一个文本编写器,用于将控件的输出写入页的 Response 属性的 OutputStream 中。 |
卸载 |
完全呈现页并已将页发送至客户端、准备丢弃该页后,将调用卸载。此时,将卸载页属性(如 Response 和 Request)并执行清理。 |
可以看出,页面经历了许多个步骤才最终形成我们所看到的页面。然后各个步骤都在做不同的事,我们可以重载掉这些方法,然后添加自己的逻辑
Public class BasePage: System.Web.UI.Page { Protected override void OnInit(EventArgs e) { //TODO… } ….. }
然后在我们的页面中使用我们重载过后的Page,我们先观察aspx页面的头部会发现这么一句:CodeBehind=”…..cs” Inherits=”……cs”,前一个是该页面的后置代码,后一个就是该页面继承至某一个类,然后我们打开后置的代码文件。可以看到该类继承至System.Web.UI.Page,这时怎么修改已经很明了了,只需要将这里的继承改成我们自己的BasePage就可以了
在重载System.Web.UI.Page 的时候会发现每个阶段的On事件处理函数,下面对几个常用的处理函数进行分析,按照执行顺序,如下:
1、OnPreInit:当框架调用该方法时,引发PreInit事件。在该事件中主要是应用App_Themes中的主题,与应用母版页,通常这一步我们不需要做什么
2、OnInit: 在这一步中,框架将初始化该页面所应用到得控件,然后将应用的Themes应用给控件,递归的触发子控件的Init方法。
3、OnInitComplete:这是一个标志事件,他的调用标示这初始化完成,所有子控件均已经初始化完毕,所以Themes都已经应用。
4、OnPreLoad:该事件标志着页面进入加载状态,在该事件中,框架会为自身和所有控件加载视图状态,然后会处理Request实例包含所有回发数据。
5、OnLoad:Load事件是我们通常运用最平凡的事件,也就是Page_Load事件,Page_xxx是所有时间的一种时间链接形式,如Page_Init将调用OnInit方法。这个事件中可以处理回发,由于视图状态,Themes,子控件都已经初始化完毕,所以便于我们添加逻辑而不必担心被其他事件把值给覆盖掉。值得注意的是,任何页面的Load顺序是先自己,再子控件,如:一个aspx页面被调用Load事件,先加载本身,然后再加载如母版页、用户控件、第三方服务器控件等。
6、OnLoadComplete:标志着所有控件的加载完成。下一步则是将加载内容进行呈现。
7、OnPreRender:该事件处理准备呈现的一些工作,如,对设置了DataSource的控件进行DataBind,通常,我们可以在这个事件上动态修改某些控件的值。
8、OnPreRenderComplete:该事件标志着准备呈现完成,数据已经完成绑定。
9:SaveViewState:该事件中,将序列化ViewState,然后再页面中以一个input:hidden的元素保存试图状态。
10:Render:在这个阶段,框架会调用所有控件的Render方法,进行页面呈现。以生产html页。
11:Unload:在页面生命周期结束后,进行卸载。
跳出ASP.NET控件模型
从以上的分析可以看出,ASP.NET控件模型的编程方式是纯服务器端得编程,我们做的一切工作都是服务器端得开发,就算是写aspx页面,也只是在告诉框架,我需要些什么东西,这些东西我需要他按照什么样的形式来工作,然后框架会按照我们制定的方法进行生成一张全新的html页面递交给客户端(严格来说是生成一个流)。
是否感觉这样的一个流程很规范,但却有些臃肿,有时,我们仅仅是需要一个input:text,为什么我却要写一个asp:TextBox,还要需要初始化一个TextBox对象,还要序列化、保存ViewState。有时,我数据命名已经准备好了,为什么不能直接生成html代码,还需要ViewState、还要加载,然后呈现。诸多问题让我们感觉,微软视乎给我们提出了一个非常便捷,简易的变成模式,但却引入了很多臃肿的流程。
其实,我们完全可以跳出控件模型的编程思想。这时,还记得asp的编程方式吗?或则,有试过php吗?再或者,对javascript的工作方式是不是还记忆犹新?对,改变其实很简单,请看以下工程实例。
这个示例工程包含表现层,业务逻辑,资源访问层和公共资源层,表现层通过ServiceClient工程访问业务逻辑层,公共资源层其中包含模型,和一些Helper方法。
目前,我们需要讨论的是如何跳出ASP.NET控件模型的编程方式,现在我们需要关心的是PL.Web工程的Default.aspx文件,可以看到这个文件没有后台cs代码。具体页面代码如下:
<%@ Page Language="C#" AutoEventWireup="true" Inherits="PL.Handle.BasePage" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <title></title> <script runat="server"> private List<DemoModel> list; private void Page_Load(object sender, EventArgs e) { HttpMehotd httpmethod = Request.GetHttpMethod(); //TODO 这里通常是为了准备一些数据,如这里可以调用PL.ServiceClient中的方法,通过BLL获取数据, //但为了演示方便这里仅仅是初始化一些简单数据 list = new List<DemoModel>() { new DemoModel(){Title="标题",Content="内容"}, new DemoModel(){Title="标题1",Content="内容1"} }; //如果是Post表单,交与post处理函数进行处理 if (httpmethod == HttpMehotd.Post) { HttpPostHandle(); } } private object HttpPostHandle() { //TODO return null; } </script> </head> <body> <form method="post" action="Default.aspx"> <%if (list != null) { foreach (var item in list) {%> <div><%=item.Title %></div> <div><%=item.Content %></div> <%} } %> <select name="seldemo"> <%if (list != null) { foreach (var item in list) {%> <option value="<%=item.Title %>"><%=item.Content %></option> <%} } %> </select> <input type="submit" value="提交" /> </form> </body> </html>
首先我删除了CodeBehind属性,并将Inherits指向了一个BasePage类,该类继承至System.Web.UI.Page。在head标签中添加一个script标签,并设置该标签的runat属性为server标示这是服务器段代码,在该标签中书写处理逻辑。之后在body标签中,去掉了form标签的runat属性,并手动设置了method为post,action为本身,就是说当提交表单时,是提交给自己进行处理。最后在form中按照预定逻辑控制需要生产的html代码。这样一来,虽然任然要走诸多生命周期事件,但是由于页面中不存在任何服务器控件,所以这一步大大被简化,当处理动态数据时我们也仅仅是按照html的预定模式进行控制输出,而不存在处理数据绑定,序列化等繁杂的工作。
也许您已经发现。这正是MVC的思想,Page工程其实只是一个View,而ServiceClient是Controller,Data.Models工程就是所谓的Model。自然而然,运用这样的变成模型也是有利于单元测试。
前台后台分离
这里所描述的前台后台分离知道是,前台开发人员不需要知道任何后台开发的知识,后台开发人员需要的仅仅是纯html页面,然后就能够进行后台开发。如果是ASP.NET控件模型这一点是很难办到的。
最后,希望本篇文章能对大家有所帮助,也希望以上编程模型能得到大家的应用,优点处理前后台分离以外,还有缩短处理周期,便于单元测试,便于扩展等诸多优点。
参考文献
《MSDN ASP.NET页生命周期概述》:
http://msdn.microsoft.com/zh-cn/library/ms178472(VS.80).aspx