转自: http://www.cnblogs.com/GodSpeed/archive/2010/07/15/1777941.html
ASP.NET应用程序生命周期趣谈(四) HttpHandler和页面生命周期
PS:本文很长,建议倒杯水拿点儿干粮再回来看,谢谢。
在之前的三篇文章中,我们还算简明扼要的学习了 asp.net 的整个生命周期,我们知道了一个 Request 进来以后先去 ISAPI Filter ,发现是 asp.net 程序后又 ASPNET_ISAPI.dll 这个 ISAPI Extension 来进行处理。在 ASPNT_ISAPI 创建了 Worder Process 后,在管道中经过 HttpModule 的处理来到 HttpHander 的手中。
我们知道 P_Handler 程序员使用 “ 乾坤大挪移 ” 对页面进行了处理后又通过管道中的 HttpModule 把 response 返回给了客户端。
那么,这么
l 所谓的 “ 乾坤大挪移 ” 到底是个什么功夫呢?
l P_Handler 又是如何被调用如何消亡的呢?
l 我们自己是否也可以创建 HttpHandler 呢 ?
l HttpHandler 和我们常用的 .ashx 又是什么关系呢?
我们就通过今天的简单讲述让大家对这些过程有个大致的了解。
从字面上我们也可以看出, HttpHandler 就是 handle http ( request )的,就像程序员,就是(做)程序的人员一样,所以 P_Handler 就是个典型的 HttpRequest 处理人员。我们也知道 IHttpHandler 接口,它就像是一个认证,任何通过了 IHttpHandler 认证(实现了这个接口)的人员( handler ),都可以处理 HttpRequest 。这个认证主要有两个内容(方法): 1 , ProcessRequest ; 2 , IsReusable 。
ASP.NET 默认有很多个 Handler ,它们处理了各式各样的 asp.net 文件,例如 .config , .cs , .aspx 等等。通过路径 C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/CONFIG/web.config 找到 web.config 文件我们可以发现系统预定义的 httphandler ,因为太多在这就不一一列举。
通过 < add path = "*.config " verb = "* " type = "System.Web.HttpForbiddenHandler " validate = "true " /> 我们可以知道我们项目中的 web.config 文件为什么不能访问了,原来是被 HttpForbiddenHandler 给屏蔽了,我估计它是直接返回了一个错误,这样我们就不能访问这些资源,同样的, .sitemap, .asax, ..cs, .csproj 等等我们熟悉的项目中的文件都是使用 HttpForbiddenHandler 来屏蔽掉的。
而但是通过 <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="true" /> 我们也可以发现原来 .aspx 是通过 PageHandlerFactory 来处理的。那么 PageHandlerFactory 又是什么东西呢?
通过反编译 PageHandlerFactory 我们可以看到:

2 {
3 // Fields
4 private bool _isInheritedInstance;
5 // Methods
6 protected internal PageHandlerFactory();
7 public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path);
8 private IHttpHandler GetHandlerHelper(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath);
9 public virtual void ReleaseHandler(IHttpHandler handler);
10 IHttpHandler IHttpHandlerFactory2.GetHandler(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath);
11 }
12
这个 page handler 的工厂通过 GetHandler 得到了一个 IHttpHandler ,这个 Handler 是什么呢?答案就在 GetHandlerHelper 方法中:

2 {
3 Page page = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof (Page), context, true , true ) as Page;
4 if (page == null )
5 {
6 return null ;
7 }
8 page.TemplateControlVirtualPath = virtualPath;
9 return page;
10 }
11
我们可以清楚的看到,这个方法返回了一个Page , wow ,原来 Page 类就是处理 .aspx 文件的 HttpHandler 啊,看到 Page 类的定义:
public class Page
: TemplateControl
, IHttpHandler
{
}
我们更是确信无疑: System.Web.UI.Page 就是处理 .aspx 页面的那个程序员 P_Handler 。这种程序员很平凡,却是不可或缺的,因为他们承担着一线的大量工作任务。而 PageHandlerFactory 就是专门培训这种处理页面的程序员的培训机构。这种培训机构很多,也就培养了各种各样的 Handler ,而我们自己也可以创建自己的培训机构,培训自己的 HttpHandler ,只要通过 IHttpHandlerFactory 这个认证(接口实现)就可以了。 Page 类是我们最常用的 P_Handler 程序员,它就像 .net 初级程序员一样普遍,只不过它不像我们这样什么都会,它只处理页面(因为它在一个大公司,微软啊,分工多明确啊)。
微软有程序员,我们也可以有,那么接下来我们就自己创建一个 HttpHandler ,我们用它来 Handler 我们定义的后缀名 .godspeed 文件。这个过程分为三步:
首先,我们创建一个实现了 IHttpHandler 的类
然后,在 web.config 文件中添加一行文字以便让 server 知道该如何处理 .godspeed 后缀名文件
最后,我们在 IIS 中映射我们的新后缀名到 asp.net ,好让 IIS 可以把这个文件传给 ASP.NET 进行处理并返回我们期望的结果。好,开始做。
1, 创建名为 TestCustomHttpHandler 的网站
2, 添加 test.godspeed 文件到网站中
3, 创建 GodSpeedHttpHandler.cs 文件,代码如下:

2
3 {
4
5 public GodSpeedHttpHandler()
6
7 {
8
9 }
10
11 #region IHttpHandler Members
12
13 public bool IsReusable
14
15 {
16
17 get { return true ; }
18
19 }
20
21 public void ProcessRequest(HttpContext context)
22
23 {
24
25 HttpResponse response = context.Response;
26
27 response.Write( " <html><body><h1>This is GodSpeed Handler! See, easy!</body></html> " );
28
29 }
30
31 #endregion
32
33 }
34
35
4, 配置 web.config
< handlers >
< add name = "GodSpeedHttpHandler " verb = "* " path = "*.godspeed " type = "MyHandlers.GodSpeedHttpHandler "/>
</ handlers >
5, 映射到 IIS 和 asp.net
6, 访问 test.godspeed
re
多简单啊 各位看官可是看明白了?不明白没关系,欢迎回复和拍砖。
而我们常用的 ashx 也一种非常简单的 handler ,它通常用来处理不需要回传的任务,比如生成动态图片等等,园子里有很多这样的示例,在这里就不详述了。
那么我们之前提到 Page 类只处理页面,它也是我们最最常用的,最最重要的 P_Handler 程序员,它究竟是如何工作呢?它的工作对我们的日常开发又有什么影响呢?在下一篇文章中我将着重讲解页面生命周期 — 也就是 Page 类的 ProcessRequest 都引发了哪些事件以及在这些事件中我们能做些什么 进行深入的探讨。
话说我们今天的重中之重:页面生命周期 ,说的就是 Page 类在处理页面的过程中都发生了哪些事件,而这些事件又是按照什么顺序发生的。 ASP.NET 的页面生命周期跟我们之前的理论讲解完全不同,它具有非常强大的实用性,所以我想通过一个小例子来进行解释和说明,以便让大家看起来更清晰和容易理解。因为 Page 类的 ProcessRequest 方法实现非常的复杂,我把代码反编译过来给大家看我觉得也没什么意义,所以我就着重给大家看一下整个页面的事件是以何种顺序 被引发的,以及在这些事件中,我们能做什么 。在这个过程中,我们主要关注如下问题:
1, 事件的执行顺序
2, 控件何时被初始化(我们什么时候能用它)
3, ViewState 何时可用
4, 更改 MasterPage 和 Theme
5, 在各个事件中还能干什么工作
按照如下步骤建立示例:
一, 我们创建一个 website
二, 加入两个 MasterPage 分别为 site.master 和 map.master ,分别在上面加一个 label 进行说明: this is site/map master page.
三, 分别加入两个 theme 为 BlueSkin 和 RedSkin, 分别对 button 的背景色进行设置,一个是 Blue ,另外一个是 Red
四, 在 default 页面中加入两个 label 和一个 button
五, Default 页面代码如下:

2 private string GetEventName( string eventName)
3 {
4 step += 1 ;
5 return eventName;
6 }
7
8 protected override void OnPreInit(EventArgs e)
9 {
10 base .OnPreInit(e);
11 Response.Write(GetEventName( " pre init event, this is the " + step + " th step! " ));
12 // lblMessage.Text = "on pre init";
13
14 this .MasterPageFile = " map.master " ;
15 this .Theme = " BlueSkin " ;
16 if (lblMessage2 == null )
17 {
18 Response.Write( " <span style='color:red;'>Server control has not been initialed on pre init</span> " );
19 }
20 else
21 {
22 Response.Write( " <span style='color:red;'>Server control has been initialed here</span> " );
23 }
24 if ( this .ViewState.Count > 0 )
25 {
26 Response.Write( " ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString());
27 }
28
29 Response.Write( " <br/> " );
30 }
31 protected override void OnInit(EventArgs e)
32 {
33 if (lblMessage2 == null )
34 {
35 Response.Write( " <span style='color:red;'>Server control has not been initialed on pre init</span> " );
36 }
37 else
38 {
39 Response.Write( " <span style='color:red;'>Server control has been initialed here</span> " );
40 }
41 base .OnInit(e);
42
43 Response.Write(GetEventName( " init event, this is the " + step + " the step! " ));
44 if (lblMessage2 == null )
45 {
46 Response.Write( " <span style='color:red;'>Server control has not been initialed on pre init</span> " );
47 }
48 else
49 {
50 Response.Write( " <span style='color:red;'>Server control has been initialed here</span> " );
51 }
52 if ( this .ViewState.Count > 0 )
53 {
54 Response.Write( " <span style='color:red;'>ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString() + " </span> " );
55 }
56 Response.Write( " <br/> " );
57
58 // this.MasterPageFile = "map.master";
59 // this.Theme = "BlueSkin";
60 }
61 protected override void OnInitComplete(EventArgs e)
62 {
63 base .OnInitComplete(e);
64 Response.Write(GetEventName( " init complete event, this is the " + step + " th step! " ));
65 if ( this .ViewState.Count > 0 )
66 {
67 Response.Write( " <span style='color:red;'>ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString() + " </span> " );
68 }
69 else
70 {
71 Response.Write( " <span style='color:red;'>ViewState can not be used here</span> " );
72 }
73 Response.Write( " <br/> " );
74 }
75 protected override void OnPreLoad(EventArgs e)
76 {
77 if ( this .ViewState.Count > 0 )
78 {
79 Response.Write( " <span style='color:red;'>ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString() + " </span> " );
80 }
81 else
82 {
83 Response.Write( " <span style='color:red;'>ViewState can not be used here</span> " );
84 }
85 base .OnPreLoad(e);
86 Response.Write(GetEventName( " pre load event, this is the " + step + " th step! " ));
87 if ( this .ViewState.Count > 0 )
88 {
89 Response.Write( " <span style='color:red;'>ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString() + " </span> " );
90 }
91 else
92 {
93 Response.Write( " <span style='color:red;'>ViewState can not be used here</span> " );
94 }
95 Response.Write( " <br/> " );
96 }
97 protected void Page_Load( object sender, EventArgs e)
98 {
99 Response.Write(GetEventName( " page load system provided, this is " + step + " th step! " ));
100 if ( this .ViewState.Count > 0 )
101 {
102 Response.Write( " <span style='color:red;'>ViewState can be used here, the ID is " + ViewState[ " ID " ].ToString() + " </span> " );
103 }
104 Response.Write( " <br/> " );
105 }
106
107 protected override void OnLoadComplete(EventArgs e)
108 {
109 base .OnLoadComplete(e);
110 Response.Write(GetEventName( " on load complete event, this is the " + step + " th step!<br/> " ));
111 }
112
113 protected override void OnPreRender(EventArgs e)
114 {
115 base .OnPreRender(e);
116 Response.Write(GetEventName( " pre render event, this is the " + step + " th step!<br/> " ));
117 }
118
119 protected override void OnPreRenderComplete(EventArgs e)
120 {
121 base .OnPreRenderComplete(e);
122 Response.Write(GetEventName( " pre render complete event, this is the " + step + " th step!<br/> " ));
123 }
124
125 protected override void OnSaveStateComplete(EventArgs e)
126 {
127
128 base .OnSaveStateComplete(e);
129 Response.Write(GetEventName( " sae state complete event, this is the " + step + " th step!<br/> " ));
130 }
131
132 protected override void Render(HtmlTextWriter writer)
133 {
134 base .Render(writer);
135 Response.Write(GetEventName( " render function, this is the " + step + " th step!<br/> " ));
136 }
137
138 protected override void OnUnload(EventArgs e)
139 {
140 if ( this == null )
141 {
142 string aaa = string .Empty;
143 }
144 if (lblMessage2 == null )
145 {
146 string ga = string .Empty;
147 }
148 base .OnUnload(e);
149 if ( this == null )
150 {
151
152 }
153 }
154
155 protected override void OnLoad(EventArgs e)
156 {
157 base .OnLoad(e);
158 Response.Write(GetEventName( " page load we created, this is the " + step + " th step!<br/> " ));
159 }
160 protected void btnSubmit_Click1( object sender, EventArgs e)
161 {
162 ViewState[ " ID " ] = " 12345 " ;
163 Response.Write(GetEventName( " button control click event, this is the " + step + " th step!<br/> " ));
164 }
六,执行结果:
七, 代码分析
a) 从执行结果我们可以看到,事件的执行顺序为:
i. PreInit
ii. Init
iii. InitComplete
iv. PreLoad
v. Load
vi. Control Event(if they have)
vii. LoadComplete
viii. PreRender
ix. PreRenderComplete
x. SaveStateComplete
xi. Render
xii. Unload
b) 从结果中我们也可以看到,在 PreInit 事件发生后控件还没有被初始化,也就是我们还不能使用。而在 Init 刚刚发生的时候已经可以使用了。也就是说,控件初始化发生在 PreInit 之后 Init 之前。总而言之,我们关心的是我们最早可以在 Init 事件里对页面 server 端控件进行操控。
c) 关于 ViewState ,本示例也清晰的显示出它在 Init Complete 之后还是不能使用,而在 PreLoad 刚刚发生的时候,就已经可以使用了。
d) 通过对比 Default.aspx 页面的配置我们知道,在 PreInit 里面的 Theme 和 MasterPage 的设置是生效了的,我们可以在 PreInit 里设置 Theme 和 MasterPage ,而在其它任何位置设置这两个东西都将抛出异常(看我注视掉的代码)
e) 其它的事件:
Load : 这个事件可能是大家最熟悉的了。需要注意的是, Page 对象会递归的调用子控件的 onload 事件直到页面和所有的子控件被加载完成。这个事件主要用来设置控件属性的值,建立数据库连接(通常不这么做)。
Control events : 这个就不多说了,主要是处理控件的事件,例如 click 。这也就让我们明白了每次我们 click 一个 Button 的时候,实际上是要先去执行 load 事件然后才执行 click 事件的,一般我们用 !IsPostBack 来判断一下从而避免执行不必要的加载逻辑。
LoadComplete : 页面所有的控件都被加载以后执行,暂时没有想到用来干什么。。。
PreRender : 在 HTML 被生成之前这是最后一个事件。每一个页面中的控件都有 PreRender 的过程。在这里对将要输出的 HTML 结果进行最后一次修改。
SaveStateComplete : 在这个时间发生之前,已经保存了所有控件和页面的,任何对 page 或者控件的改动都不会产生左右。暂时没想到用来干啥。
Render : 它不是一个事件而是一个方法。工作就是把 HTML 写回客户端浏览器。
UnLoad : 页面中的每一个控件都会发生这件事。在控件中,使用这个事件来做清理工作,例如关闭数据库连接等。对与页面本身也是做清理工作,例如关闭打开的文件和数据库连接,或者结束日志或者其它指定的工作。
f) 关于 Unload 事件,它实际上做的是销毁前 的工作。在 Unload 事件发生以后, Page 类被卸载和销毁,所以 page 类的字段值也就消失了,而我们通常也是使用在 SaveStateComplete 之前保存的 ViewState 来存储哪些我们想要存储的 page 里的数据。 HttpRuntime 做了销毁 Page 的动作,同样也是它创建的 Page 这个 handler ,现在我们知道原来不是 HttpApplication 雇佣了 P_Handler, HttpApplication 只是使用了它而已,真正雇佣(创建)并解雇(销毁) P_Handler 的是老板 HttpRuntime 。
整个 ASP.NET 生命周期基本结束,如果大家想更深入的了解其中内情,请反编译 System.Web.HttpRuntime 类。如果您不喜欢研究那么深入,我想我这几篇文章应该可以对您有些帮助。
我并不打算就此停手,接下来我还将会围绕 ASP.NET 生命周期和大家进一步深入探知 iis6 中的最重要的改进 —Application Pool 的奥秘和重要的 HttpModule—-SessionStateModule 。在这些都结束以后,我将接着跟大家学习 ASP.NET 在 IIS7 中的变化及相应的知识,欢迎大牛指导和拍砖,谢谢。