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我们可以看到:
Code
1
public
class
PageHandlerFactory : IHttpHandlerFactory2, IHttpHandlerFactory
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方法中:
代码
1
private
IHttpHandler GetHandlerHelper(HttpContext context,
string
requestType, VirtualPath virtualPath,
string
physicalPath)
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文件,代码如下:
代码
1
public
class
GodSpeedHttpHandler:IHttpHandler
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页面代码如下:
代码
1
private
int
step
=
1
;
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中的变化及相应的知识,欢迎大牛指导和拍砖,谢谢。
相关示例代码:
自定义HttpHandler和PageLifeCycle示例代码.zip
相关链接:
ASP.NET应用程序生命周期趣谈(一)
ASP.NET应用程序生命周期趣谈(二)
ASP.NET应用程序生命周期趣谈(三)