咱們先看一下執行流程圖
圖中畫紅圈的部分即是HttpModule,在說建立HttpModule以前,先說一下HttpApplication對象,HttpApplication對象由Asp.net框架建立,每一個請求對應一個HttpApplcation實例對象,Asp.Net框架內部維護了一個HttpApplication對象池,能夠複用該對象,以便節省服務器資源。HttpApplication對象內部有許多事件,其中的一些事件以下:
BeginRequest Asp.net處理的第一個事件,表示處理的開始html
AuthenticateRequest 驗證請求,通常用來取得請求用戶的信息web
PostResolveRequestCache 已經完成緩存的獲取操做api
……瀏覽器
EndRequest 本次請求處理完成緩存
其中PostResolveRequestCache 這個事件就被路由模塊監聽了。咱們看看一個標準HttpModule模塊的接口定義是怎麼樣的。
public interface IHttpModule
{
void Init(HttpApplication context);服務器
void Dispose();
app
}
注意到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}");
框架
}
咱們回顧一下這個流程。
->客戶端(在這裏是瀏覽器)發起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其實是最終處理頁面的處理者,後面再說。