asp.net管道模型学习(二):HttpModule

上一篇文章《asp.net管道模型学习(一):Http请求处理流程》学习了Http请求处理流程,这篇文章介绍HttpModule。

 

HttpModule是什么

 

在Http请求处理过程中,请求会前后两次通过一系列的HttpModule,这些Module对Http请求具有完全控制权,可以分别实现某个工作前的事情和某个工作后的事情。那么HttpModule是什么?HttpModule是实现了IHttpModule接口的程序集。在asp.net中事件可以分成三个级别:应用程序级事件、页面级事件、控件级事件,事件的触发分别与应用程序周期、页面周期、控件周期密切相关,而HttpModule是与应用程序事件密切相关的。通过HttpModule在Http请求管道中注册了期望对应用程序事件做出反应的方法,在相应事件触发的时候,便会调用这个HttpModule注册的方法,实际工作在这些方法中执行。

 

asp.net本身自带了许多HttpModule,在操作系统文件夹C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config中找到web.config:

ceb2cf90-d9e4-4baf-8c73-62188782ca4b.png

打开该配置文件,找到HttpModules节点就是系统配置好的HttpModule:

<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="RoleManager" type="System.Web.Security.RoleManagerModule" />
            <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
            <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
            <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" />
            <add name="Profile" type="System.Web.Profile.ProfileModule" />
            <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
            <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
            <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
 </httpModules>

name是HttpModule名称,type表示程序集。

 

 

实现自己的HttpModule

 

IHttpModule接口包含两个方法:

//
// 摘要:
//     向实现类提供模块初始化和处置事件。
public interface IHttpModule
{
    //
    // 摘要:
    //     处置由实现 System.Web.IHttpModule 的模块使用的资源(内存除外)。
    void Dispose();
    //
    // 摘要:
    //     初始化模块,并使其为处理请求做好准备。
    //
    // 参数:
    //   context:
    //     一个 System.Web.HttpApplication,它提供对 ASP.NET 应用程序内所有应用程序对象的公用的方法、属性和事件的访问
    void Init(HttpApplication context);
}

Init:是入口方法,该方法的参数是HttpApplication对象,在Http处理流程介绍的文章中该对象,HttpRuntime将请求转交给HttpApplication(表示Web应用程序),HttpApplication创建针对Http请求的HttpContext对象,这些对象包含如HttpRequest、HttpResponse等对象,这些对象在程序中可以通过上下文类进行访问。我们要在这个方法内注册HttpApplication对象暴露给客户端的事件,事件处理程序交给另外的方法。

过程可以这样理解:当网站第一个资源被请求时,asp.net会创建HttpApplication实例,它代表站点应用程序,同时会创建所有在Web.Config中注册过的Module(创建Module实例时调用Init方法),

在Init方法内,对要做出响应的HttpApplication暴露出的事件进行注册。HttpApplication在其应用程序周期中触发各类事件,触发事件的时候调用Module在其Init方法中注册过的方法。

Dispose:可以在垃圾回收之前进行一些清理工作。

在Http请求

下面演示自己实现一个HttpModule,然后注册运行。

新建一个类库,名字就叫Web.Core,在该类库中新建一个类GlobalModule,继承IHttpModule,实现接口的方法:

public class GlobalModule : IHttpModule
{
    public event EventHandler GlobalModuleEvent;
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);

        context.EndRequest += new EventHandler(context_EndRequest);
    }

    public void context_BeginRequest(object sender,EventArgs e)
    {
        HttpApplication appliction = (HttpApplication)sender;
        HttpContext context= appliction.Context;
        string extension=Path.GetExtension(context.Request.Url.AbsoluteUri);
        if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
        {
            context.Response.Write($"来自GlobalModule中context_BeginRequest  {DateTime.Now.ToString()} ");
        }

        if(GlobalModuleEvent!=null)
        {
            GlobalModuleEvent.Invoke(this,e);
        }

    }

    public void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication appliction = (HttpApplication)sender;
        HttpContext context = appliction.Context;

        string extension = Path.GetExtension(context.Request.Url.AbsoluteUri);
        if (string.IsNullOrWhiteSpace(extension) && !context.Request.Url.AbsolutePath.Contains("Verify"))
        {
            context.Response.Write($"来自GlobalModule中context_EndRequest {DateTime.Now.ToString()} ");
        }
    }

    public void Dispose()
    {
    }
}

我们通过这个HttpApplication对象分别给管道中BeginRequest事件和EndRequest事件注册了方法,在两个方法中都像响应流中输出字符串。

HttpModule写好了,我们需要注册它,只需要将Web.Core的dll文件拷贝到的网站项目中的bin目录下即可,本次直接在网站项目中引用该dll,在项目中的配置文件web.config中的system.webServer节点下modules中添加一项:

 <add name="GlobalModule" type="Web.Core.Pipeline.GlobalModule,Web.Core" />

name表示HttpModule名称,可以和具体的类名不一样,可以通过程序获取HttpModuleCollection集合,再通过这个name获取到具体的HttpModule对象,下面会讲到。type由逗号分隔的两部分组成,第一部分表示命名空间+类名,第二部分表示程序集。

这里要特别说明下,system.web节点针对的是VS2013之前/IIS7.0之前和经典模式,而system.webServer节点针对的是VS2013及以后/IIS7.0之后的集成模式。

在该项目中访问任意一个页面都会输出字符串:

31a6e7cc-2f69-4a17-873f-f44c5df8e1ab.png

可以理解HttpModule就是对HttpApplication的扩展。

  

展示所有的HttpModule

 

我们可以通过获取当前上下文的HttpApplication的实例的Modules属性来获取到HttpModuleCollection集合,现在我们全部在页面中展示出来:

/// <summary>
/// 展示请求的Module
/// </summary>
/// <returns></returns>
public ActionResult ShowModules()
{
    List<SysModuleInfo> moduleList = new List<SysModuleInfo>();
    HttpApplication application = HttpContext.ApplicationInstance;//获取当前上下文的HttpApplication实例
    int i = 1;
    foreach (var moduleName in application.Modules.AllKeys)
    {
        moduleList.Add(new SysModuleInfo
        {
            Id = i++,
            Name = moduleName,
            TypeName = application.Modules[moduleName].ToString()
        });
    }
    return View(moduleList); 
}

在视图页中添加:

@model IEnumerable<SysModuleInfo>
@{
    ViewBag.Title = "ShowModules";
}

<h2>ShowModules</h2>

<table class="table">
    <tr>
        <th>序号</th>
        <th>名称</th>
        <th>详细类型名称</th>
    </tr>
    @foreach (SysModuleInfo item in Model)
    {
        <tr>
            <td>
                @item.Id
            </td>
            <td>
                @item.Name
            </td>
            <td>
                @item.TypeName
            </td>
        </tr>
    }
</table>

输出:

151cf3c2-6508-46c2-b911-be19254c8146.png               

默认是18个Module,上面的GlobalModule是我们自己实现的Module。

1、OutputCacheModule完成Asp.net的输出缓存管理工作:

OutputCacheModule的配置参数通过system.web配置元素的caching子元素的outputCache元素进行定义。当启用输出缓存之后(启用还是通过配置文件,下同),

OutputCacheModule将注册HttpApplication的ResolveRequestCache和UpdateRequestCache两个事件完成输出缓存的管理。

 

2、SessionStateModule完成Session的管理工作:

这个Module的配置参数通过配置文件中的system.web配置元素的sessionState子元素进行配置。当启用Session状态管理之后,

SessionStateModule将注册HttpApplication的AcquireRequestState、ReleaseRequestState、EndRequest三个事件完成Session状态的管理工作。

 

3、ProfileModule提供个性化数据管理:

这是一个自定义的类似于Session的会话状态管理,但是个性化数据的读取和保存可以由程序员完全控制,并且提供了强类型的数据访问方式。

这个Module的配置参数在system.web的子元素profile中进行说明。当启用了个性化数据管理之后,Module将注册HttpApplication的AcquireRequestState和EndRequest事件处理。

 

4、AnonymousIdentificationModule提供匿名用户的标志:

是否启用匿名用户标志在配置文件的system.web配置元素的子元素anonymousIdentification中定义,还可以配置匿名标识的管理方式。

由于在AuthenticateRequest事件中将验证用户,获取用户名,所以这个Module注册了PostAuthenticateRequest的事件处理,当用户没有经过验证的时候,为用户分配一个唯一的匿名标识。

 

5、WindowsAuthenticationModule、FormsAuthenticationModule和PassportAuthenticationModule用来完成用户的验证工作。

它们通过配置文件中system.web的子元素authentication子元素定义,mode属性用来指定网站当前使用的验证方式,也就是哪一个Module将被用来完成验证工作。在启用验证的情况下,FormsAuthenticationModule和PassportAuthenticationModule将注册HttpApplication的AuthenticateRequest和EndRequest事件进行用户的验证处理。WindowsAuthenticationModule将注册AuthenticateRequest的事件处理。

 

6、RoleManagerModule、UrlAuthorizationModule、FileAuthorizationModule用来完成用户的授权管理:

授权管理的配置参数来自system.web的authorization子元素。UrlAuthorizationModule和FileAuthorizationModule注册了HttpApplication的AuthorizeRequest事件处理,

用来检查Url和文件的访问授权。RoleManagerModule在Url和文件访问授权检查通过之后,通过用户的标识和角色来完成用户的授权检查,

RoleManagerModule注册了HttpApplication的PostAuthenticateRequest和EndRequest事件处理。

17~19是DynamicModule,是MVC框架添加的。

 

移除指定的HttpModule

 

除了添加自己实现的HttpModule,我们还可以移除指定的HttpModule,只需要在web.config配置文件设设置,比如移除FormsAuthentication:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="false">
    ...
    <remove name="FormsAuthentication"/>
    ...
  </modules>
  ...
</system.webServer>

再次展示所有的HttpModule,发现FormsAuthentication没有执行:

8c09d6a4-1ce2-4498-85ad-4f7542e7c1b7.png

管道设计模型是一种很牛设计思想,使用灵活,我们可以对HttpModule随意控制,对某些请求移除不必要使用的HttpModule,添加自己的实现的HttpModule实现一些特定功能,甚至替换掉系统提供的某些HttpModule,自己再进行扩展。

 

HttpApplication事件的执行顺序

 

在Http请求流程中展示出了HttpApplication的所有事件,本次通过一个自定义的HttpModule来展示这些事件的执行顺序:

public class MyCustomModule : IHttpModule
{
    /// <summary>
    /// 您将需要在网站的 Web.config 文件中配置此模块
    /// 并向 IIS 注册它,然后才能使用它。有关详细信息,
    /// 请参阅以下链接: https://go.microsoft.com/?linkid=8101007
    /// </summary>
    #region IHttpModule Members

    public void Dispose()
    {
        //此处放置清除代码。
    }

    public void Init(HttpApplication application)
    {
        application.AcquireRequestState += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "AcquireRequestState  "));
        application.AuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "AuthenticateRequest  "));
        application.AuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "AuthorizeRequest     "));
        application.BeginRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "BeginRequest   "));
        application.Disposed += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "Disposed "));
        application.EndRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "EndRequest     "));
        application.Error += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "Error    "));
        application.LogRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "LogRequest     "));
        application.MapRequestHandler += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "MapRequestHandler    "));
        application.PostAcquireRequestState += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostAcquireRequestState    "));
        application.PostAuthenticateRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest    "));
        application.PostAuthorizeRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest "));
        application.PostLogRequest += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostLogRequest "));
        application.PostMapRequestHandler += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostMapRequestHandler"));
        application.PostReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostReleaseRequestState    "));
        application.PostRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute  "));
        application.PostResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostResolveRequestCache    "));
        application.PostUpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache     "));
        application.PreRequestHandlerExecute += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute   "));
        application.PreSendRequestContent += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PreSendRequestContent"));
        application.PreSendRequestHeaders += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders"));
        application.ReleaseRequestState += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "ReleaseRequestState  "));
        application.RequestCompleted += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "RequestCompleted     "));
        application.ResolveRequestCache += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "ResolveRequestCache  "));
        application.UpdateRequestCache += (s, e) => application.Response.Write(string.Format("<h4 style='color:#00f'>来自MyCustomModule 的处理,{0}请求到达 {1}</h4><hr>", DateTime.Now.ToString(), "UpdateRequestCache   "));

    }

    #endregion

    public void OnLogRequest(Object source, EventArgs e)
    {
        //可以在此处放置自定义日志记录逻辑
    }
}

在web.config中注册下,然后随便运行一个网友,效果如下:

4fbbd915-e606-4c33-a19a-e599803bb1e8.png

 

Global中的事件

 

Application_Start方法在网站启动第一时间执行的,而且只执行一次,一般在该方法中初始化一些信息。
Application_End方法在网站应用程序关闭时执行。
Session_Start和Session_End属于特殊的事件,Session_Start在新的会话启动时运行,Session_End在会话结束时运行,特别要注意的时,只有在web.config配置文件中sessionstate模式设置为Inproc(默认内存)时,才会引发Session_End事件,若会话模式设置为StateServer或SqlServer则不会引发该事件。
用这个两个事件统计在线人数并不是很靠谱,用js轮询的方式才比较准确。
在Global中可以注册HttpModule中的事件,方法名称格式为:HttpModule名称_事件名称,如我们自己实现的GlobalModule可以这样写:
protected void GlobalModule_GlobalModuleEvent(object sender, EventArgs e)
{
    Response.Write("来自Global中GlobalModule_GlobalModuleEvent的内容");
}

打开一个页面,效果如下:

649d335a-6b6a-44e8-b4b7-68f4bdd6aa8a.png

 

 
posted @ 2019-09-18 20:48  James.Yu  阅读(168)  评论(0)    收藏  举报