介绍
在Internet时代的开端,客户端的需求非常有限;.htm文件就可以满足他们的需求。但是,随着时间的流逝,客户端需求的扩充超越了.htm文件或静态文件所包含的功能。
开发者需要扩充或扩展Web服务器的功能。Web服务器厂商设计了不同的解决方案,但是都遵循同一个主题“向Web服务器插入某些组件”。所有的Web服务器补充技术都允许开发者建立并插入组件以增强Web服务器的功能。微软公司提出了ISAPI(Internet服务器API),网景公司提出了NSAPI(网景服务器API)等等。
ISAPI是一种重要的技术,它允许我们增强与ISAPI兼容的Web服务器(IIS就是一种与ISAPI兼容的Web服务器)的能力。我们使用下面的组件达到这个目的:
· ISAPI扩展
· ISAPI过滤器
ISAPI扩展是使用Win32动态链接库来实现的。你可以把ISAPI扩展看作是一个普通的应用程序。ISAPI扩展的处理目标是http请求。这意味着你必须调用它们才能激活它们。 你可以认为ISAPI过滤器仅仅就是一个过滤器而已。客户端每次向服务器发出请求的时候,请求要经过过滤器。客户端不需要在请求中指定过滤器,只需要简单地把请求发送给Web服务器,接着Web服务器把请求传递给相关的过滤器。接下来过滤器可能修改请求,执行某些登录操作等等。
由于这些组件的复杂性,实现它们非常困难。开发者不得不使用C/C++来开发这些组件,但是对于很多人来说,使用C/C++进行开发简直就是痛苦的代名词。
那么ASP.NET提供什么东西来实现这些功能呢?ASP.NET提供的是HttpHandler(HTTP处理程序)和HttpModule(HTTP模块)。
在深入了解这些组件的详细信息之前,了解一下http请求经过HTTP模块和HTTP处理程序的时候的处理流程是有价值的。
建立示例应用程序
我建立了下面一些的C#项目以演示应用程序的不同组件:
· NewHandler (HTTP处理程序)
· Webapp (演示HTTP处理程序)
· SecurityModules (HTTP模块)
· Webapp2 (演示HTTP模块)
这些应用程序的安装步骤:
· 解开attached zip文件中的所以代码。
· 建立两个虚拟目录webapp和webapp2;把这两个目录指向Webapp和Webapp2应用程序的实际物理目录。
· 把NewHandler项目中的Newhandler.dll文件复制到webapp应用程序的bin目录。
· 把SecurityModules项目中的SecurityModules.dll文件复制到webapp2应用程序的bin目录中。
ASP.NET请求的处理过程
ASP.NET请求处理过程是基于管道模型的,在模型中ASP.NET把http请求传递给管道中的所有模块。每个模块都接收http请求并有完全控制权限。模块可以用任何自认为适合的方式来处理请求。一旦请求经过了所有HTTP模块,就最终被HTTP处理程序处理。HTTP处理程序对请求进行一些处理,并且结果将再次经过管道中的HTTP模块:

| 方法名称 | 描述 |
| ProcessRequest | 这个方法实际上是http处理程序的核心。我们调用这个方法来处理http请求。 |
| IsReusable | 我们调用这个属性来决定http处理程序的实例是否可以用于处理相同其它类型的请求。HTTP处理程序可以返回true或false来表明它们是否可以重复使用。 |
| 方法名称 | 描述 |
| GetHandler | 这个方法负责建立适当的处理程序并把它的指针返回到调用代码(ASP.NET运行时)。这个方法返回的处理程序对象应该实现了IHttpHandler接口。 |
| ReleaseHandler | 这个方法负责在请求处理完成后释放http处理程序。Factory 实现决定了它的操作。Factory 实现可以是实际摧毁实例,也可以把它放入缓冲池供以后使用。 |
<httpHandlers>
<add verb="supported http verbs" path="path" type="namespace.classname, assemblyname" />
<httpHandlers>
ASP.NET运行时对HTTP处理程序的使用方式
无论你是否相信,ASP.NET都使用HTTP请求实现了大量的自己的功能。ASP.NET使用处理程序来处理.aspx、 .asmx、 .soap和其它ASP.NET文件。
下面是machine.config文件中的一个片段:
<httpHandlers>
<add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>
<add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>
<add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/>
. . . . . .
. . . . . .
</httpHandlers>
using System;
using System.Web;
namespace MyHandler
{
public class NewHandler : IHttpHandler
{
public NewHandler()
{
// TODO: 此处添加构造逻辑
}
#region Implementation of IHttpHandler
public void ProcessRequest(System.Web.HttpContext context)
{
HttpResponse objResponse = context.Response ;
objResponse.Write("<html><body><h1>Hello 15Seconds Reader ") ;
objResponse.Write("</body></html>") ;
}
public bool IsReusable
{
get
{
return true;
}
}
#endregion
}
}
<httpHandlers>
<add verb="*" path="*.15seconds" type="MyHandler.NewHandler,MyHandler"/>
</httpHandlers>


public class NewHandler : IHttpHandler, IRequiresSessionState
| 方法名称 | 描述 |
| Init | 这个方法允许HTTP模块向HttpApplication 对象中的事件注册自己的事件处理程序。 |
| Dispose | 这个方法给予HTTP模块在对象被垃圾收集之前执行清理的机会。 |
| 事件名称 | 描述 |
| AcquireRequestState | 当ASP.NET运行时准备好接收当前HTTP请求的对话状态的时候引发这个事件。 |
| AuthenticateRequest | 当ASP.NET 运行时准备验证用户身份的时候引发这个事件。 |
| AuthorizeRequest | 当ASP.NET运行时准备授权用户访问资源的时候引发这个事件。 |
| BeginRequest | 当ASP.NET运行时接收到新的HTTP请求的时候引发这个事件。 |
| Disposed | 当ASP.NET完成HTTP请求的处理过程时引发这个事件。 |
| EndRequest | 把响应内容发送到客户端之前引发这个事件。 |
| Error | 在处理HTTP请求的过程中出现未处理异常的时候引发这个事件。 |
| PostRequestHandlerExecute | 在HTTP处理程序结束执行的时候引发这个事件。 |
| PreRequestHandlerExecute | 在ASP.NET开始执行HTTP请求的处理程序之前引发这个事件。在这个事件之后,ASP.NET 把该请求转发给适当的HTTP处理程序。 |
| PreSendRequestContent | 在ASP.NET把响应内容发送到客户端之前引发这个事件。这个事件允许我们在内容到达客户端之前改变响应内容。我们可以使用这个事件给页面输出添加用于所有页面的内容。例如通用菜单、头信息或脚信息。 |
| PreSendRequestHeaders | 在ASP.NET把HTTP响应头信息发送给客户端之前引发这个事件。在头信息到达客户端之前,这个事件允许我们改变它的内容。我们可以使用这个事件在头信息中添加cookie和自定义数据。 |
| ReleaseRequestState | 当ASP.NET结束所搜有的请求处理程序执行的时候引发这个事件。 |
| ResolveRequestCache | 我们引发这个事件来决定是否可以使用从输出缓冲返回的内容来结束请求。这依赖于Web应用程序的输出缓冲时怎样设置的。 |
| UpdateRequestCache | 当ASP.NET完成了当前的HTTP请求的处理,并且输出内容已经准备好添加给输出缓冲的时候,引发这个事件。这依赖于Web应用程序的输出缓冲是如何设置的。 |
在配置文件中注册HTTP模块
当我们建立了HTTP模块并把它复制到Web应用程序的bin目录或者全局部件缓冲(Global Assembly Cache)之后,接下来就应该在web.config或machine.config中注册它了。
我们可以使用<httpModules>和<add>节点把HTTP模块添加到Web应用程序中。实际上模块都使用<add>节点列举在<httpModules>和</httpModules>节点之内了。
因为配置设置信息是可以继承的,所以子目录从父目录那儿继承配置设置信息。其结果是,子目录可能继承了一些不需要的HTTP模块(它们是父配置信息的一部分);因此,我们需要一种删除这些不需要的模块的方法。我们可以使用<remove>节点;如果我们希望删除从应用程序继承得到的所有HTTP模块,可以使用<clear>节点。
下面的代码是添加HTTP模块的一个通用示例:
<httpModules>
<add type="classname, assemblyname" name="modulename" />
<httpModules>
<httpModules>
<remove name="modulename" />
<httpModules>
<httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
<add name="Session" type="System.Web.SessionState.SessionStateModule"/>
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule"/>
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule"/>
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule"/>
<add name="UrlAuthorization"
type="System.Web.Security.UrlAuthorizationModule"/>
<add name="FileAuthorization"
type="System.Web.Security.FileAuthorizationModule"/>
</httpModules>
实现一个提供安全服务的HTTP模块
现在我们实现一个HTTP模块,它为我们的Web应用程序提供安全服务。该HTTP模块基本上是提供一种定制的身份认证服务。它将接收HTTP请求中的身份凭证,并确定该凭证是否有效。如果有效,与用户相关的角色是什么?通过User.Identity对象,它把这些角色与访问我们的Web应用程序页面的用户的标识关联起来。
下面是该HTTP模块的代码:
using System;
using System.Web;
using System.Security.Principal;
namespace SecurityModules
{
/// Class1的总体描述。
public class CustomAuthenticationModule : IHttpModule
{
public CustomAuthenticationModule()
{
}
public void Init(HttpApplication r_objApplication)
{
// 向Application 对象注册事件处理程序。
r_objApplication.AuthenticateRequest +=
new EventHandler(this.AuthenticateRequest) ;
}
public void Dispose()
{
// 此处空出,因为我们不需要做什么操作。
}
private void AuthenticateRequest(object r_objSender,EventArgs r_objEventArgs)
{
// 鉴别用户的凭证,并找出用户角色。。
1. HttpApplication objApp = (HttpApplication) r_objSender ;
2. HttpContext objContext = (HttpContext) objApp.Context ;
3. if ( (objApp.Request["userid"] == null) ||
4. (objApp.Request["password"] == null) )
5. {
6. objContext.Response.Write("<H1>Credentials not provided</H1>") ;
7. objContext.Response.End() ;
8. }
9. string userid = "" ;
10. userid = objApp.Request["userid"].ToString() ;
11. string password = "" ;
12. password = objApp.Request["password"].ToString() ;
13. string[] strRoles ;
14. strRoles = AuthenticateAndGetRoles(userid, password) ;
15. if ((strRoles == null) || (strRoles.GetLength(0) == 0))
16. {
17. objContext.Response.Write("<H1>We are sorry but we could not
find this user id and password in our database</H1>") ;
18. objApp.CompleteRequest() ;
19. }
20. GenericIdentity objIdentity = new GenericIdentity(userid,
"CustomAuthentication") ;
21. objContext.User = new GenericPrincipal(objIdentity, strRoles) ;
}
private string[] AuthenticateAndGetRoles(string r_strUserID,string r_strPassword)
{
string[] strRoles = null ;
if ((r_strUserID.Equals("Steve")) && (r_strPassword.Equals("15seconds")))
{
strRoles = new String[1] ;
strRoles[0] = "Administrator" ;
}
else if ((r_strUserID.Equals("Mansoor")) && (r_strPassword.Equals("mas")))
{
strRoles = new string[1] ;
strRoles[0] = "User" ;
}
return strRoles ;
}
}
}




<authentication mode="None"/>
<authorization>
<deny users="?"/>
</authorization>
<location path="index.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>