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中的应用。
职责链模式(摘录)
意图
是对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直道一个对象处理它为止。