HttpApplication有19個標准事件,當到達第8個事件PostMapRequestHandler觸發的時候,標志着已經獲取到了處理請求的處理程序對象,在第11個事件PreRequestHandlerExecute之后,HttpApplication將執行這個處理程序。
問題:
- HttpApplication如何選擇處理程序?
- 處理程序是什么對象?
- HttpApplication如何得到這個處理程序對象?
一、處理程序
針對不同的請求,Asp.net要有不同的處理,在Asp.net中通過各種處理程序來分別進行處理。通常情況下,根據請求的擴展名來確定處理程序。其實在Asp.net中已經預定義了很多處理程序。
1、處理程序與HttpApplication的關系
在Asp.net中,請求的真正處理是在處理程序這個環節。處理程序負責完成實際的請求處理工作,對於開發者來說,大多數的開發工作都是圍繞着處理程序展開的。實際上,接觸到HttpApplication事件處理的時候不多,HttpApplication除處理程序之外的作用,都可以理解為是為處理程序進行處理前的預處理和處理后的工作。
處理程序在不同的網站開發技術中,有着不同的名稱,在Asp.net中,稱為HttpHandler。
2、處理程序接口IHttpHandler和IHttpAsyncHandler
在Asp.net中,所有的處理程序類必須實現IHttpHandler接口或者實現IHttpAsyncHandler接口,一個同步,一個異步。
IHttpHandler的定義如下:
public interface IHttpHandler { bool IsReusable { get; } void ProcessRequest(HttpContext context); }
一般處理程序里實現的,就是這個東西。
- ProcessRequest是IHttpHandler接口的主要方法,接收並通過一個HttpContext類型的請求上下文對象,處理程序可以得到關於處理請求所需的信息。通過 HttpContext的Response屬性可以得到響應的對象,用以向客戶端返回服務器處理的結果。
- IsReusable屬性表示:“當這個處理程序對象在使用之后,是否還可以被緩存起來,在以后的請求處理中再次使用”,這個屬性主要用來配合處理程序工廠使用。
異步的處理程序派生自同步的處理程序接口,接口定義如下:
public interface IHttpAsyncHandler : IHttpHandler { IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void EndProcessRequest(IAsyncResult result); }
比IHttpHandler增加了兩個方法,BeginProcessRequest和EndProcessRequest方法。跟其他異步方法使用一樣。
3、在處理程序中使用會話
處理程序是Asp.net網站中處理請求的基本單位。在默認情況下,處理程序中甚至不能使用會話狀態,這樣可以提高網站處理的速度。對於需要讀寫會話狀態的處理程序,必須實現一個特定的標記接口IRequireSessionState,這個接口定義在命名空間System.Web.SessionState中,其中沒有定義任何成員,所以,實現這個接口並不需要在類中增加任何成員。類似地,同樣定義在這個命名空間下的接口IReadOnlySessionState也沒有定義任何程序,用來標志只需要讀取會話狀態的處理程序。
這種沒有任何成員的接口,通常被稱為標記接口,它的出現是由於.Net早期沒有標簽(Attribute)而曇花一現,在Asp.net中,這是僅有的一例。
HttpApplication的第9個事件AcquireRequestState事件和第13個事件ReleaseRequestState中,HttpApplication即通過判斷已經獲取得處理程序對象是否實現了這些接口來決定請求狀態的處理工作。
4、處理程序工廠
實現了處理程序接口的類就可以被用來創建處理程序對象直接使用,如果再配合一個處理程序工廠,那么就可以實現處理程序對象的管理。
比如,創建一個處理程序對象池,就可以不用在每次使用處理程序的時候創建一個新的對象,而是可以從池中取出一個現有的對象直接使用,以提高效率。
在Asp.net中,作為處理程序工廠的類必須實現接口IHttpHandlerFactory,這個接口在命名空間System.Web下,定義為:
public interface IHttpHandlerFactory { IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); void ReleaseHandler(IHttpHandler handler); }
其中GetHandler方法用來通過這個處理程序工廠獲取一個處理程序對象,ReleaseHandler方法用來釋放一個處理程序對象。
關系圖如下:

5、注冊處理程序
每一種處理程序用來處理一類請求,不同的請求類別通過請求的擴展名來進行區分,處理程序與請求之間的匹配關系在網站的配置文件web.config中通過配置參數來進行設置。system.web配置元素的子元素httpHandlers用來配置網站所使用的處理程序。httpHandlers元素可以包含三種子元素:add、remove和clear。
add子元素有三個必選的屬性,作用如下:
- verb通過一個逗號(,)分割的HTTP請求類型列表來表示處理請求的類型,例如:GET,POST等;使用星號(*)表示處理所有類型的請求。
- path通過一個固定的URL路徑或者一個使用星號(*)的通配符來匹配請求的URL,例如,使用*.aspx表示這個處理請求將處理所有擴展名為aspx的請求。
- type處理程序的類型名稱,或者是處理程序工廠的類型名稱,這個類型必須是類型的全名,包含命名空間、程序集(當類放在私有程序集時)。
- validate為可選的屬性,如果設置為false,那么Asp.net在第一次匹配的請求調用之前,將不會試圖加載這個類。
如果我們定義了一個處理程序類,這個類定義在命名空間MyHandler下,名為ValidateCodeHandler,這個類定義在私有程序集MyHandler.dll中,用來處理GET類型的請求,請求的擴展名為vc,那么配置參數如下:
<configuration> <system.web> <httpHandlers> <add verb="GET" path="*.vc" type="MyHandler.ValidateCodeHandler,MyHandler.dll"/> </httpHandlers> </system.web> </configuration>
在網站應用程序運行的時候,實際得到的配置文件來自於系統的machine.config,系統的web.config和網站自身的web.config合並。在web.config中Asp.net已經預先配置了57中處理程序的映射,程序員還可以通過處理程序接口擴展自定義的處理程序。
6、使用處理程序生成驗證碼
驗證碼是網站開發中常使用的安全技術,用於防止惡意的攻擊。
我們通過一個處理程序來生成發送到客戶端的驗證碼圖片,完成這個處理程序需要以下步驟:
- 創建一個類庫項目MyHandler,新的項目的默認命名空間也是MyHandler。
- 在類庫項目MyHandler中添加一個新的類ValidateCodeHandler,實現IHttpHandler接口。為了在處理程序中使用Session狀態管理,同時實現IRequiresSessionState接口。
- 在ProcessRequest方法中完成驗證碼生成的操作。
- 在網站項目中引用這個類庫項目。
- 在網站項目的配置文件web.config中注冊這個處理程序。
- 在需要驗證碼的頁面上使用處理程序生成的驗證碼。
新建一個Asp.net Web程序:
public class ValidateCodeHandler : IHttpHandler,IRequiresSessionState { private static Random random = new Random(); public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/jpeg"; Image image = new Bitmap(60, 30); //生成隨機數 int code = random.Next(1000, 10000); string codeString = code.ToString(); //使用會話狀態 context.Session["Code"] = codeString; using (Graphics g = Graphics.FromImage(image)) { g.Clear(Color.WhiteSmoke); StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; g.DrawString(codeString, new Font("Arial", 14), Brushes.Blue, new RectangleF(0, 0, image.Width, image.Height), sf); } context.Response.ContentType = "image/jpeg"; image.Save(context.Response.OutputStream, ImageFormat.Jpeg); } public bool IsReusable { get { return false; } }
然后在web.config里面增加如下配置:
<httpHandlers> <add verb="GET" path="*.vc" type="WebApplication1.ValidateCodeHandler"/> </httpHandlers>
然后啟動項目,隨便打開一個本系統下.vc后綴的路徑:

從項目中我們可以看到,所有.vc后綴的路徑,Asp.net都會映射到這個類中處理。
二、一般處理程序
雖然通過標准的方式可以創建處理程序,但是實現的步驟比較復雜,為了方便網站開發中對於處理程序的應用,從Asp.net 2.0開始,Asp.net提供了稱為一般處理程序的處理程序,允許我們私用比較簡單的方式定義擴展名為ashx的專用處理程序。
通過Visual Studio 2010創建的一般處理程序將會生成兩個文件Handler1.ashx和Handler1.ashx.cs。其中Handler1.ashx文件中的內容如下所示:
<%@ WebHandler Language="C#" Class="Handler" %>
對應的代碼文件Handler1.ashx.cs中的內容如以下代碼所示。可以非常清楚地看到,這就是一個實現IHttpHandler接口的類,如下:
public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Write("Hello World"); } public bool IsReusable { get { return false; } } }
一般處理程序實際上就是一個處理程序類,這個處理程序被Asp.net直接在系統的配置文件中映射到了ashx擴展名的請求上。這樣,我們就不需要在配置文件中進行配置了。
1、一般處理程序工廠
對於一般處理程序來說,擴展名請求的程序並不需要程序員在web.config中進行配置,這是因為這個處理程序已經定義在系統配置文件的web.config。所以瀏覽器可以直接請求擴展名為ashx的地址:
<add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" />
通過配置文件,我們可以看到,對於擴展名為ashx的請求是通過定義在命名空間System.Web.UI下的SimpleHandlerFactory處理程序工廠來完成的。
當請求一個ashx擴展名的服務器上的資源的時候,SimpleHandlerFactory將找到對應的ashx文件,通過這個文件找到對應的處理程序,最后,SimpleHandlerFactory通過反射創建一個此類型的處理程序對象實例,並通過GetHandler方法返回給HttpApplication,完成最終的請求處理過程。
2、使用一般處理程序的場合
對於Asp.net網站來說,最常見的處理結果就是HTML網頁,生成網頁的工作通常使用擴展名為aspx的Web窗體來完成,對於處理結果不是HTML的請求,都可以通過一般處理程序來完成。例如生成RSS Fed,XML,圖片等。
一般處理程序是Asp.net網站中最為簡單、高效的處理程序,在處理返回類型不是HTML的請求中有着重要的作用。
3、使用一般處理程序生成驗證碼圖片
例如,在前面實現的驗證碼處理程序用一般處理程序能夠更快地完成,直接在ProcessRequest方法中實現功能就可以了,原來的第1、2、4、5步都可以省略。
public class Handler1 : IHttpHandler, IRequiresSessionState { private static Random random = new Random(); public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/jpeg"; Image image = new Bitmap(60, 30); //生成隨機數 int code = random.Next(1000, 10000); string codeString = code.ToString(); //使用會話狀態 context.Session["Code"] = codeString; using (Graphics g = Graphics.FromImage(image)) { g.Clear(Color.WhiteSmoke); StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; g.DrawString(codeString, new Font("Arial", 14), Brushes.Blue, new RectangleF(0, 0, image.Width, image.Height), sf); } context.Response.ContentType = "image/jpeg"; image.Save(context.Response.OutputStream, ImageFormat.Jpeg); } public bool IsReusable { get { return false; } } }
一般處理程序要使用Session也是要實現接口IRequiresSessionState的。
4、使用一般處理程序生成JSON
代碼示例:
public class UploadPercentHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { Person p = new Person(); p.Id = 1; p.Name = "撼地神牛"; JavaScriptSerializer jss = new JavaScriptSerializer(); string json = jss.Serialize(p); context.Response.ContentType = "application/json"; context.Response.Write(json); } public bool IsReusable { get { return false; } } } public class Person { public int Id { get; set; } public string Name { get; set; } }
輸出:

三、頁面處理程序
Asp.net使用模板的方式來生成一個處理程序。模板的擴展名為aspx,通過一個內置的處理程序工廠PageHandlerFactory將aspx形式的模板編譯生成處理程序代碼,然后,將這個處理程序返回給HttpApplication完成請求的處理。aspx形式的模板文件可以直接通過文本編輯器進行編輯。
1、頁面處理工廠
在Asp.net的系統配置文件中,已經進行了如下的配置,可以看到,對於擴展名為aspx的請求,將由PageHandlerFactory這個處理程序工廠進行處理。
配置如下:
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" />
這個類定義在命名空間System.Web.UI下,具體的定義如下:
public class PageHandlerFactory : IHttpHandlerFactory
這是一個典型的處理程序工廠,用來返回一個實現IHttpHandler接口的處理程序對象實例。PageHandlerFactory將尋找匹配請求名稱的aspx文件,然后將這個模板文件進行解析,通過代碼生成機制生成相應的頁面類。這個頁面類派生自Page類,Page類定義在命名空間System.Web.UI下,類的定義為:
public class Page : TemplateControl,IHttpHandler
aspx模板文件與Page類和生成類之間的關系是:
PageHandlerFactory讀取aspx文件,將aspx文件解析生成一個派生自Page的類;
aspx模板的解析和代碼生成僅僅出現在第一次處理的時候,以后的請求將直接使用已經編譯生成的程序集,所以,這個處理過程並不會降低網站的處理速度。
2、創建頁面處理程序
在PageHandlerFactory的內部,通過PageParser這個類解析指定的aspx文件生成Page類的派生類,這個派生類用來創建頁面處理程序的對象實例。這個類定義在命名空間System.Web.UI中,完整的定義如下:
public sealed class PageParser
PageParser有一個靜態方法
GetCompiledPageInstance(string virtualPath,string inputFile,HttpContext context)
GetCompiledPageInstance方法內部又使用了BuildManager類來創建頁面對象實例,這個類定義在命名空間System.Web.Compilation下,完整的類定義如下:
public sealed class BuildManager
BuildManager的CreateInstanceFromVirtualPath方法通過頁面的虛擬路徑通過代碼生成得到派生的頁面類,然后通過反射創建這個頁面對象,代碼如下:
public static Object CreateInstanceFromVirtualPath(string virtualPath,Type requiredBaseType)
3、生成的代碼
對於一個aspx模板文件來說,一般情況下將對應存在一個同名的cs后台代碼文件,其中定義了一個與頁面同名的類,通過代碼文件可以看到這個類派生自Page的類。
PageHandlerFactory通過aspx文件將生成兩個類,一個為與后台代碼中定義的類同名的部分類,這個部分類將與后台代碼中定義的類在編譯的時候合並為一個派生自Page的頁面派生類。但是,在Asp.net中,創建實際頁面對象的類並不是這個類,而是生成的第二個類,一般情況下,這個類的名字為頁面的名字后面加上下划線和aspx。這才是實際創建頁面對象的頁面類。
假設有一個Person.aspx,那么應該存在一個對應的后台代碼文件Person.aspx.cs,其中定義了Person這個Page的派生類。
通過PageHandlerFactory,Person.aspx將生成兩個類,部分類Person和Person_aspx。其中,后台代碼文件中的Person將和生成的部分類Person在編譯的時候合並為一個完整的Person類定義,而Person_aspx則是從Person派生的一個頁面類,用來創建實際的頁面對象實例。
4、使用頁面處理程序
由於頁面處理程序是通過模板生成的,所以,大多數情況下,我們只要創建好這個aspx的模板就可以了,HttpApplication會通過PageHandlerFactory創建這個生成的處理程序,然后使用這個處理程序完成服務器的處理任務。現在,如果我們需要通過處理程序生成一個網頁的話,只需要設計一個aspx頁面即可。
在一些特殊的情況下,我們需要創建一些生成HTML輸出的程序。但是這些程序並不希望能夠被客戶端直接請求,那么,可以借助PageParser或者BuildManager通過aspx格式的模板來創建一個自定義的處理程序完成處理任務。
