上篇回顾
上一篇,简单的讲了一下WCF里面如何拦截一个操作,不过,只说了一半,因为程序拦截住WCF的一个操作请求之后,并不知道应该去干什么,因此,这个拦截没有发挥它该有的功能。那么怎么才能让我们的拦截发挥作用,并且有能够实现主题——基于配置的实现哪?
思考这个问题的时候,是否想到了在Web上大显身手的MVC模式哪?想一下MVC是如何判断应该调用那个Controller的,当然当然是RouteTable。
那么我们为什么不也作一个路由表哪?
打造路由表——寻找Url的等价物
开始打造我们自己的路由表之前,需要想明白一件事情,WCF里面的路由的URL怎么取,事实上,WCF的每个操作都有自己的Action,因此,完全可以用Action来作为WCF中Url的等价物。
那么,怎么在WCF中抓取出当前操作的Action哪?
其实,相当的简单,只需要这样一句就可以了:
string action = OperationContext.Current.IncomingMessageHeaders.Action;
有了Action,这个Url的等价物之后,我们需要做的就是决定如何定义一个路由表,这个字符串简单,变成一个可以执行的操作。
打造路由表——思考路由策略
因为是自己打造路由,所以,很多时候可以跳出现有的MVC之类的框架的思维定式,寻找一个真正想要的策略(即使不是什么最佳策略,也无关紧要)。
对于任何一个WCF的操作来说,总是可以想象到下面这几步的:
- 日志
- 验证参数
- 错误处理
- 各种未知的操作
其中,各种未知操作可能也会出现管道/过滤器式(pipe/filter)的处理,前一步做完再做下一步。因此,我们的路由表最好能支持链式处理。
说到这里,是不是想到了什么?
至少我在第一时间想到的是:设计模式——责任链模式。
打造路由表——基于责任链的路由表
既然确定了要基于责任链,那么首先来准备责任链的基础类型:
public interface IOperationHandler { IOperationHandler Next { get; set; } object Request { get; set; } object Response { get; set; } void Handle(); }
此时,是不是发现上一篇中之前要限定操作只有一个参数和一个返回值了,以及只支持同步方法,为这里的接口带来了巨大的简化。
打造路由表——路由到原始处理
在打造别的Handler之前,先写一个原始的WCF的处理
internal sealed class RawHandler : IOperationHandler { public object Instance { get; private set; } public object Request { get; set; } public object Response { get; set; } private readonly IOperationInvoker m_raw; public RawHandler(object instance, IOperationInvoker raw) { Instance = instance; m_raw = raw; } public override void Handle() { object[] outputs; Response = m_raw.Invoke(Instance, new object[] { Request }, out outputs); } }
有了这个RawHandler,就可以打造出:
public abstract class HandlerFactory { private static HandlerFactory m_default = new DefaultHandlerFactory(); public static HandlerFactory Default { get { return m_default; } } private static HandlerFactory m_current = m_default; public static HandlerFactory Current { get { return m_current; } set { m_current = value; } } public abstract IOperationHandler CreateHandlerChains(RawHandler raw); } internal sealed class DefaultHandlerFactory : HandlerFactory { public override IOperationHandler CreateHandlerChains(RawHandler raw) { IOperationHandler handler = raw; // todo : create handler chains ... return handler; } }
然后,在把这个HandlerFactory放到上一篇的ControllerInvoker类里面:
public object Invoke(object instance, object[] inputs, out object[] outputs) { var raw = new RawHandler(instance, Inner); var c = HandlerFactory.Current.CreateHandlerChains(raw); c.Request = inputs[0]; c.Handle(); outputs = new object[0]; return c.Response; }
到目前为止,WCF服务就有可以正常的跑起来了,当然目前没有更改任何WCF的行为,而仅仅是绕了一圈,把服务里面加了个拦截和路由,并且路由总是直接调用服务原来的代码实现。
感到很沮丧?别急,虽然到这里写了一大堆类,并且没有更改任何的WCF的默认行为,但是,已经成功的加入了一个可以更改WCF行为的注入点:CreateHandlerChains方法
通过这个方法,我们可以随时添加Handler,来修改WCF的行为。
打造路由表——硬编码的路由表
在思考如何动态配置之前,不妨先从硬编码开始。所以写一个简单的HardCodeHandlerFactory:
public class HardCodeHandlerFactory : HandlerFactory { private readonly List<HandlerInfo> Creators = new List<HandlerInfo>(); private sealed class HandlerInfo { public string ActionPattern; public Func<IOperationHandler, IOperationHandler> Creator; public bool IsMatch(string action) { return Regex.IsMatch(action, ActionPattern); } } public void Add(Func<IOperationHandler, IOperationHandler> creator, string actionPattern) { Creators.Add(new HandlerInfo { ActionPattern = actionPattern, Creator = creator, }); } public override IOperationHandler CreateHandlerChains(RawHandler raw) { IOperationHandler handler = raw; string action = OperationContext.Current.IncomingMessageHeaders.Action; foreach (var item in Creators) { if (item.IsMatch(action)) { handler = item.Creator(handler); } } return handler; } }
通过这个简单的Factory,可以很简单的通过添加Handler,作用于那些Action符合Pattern的操作。
为了方便演示,先定义一个简单的Handler:
public abstract class OperationHandlerBase : IOperationHandler { private object m_request; private object m_response; public IOperationHandler Next { get; set; } public abstract void Handle(); public object Request { get { if (Next == null) return m_request; else return Next.Request; } set { if (Next == null) m_request = value; else Next.Request = value; } } public object Response { get { if (Next == null) return m_response; else return Next.Response; } set { if (Next == null) m_response = value; else Next.Response = value; } } } public class LogHandler : OperationHandlerBase { public override void Handle() { Trace.TraceInformation("Begin action:{0}", OperationContext.Current.IncomingMessageHeaders.Action); // this handler can handle nothing. //if (false) // return; //else if (Next != null) { Next.Handle(); } else { Trace.TraceInformation("Action {0} not implemented.", OperationContext.Current.IncomingMessageHeaders.Action); throw new FaultException("Not implemented."); } Trace.TraceInformation("End action:{0}", OperationContext.Current.IncomingMessageHeaders.Action); } }
然后再把这个Handler注册到工厂中,例如:
HardCodeHandlerFactory f = new HardCodeHandlerFactory(); f.Add(handler => new LogHandler { Next = handler }, "\\S"); HandlerFactory.Current = f;
这样,所有有Controller这个特性的WCF操作都会在开始和正常结束的时候输出2行日志(因为正则\S可以匹配和Action,如果需要选择性的注入,可以修改正则)。
当然,因为LogHandler本身并不能处理掉任何的请求,因此,在责任链中的它必须把请求发送给下一个处理者(如果没有下一个处理者,那就是有问题的责任链,因此直接抛错)。
但是在某些Handler中(例如负责参数检查的Handler),如果发现参数有问题,将可以直接将请求变成已经处理,从而短路后面的处理。
打造路由表——思考和下篇预告
前面一节,已经可以简单实现HardCode的路由功能,然后可以思考一下如何通过配置的形式来更改路由表。
思考一下路由的要素:
- 处理器类型是如何发现的(配置文件指定等方式)
- 处理器的创建(构造函数等约定)
- 处理器是否需要可配置的生效(也就是HardCode中的正则的作用)
- 环境变量(也就是前面的处理器可以修改环境值而影响后面的处理器行为)
下一篇将解决这些问题,实现一个基于配置的WCF。