擴展 IHttpModule

  上篇提到請求進入到System.Web後,建立完HttpApplication對象後會執行一堆的管道事件,而後能夠經過HttpModule來對其進行擴展,那麼這篇文章就來介紹下如何定義咱們本身的module來實現對項目的一些擴展。web

  先看一下IHttpModule的接口源碼,只有Dispose()和Init(HttpApplication context)這兩個方法,從方法名稱咱們就能夠知道Dispose方法是用來釋放資源的,而Init方法天然就是用來初始化的,通常僅用於給指望的HttpApplication事件註冊方法。api

using System;
using System.Security.Permissions;
namespace System.Web
{
    /// <summary>
    /// Provides module initialization and disposal events to the implementing class.
    /// </summary>
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public interface IHttpModule
    {
        /// <summary>
        /// Initializes a module and prepares it to handle requests.
        /// </summary>
        /// <param name="context">
        /// An <see cref="T:System.Web.HttpApplication" /> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application 
        /// </param>
        void Init(HttpApplication context);
        /// <summary>
        ///    Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule" />.
        /// </summary>
        void Dispose();
    }
}

  Init方法擁有HttpApplication的參數,那麼咱們先來實現一個本身定義的HttpModule。實現自定義的httpmodule須要實現IHttpModule接口。瀏覽器

namespace Jesen.Web.Core
{
    /// <summary>
    /// 自定義實現HttpModule,實現IHttpModule接口
    /// </summary>
    public class MyHttpModule : IHttpModule
    {
        public void Dispose()
        {
            
        }

        public void Init(HttpApplication context)
        {
            //在此處添加管道事件的處理
            context.BeginRequest += new EventHandler(Context_BeginRequest);
            context.EndRequest += new EventHandler(Context_EndRequest);
        }


        private void Context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication context = sender as HttpApplication;

            context.Response.Write("<h1 style='color:#00f'>來自MyHttpModule的處理開始</h1>");
        }


        private void Context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication context = sender as HttpApplication;

            context.Response.Write("<h1 style='color:#00f'>來自MyHttpModule的處理結束</h1>");
        }
    }
}

 

  實現完IHttpModule接口後咱們還須要對自定義的HttpModule進行註冊,註冊的方式是經過web.config來配置。session

  VS2013及之後/IIS7.0以後的集成模式的配置位置:app

<system.webServer>
      <modules runAllManagedModulesForAllRequests="false">
      <!--runAllManagedModulesForAllRequests處理靜態文件的請求-->
      <remove name="FormsAuthentication" />
      <remove name="WindowsAuthentication" />
      <remove name="PassportAuthentication" />
      <remove name="RoleManager" />
      <remove name="FileAuthorization" />
      <remove name="UrlAuthorization" />
      <add name="MyHttpModule" type="Jesen.Web.Core.MyHttpModule,Jesen.Web.Core"/>
</system.webServer>

  若是是VS2013之前/IIS7.0以前和經典模式的配置位置:框架

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <!--<httpModules>
      <remove name="FormsAuthentication" />
      <add name="MyHttpModule" type="Jesen.Web.Core.MyHttpModule,Jesen.Web.Core" />
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
    </httpModules>
  </system.web>

   接着咱們運行程序,瀏覽器訪問結果以下,瀏覽器輸出咱們上面自定義的HttpModule處理事件的代碼。asp.net

  其實module對全部請求都起做用,在每次接收到請求的時候,都會有一堆的Module被執行,由於.Net框架已經在全局配置文件裏面配好了一些必要的module,該配置文件地址:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config,咱們能夠在本身的項目中的web.config文件中去移除一些不須要的module,例如上面的 <remove name="FormsAuthentication" />移除了Form表單驗證。全局web.config配置文件對httpModule的配置以下:ide

  至此咱們能夠總結一下module的做用,一、能夠用來作權限認證;二、能夠作Url轉發;三、能夠作性能監控;四、能夠用來作反爬蟲,檢測同一個IP頻繁的請求。固然,module不適合單獨爲某些請求而服務,由於它是針對全部請求的。性能

  接下來咱們來看下asp.net中的另外一類事件,咱們把代碼改了一下,在咱們自定義的Module裏面咱們定義了一個事件MyHttpModuleEvent,咱們依然按照上面註冊module的方法在web.config文件中註冊好MyHttpModule。不一樣的是咱們在項目啓動文件Global.asax.cs文件中添加了 以web.config註冊模塊的名稱_Module中的事件的一個方法。運行項目以後,咱們發現該方法MyHttpModule_MyHttpModuleEvent被調用了。網站

  /// <summary>
    /// 自定義實現HttpModule,實現IHttpModule接口
    /// </summary>
    public class MyHttpModule : IHttpModule
    {
        public event EventHandler MyHttpModuleEvent;

        public void Dispose()
        {
            
        }

        public void Init(HttpApplication context)
        {
            //在此處添加管道事件的處理
            context.BeginRequest += new EventHandler(Context_BeginRequest);
            context.EndRequest += new EventHandler(Context_EndRequest);
        }


        private void Context_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;//Url重寫
            if (context.Request.Url.AbsolutePath.Equals("/Pipe/Some", StringComparison.OrdinalIgnoreCase))
                context.RewritePath("/Pipe/Another");

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


        private void Context_EndRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            HttpContext context = application.Context;
        }
    }
 protected void MyHttpModule_MyHttpModuleEvent(object sender, EventArgs e)
 {
      Response.Write("<h3>來自Global.asax 的 MyHttpModule_MyHttpModuleEvent</h2>");
 }

 

那麼爲何這個方法會被調用,這個實際上是.Net框架約定俗成的。約定命名方式:HttpModule註冊名稱_事件名稱,而這個事件的執行時機是由咱們在自定義的module中肯定的。

  最後咱們來看下Global.asax.cs文件中的一些方法。        

         /// <summary>
     /// 網站啓動時,響應第一次請求的執行的 /// 之後再以不執行了 /// 挺合適作一些初始化 /// </summary> protected void Application_Start() { AreaRegistration.RegisterAllAreas();//註冊區域路由 GlobalConfiguration.Configure(WebApiConfig.Register);//註冊webapi路由 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);//註冊全局過濾器 RouteConfig.RegisterRoutes(RouteTable.Routes);//註冊路由 BundleConfig.RegisterBundles(BundleTable.Bundles); }
protected void Application_End(object sender, EventArgs e) { //在應用程序關閉時運行的代碼 logger.Info("Application_End"); } /// <summary> /// 請求出現異常,均可以處理 /// 也能夠完成全局異常處理 /// filter只能處理控制器裏面的異常 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_Error(object sender, EventArgs e) { // 在出現未處理的錯誤時運行的代碼 var error = Server.GetLastError(); logger.Info("Application_Error"); Response.Write("出錯"); Server.ClearError(); } protected void Session_Start(object sender, EventArgs e) { // 在新會話啓動時運行的代碼 logger.Info("Session_Start"); } protected void Session_End(object sender, EventArgs e) { // 在會話結束時運行的代碼。 // 注意: 只有在 Web.config 文件中的 sessionstate 模式設置爲 // InProc(默認內存裏) 時,纔會引起 Session_End 事件。若是會話模式設置爲 StateServer // 或 SQLServer,則不會引起該事件。 logger.Info("Session_End"); }

  從該文件中,咱們能夠看到Session_Start和Session_End兩個方法,它們的調用也是.Net框架約定的,由於咱們經過反射查看SessionStateModule的源碼,能夠看到其定義了Start和End兩個事件。

        public event EventHandler Start
        {
            add
            {
                this._sessionStartEventHandler = (EventHandler)Delegate.Combine(this._sessionStartEventHandler, value);
            }
            remove
            {
                this._sessionStartEventHandler = (EventHandler)Delegate.Remove(this._sessionStartEventHandler, value);
            }
        }
        public event EventHandler End
        {
            add
            {
                SessionOnEndTarget onEndTarget = this._onEndTarget;
                lock (onEndTarget)
                {
                    if (this._store != null && this._onEndTarget.SessionEndEventHandlerCount == 0)
                    {
                        this._supportSessionExpiry = this._store.SetItemExpireCallback(new SessionStateItemExpireCallback(this._onEndTarget.RaiseSessionOnEnd));
                    }
                    SessionOnEndTarget expr_4E = this._onEndTarget;
                    int sessionEndEventHandlerCount = expr_4E.SessionEndEventHandlerCount + 1;
                    expr_4E.SessionEndEventHandlerCount = sessionEndEventHandlerCount;
                }
            }
            remove
            {
                SessionOnEndTarget onEndTarget = this._onEndTarget;
                lock (onEndTarget)
                {
                    SessionOnEndTarget expr_17 = this._onEndTarget;
                    int sessionEndEventHandlerCount = expr_17.SessionEndEventHandlerCount - 1;
                    expr_17.SessionEndEventHandlerCount = sessionEndEventHandlerCount;
                    if (this._store != null && this._onEndTarget.SessionEndEventHandlerCount == 0)
                    {
                        this._store.SetItemExpireCallback(null);
                        this._supportSessionExpiry = false;
                    }
                }
            }
        }

 

  須要注意的是在每處理一個Http請求時,應用程序事件都會觸發一遍,可是Application_Start和 Application_End 例外,它僅在第一個資源文件被訪問時被觸發。Http Module沒法註冊和響應Session事件,對於Session_Start 和 Session_End,只能經過Glabal.asax來處理。

相關文章
相關標籤/搜索