我們先看一下執行流程圖圖中畫紅圈的部分便是HttpModule,在說創建HttpModule之前,先說一下HttpApplication對象,HttpApplication對象由Asp.net框架創建,每個請求對應一個HttpApplcation實例對象,Asp.Net框架內部維護了一個HttpAp... ...
我們先看一下執行流程圖
圖中畫紅圈的部分便是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啟動,然後在瀏覽器輸入http://localhost:63210/webapi/home/index,註意在我這裡埠號是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();
}
}
}
}