在之前的ASP.NET是如何在IIS下工作的這篇文章中介紹了ASP.NET與IIS配合工作的機制,在http請求經過一系列處理後,最後到達ASP.NET管道中,這時,就是Http Modules和HttpHandler出場的時候了。
再來擺出管道工作時序圖來一看:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcucDO4ITM4IDOzUzM3EzMx8CX1ADNxAjMvwFN2MzM3IzLcd2bsJ2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
HttpModule
HttpModule是類似于過濾器的作用,可以沒有,也可以有任意個,每一個都可以訂閱管道事件中的任意個事件,在每個訂閱的事件中可自定義功能實作。
HttpModule是實作IHttpModule接口的類。接口如下:
public interface IHttpModule
{
// 摘要:
// 處置由實作 System.Web.IHttpModule 的子產品使用的資源(記憶體除外)。
void Dispose();
//
// 摘要:
// 初始化子產品,并使其為處理請求做好準備。
//
// 參數:
// context:
// 一個 System.Web.HttpApplication,它提供對 ASP.NET 應用程式内所有應用程式對象的公用的方法、屬性和事件的通路
void Init(HttpApplication context);
}
下面實作一個HttpModule,并訂閱管道中的一系列事件,訂閱事件就是在Init方法中綁定EventHandler的過程:
代碼有點長,因為我把每一個事件都訂閱了,這樣一來可以清楚的看出哪些事件執行了,這些事件執行的先後順序是什麼。代碼如下:
public class MyModule : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
//此處放置清除代碼。
}
public void Init(HttpApplication context)
{
// 下面是如何處理 LogRequest 事件并為其
// 提供自定義日志記錄實作的示例
context.LogRequest += new EventHandler(OnLogRequest);
context.BeginRequest += new EventHandler(context_BeginRequest);
context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
context.Disposed += new EventHandler(context_Disposed);
context.Error += new EventHandler(context_Error);
context.EndRequest += new EventHandler(context_EndRequest);
context.MapRequestHandler += new EventHandler(context_MapRequestHandler);
context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
context.PostLogRequest += new EventHandler(context_PostLogRequest);
context.PostReleaseRequestState += new EventHandler(context_PostReleaseRequestState);
context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
context.PostUpdateRequestCache += new EventHandler(context_PostUpdateRequestCache);
context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
context.RequestCompleted += new EventHandler(context_RequestCompleted);
context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache);
context.UpdateRequestCache += new EventHandler(context_UpdateRequestCache);
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
context.PreSendRequestContent += new EventHandler(context_PreSendRequestContent);
context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);
}
void context_Error(object sender, EventArgs e)
{
WriteLog("Error");
//HttpContext.Current.Response.Write("Error<br />");
}
void context_UpdateRequestCache(object sender, EventArgs e)
{
WriteLog("UpdateRequestCache");
//HttpContext.Current.Response.Write("UpdateRequestCache<br />");
}
void context_ResolveRequestCache(object sender, EventArgs e)
{
WriteLog("ResolveRequestCache");
// HttpContext.Current.Response.Write("ResolveRequestCache<br />");
}
void context_RequestCompleted(object sender, EventArgs e)
{
WriteLog("RequestCompleted");
// HttpContext.Current.Response.Write("RequestCompleted<br />");
}
void context_ReleaseRequestState(object sender, EventArgs e)
{
WriteLog("ReleaseRequestState");
//HttpContext.Current.Response.Write("ReleaseRequestState<br />");
}
void context_PostUpdateRequestCache(object sender, EventArgs e)
{
WriteLog("PostUpdateRequestCache");
//HttpContext.Current.Response.Write("PostUpdateRequestCache<br />");
}
void context_PostResolveRequestCache(object sender, EventArgs e)
{
WriteLog("PostResolveRequestCache");
//HttpContext.Current.Response.Write("PostResolveRequestCache<br />");
}
void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
WriteLog("PostRequestHandlerExecute");
//HttpContext.Current.Response.Write("PostRequestHandlerExecute<br />");
}
void context_PostReleaseRequestState(object sender, EventArgs e)
{
WriteLog("PostReleaseRequestState");
//HttpContext.Current.Response.Write("PostReleaseRequestState<br />");
}
void context_PostLogRequest(object sender, EventArgs e)
{
WriteLog("PostLogRequest");
//HttpContext.Current.Response.Write("PostLogRequest<br />");
}
void context_PostAuthorizeRequest(object sender, EventArgs e)
{
WriteLog("PostAuthorizeRequest");
//HttpContext.Current.Response.Write("PostAuthorizeRequest<br />");
}
void context_PostAuthenticateRequest(object sender, EventArgs e)
{
WriteLog("PostAuthenticateRequest");
//HttpContext.Current.Response.Write("PostAuthenticateRequest<br />");
}
void context_PostAcquireRequestState(object sender, EventArgs e)
{
WriteLog("PostAcquireRequestState");
//HttpContext.Current.Response.Write("PostAcquireRequestState<br />");
}
void context_MapRequestHandler(object sender, EventArgs e)
{
WriteLog("MapRequestHandler");
//HttpContext.Current.Response.Write("MapRequestHandler<br />");
}
void context_Disposed(object sender, EventArgs e)
{
WriteLog("Disposed");
//HttpContext.Current.Response.Write("Disposed<br />");
}
void context_AuthorizeRequest(object sender, EventArgs e)
{
WriteLog("AuthorizeRequest");
//HttpContext.Current.Response.Write("AuthorizeRequest<br />");
}
void context_AcquireRequestState(object sender, EventArgs e)
{
WriteLog("AcquireRequestState");
//HttpContext.Current.Response.Write("AcquireRequestState<br />");
}
void context_PreSendRequestHeaders(object sender, EventArgs e)
{
WriteLog("PreSendRequestHeaders");
//HttpContext.Current.Response.Write("PreSendRequestHeaders<br />");
}
void context_PreSendRequestContent(object sender, EventArgs e)
{
WriteLog("PreSendRequestContent");
//HttpContext.Current.Response.Write("PreSendRequestContent<br />");
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
WriteLog("PreRequestHandlerExecute");
//HttpContext.Current.Response.Write("PreRequestHandlerExecute<br />");
}
void context_EndRequest(object sender, EventArgs e)
{
WriteLog("EndRequest");
//HttpContext.Current.Response.Write("EndRequest<br />");
}
void context_BeginRequest(object sender, EventArgs e)
{
WriteLog("*******************************************************************************");
HttpApplication app = sender as HttpApplication;
WriteLog(app.Context.Request.Path);
WriteLog("BeginRequest");
//HttpContext.Current.Response.Write("BeginRequest<br />");
}
void context_AuthenticateRequest(object sender, EventArgs e)
{
WriteLog("AuthenticateRequest");
//HttpContext.Current.Response.Write("AuthenticateRequest<br />");
}
#endregion
public void OnLogRequest(Object source, EventArgs e)
{
//可以在此處放置自定義日志記錄邏輯
WriteLog("OnLogRequest");
//HttpContext.Current.Response.Write("OnLogRequest<br />");
}
public void context_PostMapRequestHandler(object sender, EventArgs e)
{
WriteLog("PostMapRequestHandler");
//HttpContext.Current.Response.Write("PostMapRequestHandler<br />");
}
public void WriteLog(string message)
{
string path = @"d:\aspnet\httpmodule.txt";
StreamWriter writer = null;
if (!File.Exists(path))
{
writer = File.CreateText(path);
}
else
{
FileInfo info = new FileInfo(path);
writer = info.AppendText();
}
writer.WriteLine(message);
writer.Flush();
writer.Close();
}
}
訂閱的事件實作中,将事件名稱儲存到我本地D盤的一個文本檔案中。
代碼實作完畢了,下一步就是要代碼起作用了,很簡單,隻需要在web.config中簡單配置就可以了。配置中注意IIS7內建模式和IIS7經典模式(包括IIS6)的差別,配置如下:
<!--IIS6或者IIS7經典模式-->
<system.web>
<httpModules>
<add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
</httpModules>
</system.web>
<!--IIS7內建模式-->
<system.webServer>
<modules>
<add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
</modules>
</system.webServer>
如此一來,一個HttpModule及其配置工作就完成了,接下來,釋出網站到IIS或者直接在VS中運作,随便通路項目中的一個檔案(任何檔案類型都可以),我的項目中有一個WebForm2.aspx的頁面,我在浏覽器中通路這個頁面,發現頁面是空白的,因為頁面中我什麼都沒寫,上面的Module實作中,我把輸出全部放到本地D盤的一個文本檔案中了,ok,打開那個文本檔案。如圖:
我們看到輸出内容,第2行是通路的頁面位址,下面依次為訂閱的事件輸出,我們清楚的看到了事件的執行順序。
BeginRequest #發出信号表示建立任何給定的新請求。 此事件始終被引發,并且始終是請求處理期間發生的第一個事件
AuthenticateRequest #發出信号表示配置的身份驗證機制已對目前請求進行了身份驗證。 訂閱 AuthenticateRequest 事件可確定在處理附加子產品或事件處理程式之前對請求進行身份驗證
PostAuthenticateRequest #預訂 PostAuthenticateRequest 事件的功能可以通路由 PostAuthenticateRequest 處理的任何資料
AuthorizeRequest #發出信号表示 ASP.NET 已對目前請求進行了授權。 訂閱 AuthorizeRequest 事件可確定在處理附加的子產品或事件處理程式之前對請求進行身份驗證和授權
PostAuthorizeRequest #發出信号表示 ASP.NET 已對目前請求進行了授權。 訂閱 PostAuthorizeRequest 事件可確定在處理附加的子產品或處理程式之前對請求進行身份驗證和授權
ResolveRequestCache #引發這個事件來決定是否可以使用從輸出緩沖傳回的内容來結束請求。這依賴于Web應用程式的輸出緩沖時怎樣設定的
PostResolveRequestCache #在 ASP.NET 跳過目前事件處理程式的執行并允許緩存子產品滿足來自緩存的請求時發生
MapRequestHandler #ASP.NET 基礎結構使用 MapRequestHandler 事件來确定用于目前請求的請求處理程式
PostMapRequestHandler #在 ASP.NET 已将目前請求映射到相應的事件處理程式時發生
AcquireRequestState #當 ASP.NET 擷取與目前請求關聯的目前狀态(如會話狀态)時發生
PostAcquireRequestState #預訂 AcquireRequestState 事件的功能可以通路由 PostAcquireRequestState 處理的任何資料
PreRequestHandlerExecute #在ASP.NET開始執行HTTP請求的處理程式之前引發這個事件。在這個事件之後,ASP.NET 把該請求轉發給适當的HTTP處理程式
PostRequestHandlerExecute #在 ASP.NET 事件處理程式(例如,某頁或某個 XML Web service)執行完畢時發生
ReleaseRequestState #在 ASP.NET 執行完所有請求事件處理程式後發生。 該事件将使狀态子產品儲存目前狀态資料
PostReleaseRequestState #在 ASP.NET 已完成所有請求事件處理程式的執行并且請求狀态資料已存儲時發生
UpdateRequestCache #當 ASP.NET 執行完事件處理程式以使緩存子產品存儲将用于從緩存為後續請求提供服務的響應時發生
PostUpdateRequestCache #在 ASP.NET 完成緩存子產品的更新并存儲了用于從緩存中為後續請求提供服務的響應後,發生此事件
OnLogRequest #恰好在 ASP.NET 為目前請求執行任何記錄之前發生,即使發生錯誤,也會引發 LogRequest 事件
PostLogRequest #在 ASP.NET 處理完 LogRequest 事件的所有事件處理程式後發生
EndRequest #在 ASP.NET 響應請求時作為 HTTP 執行管線鍊中的最後一個事件發生
PreSendRequestContent #恰好在 ASP.NET 向用戶端發送内容之前發生,可能發生多次
PreSendRequestHeaders #恰好在 ASP.NET 向用戶端發送 HTTP 标頭之前發生
RequestCompleted #在任何托管子產品和處理程式執行後,它使子產品清理資源
通路一個頁面的過程中,依次觸發了23個事件,而HttpModule可訂閱的事件個數為25個,觀察發現,Error和Disposed這兩個事件沒有觸發。Error事件在發生錯誤的情況下執行,而Disposed事件,當我們關閉剛才打開的頁面,再到文本檔案裡檢視,發現Disposed事件出現了,是以Disposed在會話結束後觸發。
由于HttpModule的個數可以有多個,我們可以按照上面的方式定義HttpModule實作類,然後再web.config中增加配置項,就可以實作多個HttpModule同時訂閱管道事件了。
介紹完HttpModule,那麼HttpHandler又是什麼呢,它又在什麼什麼時候執行呢?接下來看一下HttpHandler。
HttpHandler
HttpHandler是HTTP請求的進行中心,真正地對用戶端請求的伺服器頁面做出編譯和執行,并将處理過後的資訊附加在HTTP請求資訊流中再次傳回到HttpModule中。
HttpHandler與HttpModule不同,一旦定義了自己的HttpHandler類,那麼它對系統的HttpHandler的關系将是“覆寫”關系。
HttpHandler是實IHttpHandler接口的類,IHttpHandler接口定義如下:
public interface IHttpHandler
{
// 摘要:
// 擷取一個值,該值訓示其他請求是否可以使用 System.Web.IHttpHandler 執行個體。
//
// 傳回結果:
// 如果 System.Web.IHttpHandler 執行個體可再次使用,則為 true;否則為 false。
bool IsReusable { get; }
// 摘要:
// 通過實作 System.Web.IHttpHandler 接口的自定義 HttpHandler 啟用 HTTP Web 請求的處理。
//
// 參數:
// context:
// System.Web.HttpContext 對象,它提供對用于為 HTTP 請求提供服務的内部伺服器對象(如 Request、Response、Session
// 和 Server)的引用。
void ProcessRequest(HttpContext context);
}
接口中隻有一個屬性和一個方法,是以實作一個HttpHandler也很簡單,下面實作一個簡單的HttpHandler,代碼如下:
public class MyIISHandler : IHttpHandler
{
/// <summary>
/// 您将需要在網站的 Web.config 檔案中配置此處理程式
/// 并向 IIS 注冊它,然後才能使用它。有關詳細資訊,
/// 請參見下面的連結: http://go.microsoft.com/?linkid=8101007
/// </summary>
#region IHttpHandler Members
public bool IsReusable
{
// 如果無法為其他請求重用托管處理程式,則傳回 false。
// 如果按請求保留某些狀态資訊,則通常這将為 false。
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
//在此處寫入您的處理程式實作。
WriteLog("請求一個asox頁面");
}
#endregion
}
上面我實作了一個很簡單的HttpHandler類,在ProcessRequest方法中,調用上面的HttpModule類中寫文本檔案的方法,在文本檔案中寫入“請求一個asox頁面”,沒錯,是一個asox頁面,我自己定義的檔案格式,下面我會在web.config中添加配置項:
<!--IIS6或者IIS7經典模式-->
<system.web>
<httpHandlers>
<add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
</httpHandlers>
</system.web>
<!--IIS7內建模式-->
<system.webServer>
<handlers>
<add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
</handlers>
</system.webServer>
配置項屬性的解釋:
path:指定了需要調用處理程式的路徑和檔案名(可以包含通配符)。“*”、“*.aspx”、“booklist.aspx”、“test1.aspx,test2.aspx”、“*.asox”、“*.txt”。
verb:指定了處理程式支援的HTTP動作。*-支援所有的HTTP動作;“GET”-支援Get操作;“POST”-支援Post操作;“GET, POST”-支援兩種操作。
type:用名字空間、類名稱和程式集名稱的組合形式指定處理程式或處理程式工廠的實際類型。ASP.NET運作時首先搜尋bin目錄中的DLL,接着在GAC中搜尋。
接着,釋出站點到IIS。打開IIS,找到目前站點的“處理程式映射”,會發現多了剛剛配置的HttpHandler,如圖:
沒錯,關于對*.asox這種類型的檔案,就可以映射到上面建立的HttpHandler來進行處理,觀察其它條目發現,像*.aspx、*.ashx的處理程式是System.Web.UI.PageHandlerFactory和System.Web.UI.SimpleHandlerFactory這樣的工廠類型。沒錯,可以指定處理程式為一個HttpHandler,也可以指定為一個抽象工廠類型。先不說工廠類型的事兒,通路一下網站中的asox頁面,看一下文本檔案的記錄情況。
起作用了,在HttpModule輸出的一堆資訊中,夾雜着HttpHandler的輸出,當然這僅限于通路asox類型的頁面,因為我隻對路徑為*.asox的檔案格式做了設定,修改下配置檔案,例如将path=”*.asox”改為path=”*.aspx”,那麼ASP.NET對*.aspx頁面原有的解析機制将被我們設定的處理程式所覆寫。
回到上面的輸出内容,我們觀察HttpHandler輸出内容所在的位置,位于PreRequestHandlerExecute和PostRequestHandlerExecute這兩個事件之間,這與HttpApplication類中的管道事件的建立過程有關。
前面說到了,處理處理程式可以指定為一個工廠類型,下面,我就建立一個工廠類型的處理程式。
這裡所說的工廠類型的處理程式,就是實作了IHttpHandlerFactory接口的類,IHttpHandlerFactory接口定義如下:
public interface IHttpHandlerFactory
{
// 摘要:
// 傳回實作 System.Web.IHttpHandler 接口的類的執行個體。
//
// 參數:
// context:
// System.Web.HttpContext 類的執行個體,它提供對用于為 HTTP 請求提供服務的内部伺服器對象(如 Request、Response、Session
// 和 Server)的引用。
//
// requestType:
// 用戶端使用的 HTTP 資料傳輸方法(GET 或 POST)。
//
// url:
// 所請求資源的 System.Web.HttpRequest.RawUrl。
//
// pathTranslated:
// 所請求資源的 System.Web.HttpRequest.PhysicalApplicationPath。
//
// 傳回結果:
// 處理請求的新的 System.Web.IHttpHandler 對象。
IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
//
// 摘要:
// 使工廠可以重用現有的處理程式執行個體。
//
// 參數:
// handler:
// 要重用的 System.Web.IHttpHandler 對象。
void ReleaseHandler(IHttpHandler handler);
}
同樣很簡單,也是隻有兩個接口方法,下面是實作這個接口的工廠,代碼如下:
public class MyHttpHandlerFactory:IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
//寫日志
WriteLog(string.Format("requestType:{0}|url:{1}|pathTranslated:{2}", requestType, url, pathTranslated));
//生成一個IHttpHandler
Type t = typeof(MyIISHandler);
IHttpHandler handler = Activator.CreateInstance(t) as IHttpHandler;
return handler;
}
public void ReleaseHandler(IHttpHandler handler)
{
}
}
方法中,傳回了前面建立的那個HttpHander類,依然調用記錄文本檔案的方法輸出内容,友善觀察執行的實際和具體内容。配置檔案改改為這個工廠類。
<add name="mycustomFactoryHandler" path="*.asox" verb="*" type="fengzheng.MyHttpHandlerFactory,handler_modules"/>
配置完畢後,通路網站中的asox頁面,打開文本檔案,内容如下:
我們發現,工廠類中構造IHttpHandler接口的方法發生在HttpModule的MapRequestHandler之後,這同樣與HttpApplication類中構造管道事件有關。
說了這麼多,那麼,HttpModule和HttpHandler究竟能幹什麼呢?
HttpModule很常用的一個作用就是Url重寫,URLRewriter就是基于HttpModule實作的。
另外,有通過HttpHandler對圖檔加水印,防止盜鍊的。
具體的可以參考這篇文章
部署網站注意事項:
網站采用.net 4.0內建模式部署,內建模式是一種統一的請求處理管道,它将ASP.NET請求管道與IIS核心管道組合在一起,這種模式能夠提供更好的性能,能夠實作配置和治理的子產品化,而且增加了使用托管代碼子產品擴充IIS時的靈活性。IIS經典模式與內建模式的差別
內建模式和經典模式的配置檔案稍有不同,部署時需要注意針對不同的部署模式,修改配置檔案。在vs2013中建立的web應用程式,預設的web.config内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!--
有關如何配置 ASP.NET 應用程式的詳細資訊,請通路
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
</configuration>
- 按照經典模式部署,配置檔案應該如下:
<?xml version="1.0" encoding="utf-8"?>
<!--
有關如何配置 ASP.NET 應用程式的詳細資訊,請通路
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<httpModules>
<add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
</httpModules>
<httpHandlers>
<add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
</httpHandlers>
</system.web>
</configuration>
經典模式經測試總是出現如下錯誤,500.21 - 子產品無法識别:
HTTP 錯誤 500.21 - Internal Server Error
處理程式“PageHandlerFactory-ISAPI-4.0_64bit”在其子產品清單中有一個錯誤子產品“IsapiModule”
至于錯誤原因:目前還不是很清楚。
- 按照內建模式部署,配置檔案應該如下,将配置資訊放到system.webServer節點之下:
<?xml version="1.0" encoding="utf-8"?>
<!--
有關如何配置 ASP.NET 應用程式的詳細資訊,請通路
http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<modules>
<add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
</modules>
<handlers>
<add name="mycustomhandler" path="*" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
</handlers>
</system.webServer>
</configuration>
人生沒有回頭路,珍惜當下。