我们先看一下执行流程图

图中画红圈的部分便是httpmodule,在说创建httpmodule之前,先说一下httpapplication对象,httpapplication对象由asp.net框架创建,每个请求对应一个httpapplcation实例对象,asp.net框架内部维护了一个httpapplication对象池,可以复用该对象,以便节省服务器资源。httpapplication对象内部有许多事件,其中的一些事件如下:

beginrequest asp.net处理的第一个事件,表示处理的开始

authenticaterequest 验证请求,一般用来取得请求用户的信息

postresolverequestcache 已经完成缓存的获取操作

……

endrequest 本次请求处理完成

其中postresolverequestcache 这个事件就被路由模块监听了。我们看看一个标准httpmodule模块的接口定义是怎么样的。

public interface ihttpmodule
{
     void init(httpapplication context);

    void dispose();

}

注意到init(httpapplication context)这个方法,每注册一个httpmodule模块,asp.net框架内部通过反射获取对应的程序集,并通过反射调用init方法,context参数便是处理一个http请求的httpapplication实例对象。那怎么让我们注册的模块参与到处理http请求中呢?前面提到httpapplication内部有许多事件,而我们的httpmodule中的init方法会被asp.net框架调用,所以我们可以利用这个去注册相应的事件,执行我们的代码,我们在这里可以做很多事,比如自定义验证、自定义授权,图片压缩,图片加水印,服务器日志记录、恶意请求拦截等等。

为了演示自定义httpmodule的使用,我们创建一个寄宿模式为webhost的webapi,为了便于理解asp.net webapi的组成,我们不直接创建一个webapi模板的项目。

1.使用vs2017创建一个空的解决方案

2.新建一个空的.netframwork类库项目和一个空的asp.net web应用

我们对webapi类库项目引用webapi所需的system.web.http.dll。我在i:\program files (x86)\microsoft visual studio\shared\packages\microsoft.aspnet.webapi.core.5.2.7\lib\net45路径下找到了该dll。

然后在webapi项目下建立一个类,使用命名空间system.web.htpp,继承该命名空间下的apicontroller即可,然后定义一个方法,我定义的如下。注意类名一定要以controller结尾,否则在路由匹配成功后,webapi框架内部不能通过路由解析到的值去反射构建对应的控制器实例对象。

然后对webhost项目引用如下dll(我都是在i:\program files (x86)\microsoft visual studio\shared\packages\目录下找到的,实在找不到就创建一个webapi模板然后到项目目录下的package文件中去拷贝)

system.web.http.dll

system.web.http.webhost.dll

system.net.http.formatting.dll

webhost项目还要保持对webapi项目的引用

为webhost项目添加global.asax文件,vs为我们自动创建了一个global类,并继承于system.web命名空间下的httpapplication类。这个类就是处理http请求的类。我们也可以在这里监听相应的事件,监听函数格式以xxx_xxx的形式定义,至于为什么要这么定义的原因是为了便于通过反射进行注册。有些事件只能在这里监听,比如application_start,application_start在整个应用生命周期中只会执行一次,相当于静态构造函数.我们在application_start中去注册全局的路由表,在webhost模式下,webapi的路由系统实际上由asp.net的路由系统完成的,当然webapi本身的路由系统也可以完成。我们注册以下路由表。

protected void application_start(object sender, eventargs e)
{
     globalconfiguration.configuration.routes.maphttproute(name: “first”,routetemplate: “webapi/{controller}/{action}”);

}

然后在vs2017环境下使用iis express启动,然后在浏览器输入,注意在我这里端口号是63210。便可得到如下结果。

我们回顾一下这个流程。

->客户端(在这里是浏览器)发起http请求

->iis express接收到该请求

->iis express发现该请求路径不是已知的静态资源类型,进而把请求交给asp.net托管代码处理

->asp.net从httpapplication池中取一个实例对象去处理该http请求

->注册相应httpmodule模块,并调用init()函数,这个时候httpcontext还没有形成,我们只能在这里注册相应的监听事件函数。

->httpcontext形成,httpapplication实例对象内部的事件轮流触发,其中有一个postmaprequesthandler事件,在这个事件触发后,会调用相应httphandler执行相应的处理,后面再说httphandler

->必要的事件触发完成之后生成http报文并交由iis express返回给客户端

->客户端接受http报文并解析显示在客户端中。

这只是一个大概的过程,具体比这复杂,但我们关注点在httpmodule上,所以足够了。

我们现在尝试自己创建httpmodule

我在webhost项目下建立了一个文件夹,然后新建了一个authorizehttpmodule,模拟授权,功能很简单,看http请求头中是否包含name字段,并且值为hk,否则,向http响应报文中的body部分写入内容,并拦截请求。

namespace webhost.httpmodules
{
     public class authorizehttpmodule : ihttpmodule
     {
         public void dispose()
         {
             return;
         }

        public void init(httpapplication context)
         {
             //此时httpcontext还未构建完成,不能在这里操作httpcontext
             //注册事件监听函数
             context.beginrequest += authorize;
         }
         private void authorize(object sender,eventargs e)
         {
             httpapplication app = sender as httpapplication;

            if (app.request.headers.get(“name”) != “hk”)
             {
                 //不加这一行客户端可能不能自动正确的解析字符编码
                 app.response.headers.add(“content-type”, “text/html;charset=utf-8”);
                 //通过write(string s)写入的字符串在内部默认被转换为utf-8编码。c#string默认编码为utf-16
                 //要先写入原始的字符串编码,调用binarywrite(byte[] bytes)
                 app.response.write(“验证不通过!”);
                 app.completerequest();
             }
         }
     }
}

在webhost项目目录下的web.config中配置httpmodule信息,以便asp.net框架读取。注意,有个全局的web.config文件,其中默认注册了许多httpmodule,其中有一个httpmodule(webdavmodule)会截断put和delete请求。

如上,我添加了system.webserver节点(iis集成模式下是这样配置,经典模式自行搜索),并在子节点modules下添加了自定义的httpmodule,其中name是httpmodule名称,asp.net内部会以这个名称作为key,type表示httpmodule的类型名,以便反射读取相应的类型对象。该类型所在的程序集必须在web应用程序的bin目录,否则不能正常加载,由于我创建的httpmudle是在webhost项目中,所以不会出现问题。我们现在启动iis express看看效果。

可以看到我们的httpmodule起作用了。我们用postman为请求头添加一个字段name,值为hk,然后请求/webapi/home/index试试看

可以看到没有被拦截。前面我们提到了一个httphandler,这个httphandler实际上是最终处理页面的处理者,后面再说。