IHttpHandler不是什麼新鮮的東西,大家都知道怎麼用,故在本文標題是“小練”。
那麼練習什麼呢?
考慮:
在一個Web應用中,我們可以定義一個IHttpHandler處理常式完成一個工作,也可以定義多個IHttpHandler處理常式完成多個工作,如果這些工作互相獨立,那麼應該為每一個處理常式定義一個副檔名,如此大量的處理常式有可能使你感到困惑,而且系統還需要知道如何使用這些處理常式。
練習目的:
建立一個IHttpHandler處理常式來執行所有需要處理的工作,並且其本身並不知道需要處理的工作是什麼。
目的說明:
我的意思是通過一個httpHandlers擴充,實現任意多個處理常式,這樣就省去在設定檔裡寫很多system.web/httpHandlers/add
源碼下載
分析:
假設成功建立了如上文所述的處理常式,則該處理常式將處理多個未知任務,對於處理常式來說,它只知道任務的參與者,而不知道參與者能完成什麼任務(只有參與者自己知道能做什麼)。顯然,這種功能需要反射來幫忙,而且需要建一個設定檔類,把參與者一個個加進來就像appSettings中的add一樣。找到參與者之後,就不難開展工作了,可以先問每個參與者都會做什麼,然後就是:Do it,按照這一思路,最簡單的就是再迴圈然中判斷,為了讓練習能夠更有意思,我不選擇這個做法,而是運用設計模式裡的職責鏈模式來完成,即把所有參與者都“串”起來,先問第一個人能不能做,不能做再通過他詢問第二個人,依次類推,直至最後一個,如果還找不到能做事的人,那隻好把工作throw給“領導”了。
代碼:
首先是有關配置的類
internal class HandlerSectionElement : ConfigurationElement
{
public HandlerSectionElement()
{
//
// TODO: 在此處添加建構函式邏輯
//
}
[ConfigurationProperty("type", IsRequired = true)]
[StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\")]
public string TypeString
{
get
{ return (string)this["type"]; }
set
{ this["type"] = value.Trim(); }
}
}
internal class HandlerSectionCollection : ConfigurationElementCollection
{
public HandlerSectionCollection()
{
//
// TODO: 在此處添加建構函式邏輯
//
}
protected override ConfigurationElement CreateNewElement()
{
HandlerSectionElement element = new HandlerSectionElement();
BaseAdd(element);
return element;
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((HandlerSectionElement)element).TypeString;
}
public string[] TypeKeys
{
get
{
object[] keys = BaseGetAllKeys();
string[] temp = new string[keys.Length];
for (int i = 0; i < keys.Length; i++)
{
temp[i] = (string)keys[i];
}
return temp;
}
}
}
internal class HandlerSection : ConfigurationSection
{
public HandlerSection()
{
//
// TODO: 在此處添加建構函式邏輯
//
}
[ConfigurationProperty("handlers", IsDefaultCollection = false, IsRequired = true)]
[ConfigurationCollection(typeof(HandlerSectionCollection), AddItemName = "add", ClearItemsName = "clear")]
public HandlerSectionCollection Handlers
{
get
{
return (HandlerSectionCollection)base["handlers"];
}
}
}
有了這些類就可以在Web.config中配置了
<configSections>
<section name="chainHandlerSection" type="ChainHandlerLib.HandlerSection,ChainHandlerLib"
allowDefinition="Everywhere" allowLocation="false"/>
</configSections>
<chainHandlerSection>
<handlers>
<add type="TestHandlerLibrary.TestHandler2,TestHandlerLibrary"/>
<add type="TestHandlerLibrary.TestHandler,TestHandlerLibrary"/>
<add type="HelloHandler"/>
</handlers>
</chainHandlerSection>
接下來是擷取配置資料類,通過它來讀取配置節點
internal class HandlerConfig
{
HandlerSection _sections;
public HandlerConfig()
{
_sections = (HandlerSection)ConfigurationManager.GetSection("chainHandlerSection");
}
public HandlerSectionCollection Handlers
{
get
{
return _sections.Handlers;
}
}
}
關鍵步驟:定義一個抽象類別ChainHandler,實現IHttpHandler,用來表示鏈表中的每一項,其他Handler只需實現該類即可
public abstract class ChainHandler : IHttpHandler
{
ChainHandler _next;
public ChainHandler Next
{
get
{
return _next;
}
set
{
_next = value;
}
}
public virtual IHttpHandler GetHttpHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
if (Next != null)
return Next.GetHttpHandler(context, requestType, url, pathTranslated);
else
{
//已至鏈表尾部仍未找到適合的處理常式
throw new NotImplementedException("未知處理常式");
}
}
#region IHttpHandler 成員
public virtual bool IsReusable { get { return false; } }
public abstract void ProcessRequest(HttpContext context);
#endregion
}
GetHttpHandler預設是將請求轉交到後繼節點,在具體類中可以重寫該方法。
下一步是編寫HttpHandler的處理工廠,接收處理請求,然後交給鏈表
public sealed class ChainHandlerFactory : IHttpHandlerFactory
{
/// <summary>
/// 鏈表第一個值
/// </summary>
ChainHandler _first;
public ChainHandlerFactory()
{
_first =BuildChain();
}
#region IHttpHandlerFactory 成員
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
return _first.GetHttpHandler(context, requestType, url, pathTranslated);
}
public void ReleaseHandler(IHttpHandler handler)
{
handler = null;
}
#endregion
#region 構造鏈表
/// <summary>
/// 構造鏈表
/// </summary>
/// <returns></returns>
private ChainHandler BuildChain()
{
HandlerConfig config = new HandlerConfig();
//擷取類型字串數組
string[] typeKeys = config.Handlers.TypeKeys;
List<ChainHandler> list = new List<ChainHandler>();
//預設的App_Code程式集名
string webCodeAssamblyName = "App_Code";
//首先把assamblyName值設定為當前Web應用程式的App_Code程式集,如果
//config裡定義了assambly,將在後面設定其新值,否則使用App_Code程式集
//擷取域中所有載入的程式集
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
//迴圈檢查App_Code程式集
for (int i = loadedAssemblies.Length - 1; i > 0; i--)
{
if (loadedAssemblies[i].FullName.StartsWith("App_Code."))
{
webCodeAssamblyName = loadedAssemblies[i].FullName;
break;
}
}
foreach (string typekey in typeKeys)
{
string className = string.Empty;
//程式集名為App_Code程式集名
string assamblyName = webCodeAssamblyName;
string[] splitTemp = typekey.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
className = splitTemp[0].Trim();
//設定新assambly值
if (splitTemp.Length == 2)
assamblyName = splitTemp[1].Trim();
//反射構造執行個體
ChainHandler handler = Assembly.Load(assamblyName).CreateInstance(className) as ChainHandler;
if (handler != null)
list.Add(handler);
}
//設定Next
for (int i = 0; i < list.Count - 1; i++)
{
list[i].Next = list[i + 1];
}
if (list.Count < 1)
throw new NotImplementedException("chainHandlerSection/handlers 節點未配置正確");
return list[0];
}
#endregion
}
BuildChain方法讀取配置,然後通過反射構造其具體類,並添加到鏈表,鏈表的順序和配置的順序一致,最後通過一個迴圈,依次設定Next屬性,將整個鏈表串連起來,返回鏈表中的第一項。GetHandler方法從第一項開始執行GetHttpHandler,即詢問是否可以處理請求。
值得一提的是如何擷取App_Code程式集,該程式集在發布前的網站是以App_Code.xxxx.dll形式出現的(xxxx表示隨機字元),而在發布後的網站中是以App_Code.dll形式出現的。對於後一種情況比較好辦,只要將程式集的名稱置為“App_Code”即可,而前一種情況,似乎沒有比從應用程式定義域載入的所有程式集中迴圈檢查“App_Code.”來擷取對應的隨機程式集更好的辦法。
下面看看具體的處理常式如何寫
public class HelloHandler:ChainHandlerLib.ChainHandler
{
public HelloHandler()
{
//
// TODO: 在此處添加建構函式邏輯
//
}
public override IHttpHandler GetHttpHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
if (context.Request.QueryString.Count==0)
return this;
return base.GetHttpHandler(context, requestType, url, pathTranslated);
}
public override void ProcessRequest(HttpContext context)
{
context.Response.Write("<i><b>你好,請登入</b></i>");
}
}
代碼比較簡單,重寫GetHttpHandler方法:當web請求中沒有QueryString字元時,返回本身,這樣ChainHandlerFactory就得到了一個處理常式的執行個體,然後就調用HelloHandler.ProcessRequest執行請求,輸出一行文字。
當然,在執行前還需要對Web.config配置
<httpHandlers>
<add verb="*" path="*.ashx" type="ChainHandlerLib.ChainHandlerFactory,ChainHandlerLib"/>
</httpHandlers>
path可以自訂,隨你喜好,此處用ashx是為了方便,不用設定IIS了。
小結
上面的練習探討了職責鏈模式在IHttpHandler中的應用。
職責鏈模式(摘錄)
意圖
是對象都有機會處理請求,從而避免請求的寄件者和接受者之間的耦合關係。將這些對象連成一條鏈,並沿著這條鏈傳遞請求,直道一個對象處理它為止。