上篇提到請求進入到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來處理。