WCF后续之旅(4):WCF Extension Point 概览
在本系列的每篇文章中,我多次提到WCF是一个极具可扩展性的分布是消息通信框架。为了让读者对WCF Extension有一个总体的的认识,在这里我会简单列举了我们经常使用的绝大部分的扩展点,以及通过这些扩展点能够解决实现项目开发中的那些问题。
有一点需要特别提醒的是:对WCF extensions的灵活应用依赖于你对channel layer和service mode dispatching system的深入理解。所以,如果你对channel layer不甚了解,可以参阅本系列的第一个部分(WCF是如何通过Binding进行通信的)和第二部分(如何对Channel Layer进行扩展——创建自定义Channel), 若是想了解更多关于dispatching system的细节,可以参考本系列的第三部分(WCF Service Mode Layer 的中枢—Dispatcher)。
现在,我们按照在上一篇文章的Dispatching的执行流程,来介绍dispatching system中可以用于对WCF进行扩展的对象,已经这个可扩展对象具体解决的问题和扩展的方式。为了利于读者理解这些可扩展对象具体被使用在Dispatching整个的生命周期的哪个阶段,我们在标注Step 1、Step 2…字样,读者可以在上一篇文章中查阅对应的步骤在执行怎样的功能。
1 、自定义InstanceContextProvider(Step 5)
在WCF infrastructure中, InstanceContext是以一个很重要的概念。InstanceContext是什么呢?简言之,InstanceContext就是对service instance的封装(service instance wrapper),对于每一个service instance来讲,WCF都会通过一些contextual information对其进行包装。这些contextual information存在的目的在于让不同的request关联到对应的service instance上。对于不同的Instancing Mode(PerCall、PerSession和Singleton),我们往往具有不同的InstanceContext。而对于PerSession方式的instancing mode,InstanceContext显得尤为重要,原因很简单,我们必须保证来自同一个Session的request被分发到同一个service instance,不然很难维护其session的信息。
InstanceContext的获取通过InstanceContextProvider来实现。在WCF中所有的InstanceContextProvider实现了System.ServiceModel.Dispatcher.IInstanceContextProvider interface。
public interface IInstanceContextProvider
{
// Methods
InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel);
void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel);
bool IsIdle(InstanceContext instanceContext);
void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext);
}
- GetExistingInstanceContext:当message有ChannelDispather交付到EndpointDispatcher的时候,该方法会被调用去试图获取一个已经存在的InstanceContext。比如:PerSession模式下,如果session已经开始,那个会返回绑定到当前session的InstanceContext,否则return null;对于Singleton模式,由于使用一个service instance来处理所有的request,所以一旦service instance被创建,后续的request都将返回同一个InstanceContext,否则return null;而对于PerCall来说,由于对于每个request来说,都需要创建一个新的service instance来处理,所以它永远是return null。
- InitializeInstanceContext:如何GetExistingInstanceContext返回null,将通过这个方法来创建和初始化一个新的InstanceContext.
- IsIdle:当所有的InstanceContext操作完成以后,该方法会被调用,返回的bool类型的结果将用作是否对InstanceContext进行清理和回收的依据。如何你不希望对创建的InstanceContext进行回收,那么你可以将此方法返回为false。比如:Singleton和PerSession模式下就直接return false;而PerCall则return true。
- NotifyIdle:当对InstanceContext进行真正的清理和回收时,此方法会被回调。
和3种instancing mode相匹配, WCF定义了3种InstanceContextProvider:
- System.ServiceModel.Dispatcher.PerCallInstanceContextProvider
- System.ServiceModel.Dispatcher.PerSessionInstanceContextProvider
- System.ServiceModel.Dispatcher.SingletonInstanceContextProvider
通过对InstanceContextProvider进行扩展,创建你自定义的InstanceContextProvider,你可以以你需要的方式关联request和service instance。比如:为了性能的提升,你可能试图通过一种对象池的机制实现对service instance的创建和提取,当需要使用到某个service instance的时候,先从对象池中获取该对象,如果不存在再从新创建对象。当service instance调用完毕,将其放入对象池中。这样避免了过于频繁的对象创建而引起对性能的影响。有兴趣的朋友不妨试着做一做。
注:当你自定义InstanceContextProvider的时候,一般继承base class:System.ServiceModel.Dispatcher.InstanceContextProviderBase而不是完全实现System.ServiceModel.Dispatcher.IInstanceContextProvider interface。
2 、自定义MessageFilter(Step 6)
通过对dispatching system的介绍,我们了解到:当ChannelDispather通过ChannelListener创建的Channel接收到request message之后,自己不会对message进行处理,而是遍历自己的EndpointDispatcher集合属性,找到与request message相匹配的EndpointDispatcher。到底怎样的匹配规则会被采用呢?
具体实现是这样的:每个EndpointDispatcher都定义了两个特殊的属性:AddressFilter和ContractFilter,它们的类型继承了abstract class:System.ServiceModel.Dispatcher.MessageFilter。
public abstract class MessageFilter
{
// Methods
protected MessageFilter();
protected internal virtual IMessageFilterTable<FilterData> CreateFilterTable<FilterData>();
public abstract bool Match(Message message);
public abstract bool Match(MessageBuffer buffer);
}
MessageFilter定义了两个Match重载,所以子类实现该重载实现自定义的匹配规则。在具体实现的时候,会解析request message或者MessageBuffer (message 的memory buffer表示)(一般是message header),来判断该request是否和对应的EndpointDispatcher相互匹配。
WCF为我们提供了一下6类Message Filter:
- System.ServiceModel.Dispatcher.ActionMessageFilter:通过message Action header进行匹配。
- System.ServiceModel.Dispatcher.MatchAllMessageFilter:匹配所有的message,也就是直接返回true。
- System.ServiceModel.Dispatcher.EndpointAddressMessageFilter:根据message的To header 进行匹配。
- System.ServiceModel.Dispatcher.MatchNoneMessageFilter:不会和任何的message匹配,也就是直接返回false。
- System.ServiceModel.Dispatcher.PrefixEndpointAddressMessageFilter:和EndpointAddressMessageFilter相似,不过这次匹配的不是整个message的To header ,而是To header 的前缀。
- System.ServiceModel.Dispatcher.XPathMessageFilter:给予Xpath expression的匹配方式。
那么EndpointDispatcher的ContractFilter和AddressFilter采用的又是那种类型的MessageFilter呢?通过Reflector看看EndpointDispatcher的构造函数就会知道,AddressFilter采用的是EndpointAddressMessageFilter,而ContractFilter采用的则是MatchAllMessageFilter。也就是说,当ChannelDispather在为接收到的request message选择合适的EndpointDispatcher的时候,会根据message To header上的address进行匹配。
你也许会问,如何有不止一个EndpointDispatcher满足匹配条件怎么办呢?答案是:ChannelDispatcher会选择一个Filter优先级最高的一个,该优先级由EndpointDispather的FilterPriority属性来决定。
如何你想改变这种默认的filter方式,你可以通过你自定义的behavior,来改变EndpointDispatcher的这两个Filter:AddressFilter和ContractFilter。你或许会怀疑是否有这样做的必要,那么我给你一个具体的例子:我们都知道我们有两种传统的Web request方式:Get和Post,前者根据query string,后者基于form。在实际的项目中,你可能会使用到形如Get的方式来访问WCF service,这在Ajax的应用中应该有这样的需求,那么你就不能以现有的Filter方式找到所需的EndpointDispather,这时候,你需要一个特殊的AddressFilter,一个基于解析query string的filter.
3 、自定义MessageInspector(Step 9)
在client端和service端都有自己的MessageInspector,client端叫做ClientMessageInspector,实现System.ServiceModel.Dispatcher.IClientMessageInspector interface,而service端叫做 DispatchMessageInspector, 实现了System.ServiceModel.Dispatcher.IDispatchMessageInspector interface。DispatchMessageInspector允许你在request message交付到具体的DispatchOperation付诸执行之前或者reply message返回client之前对incoming message/outgoing message进行检验、修改或者其他一些基于message的操作;
IDispatchMessageInspector的定义很简单:
public interface IDispatchMessageInspector
{
// Methods
object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
void BeforeSendReply(ref Message reply, object correlationState);
}
在实际的项目开发中,MessageInspector使用相当广范,比如:我们可以定义自己的MessageInspector实现Logging功能;或者在Client端通过ClientMessageInspector添加一些与业务无关的context信息,并在service通过DispatchMessageInspector将其取出。在本系列后续的部分我将给你一个application context propagation的具体应用。
4、 自定义InstanceProvider(Step 10 & Step 11)
顾名思义,InstanceProvider就是用于创建或者提供service instance的。不过,除了提供service instance的创建者或者提供者的身份外,InstanceProvider还用于service instance的释放和回收。所有的IntanceProvider实现了System.ServiceModel.Dispatcher.IInstanceProvider interface:
public interface IInstanceProvider
{
// Methods
object GetInstance(InstanceContext instanceContext);
object GetInstance(InstanceContext instanceContext, Message message);
void ReleaseInstance(InstanceContext instanceContext, object instance);
}
如果InstanceProvider对应的DispatchOperation.ReleaseInstanceBeforeCall 为true的话,IntanceProvider将通过DispatchRuntime的InstanceProvide属性提取出来,通过调用ReleaseInstance()方法释放掉现有的service instance。
然后才会调用GetInstance方法,得到新的service instance. 基于自定义InstanceProvide的WCF extension也是比较常见的。比较有意义的一个应用是通过自定义InstanceProvider实现AOP。你可以将service instance创建过程中加入一些额外的特性实现method interception。比如: 通过自定义InstanceProvider,你将和容易实现Enterprise library PIAB(Policy Injection Application Block)/Unity Application Block和WCF的集成。我将在本系列后续的文章中介绍相关的实现。
5 、自定义CallContextInitializer (Step 12 & Step 18)
提到CallContextInitializer,我想有一部分人会马上想到System.Runtime.Remoting.Messaging.CallContext。CallContext为我们创建基于当前线程的Ambient context提供了便利。通过CallConext,我们和容易地将一些contextual information保存在TLS(Thread Local Storage)中。
类似地,DispatchOperation的CallContextInitializers提供了一个CallContextInitializer的集合,这些CallContextInitializer可以帮助我们对TLS进行初始化和释放回收的工作。比如在某个service 方法被真正之前,我们希望设置一些Context的数据,这些数据可能使业务有关,但大部分是和具体的业务逻辑没有关系的,比如一些Auditing的数据。在方式执行完成后,对这些context数据进行清理和回收。 WCF下的所有CallContextInitializer实现了System.ServiceModel.Dispatcher.ICallContextInitializer interface:
public interface ICallContextInitializer
{
// Methods
void AfterInvoke(object correlationState);
object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
}
再给大家介绍一个使用CallContextInitializer的场景。假设有一个service专门提供message,考虑到Localization,当客户访问你的service获取某项message entry的时候,你希望根据该client的当前culture/UI culture返回具体的message。但是你不希望将这个culture作为API的一部分。这样你就可以这样做:在client端,通过自定义ClientMessageInspector将当前的Culture附加到outgoing message的header中。在service端,通过一个自定义的CallContextInitializer,将culture的值从incoming message header取出,并用这个culture设置当前线程的Culture和UICulture。那么message的获取只需要考虑当前线程的Culture就可以了。我将在本系列后续的文章介绍这个应用。
6 、自定义MessageFormatter(Step 13 & Step 17)
对整个WCF infrastructure,我们可以将其分成两个世界,其中一个是基于message的世界;而另一个则是object的世界。对于前者来讲,所有的数据通过message进行封装,后者则同一个个具体的object来呈现。要实现具体的service功能,毫无疑问,需要调用具体的方法,传入具体的参数,而这些输入参数是一个个的对象,方法执行完成生成的结果也是一个个的对象。但是我们最初接受的request确实一个message,方法执行的参数也一XML InfoSet的形式封装在message中;我们最终生成的结果也不能直接以object的形式返回来client。所以我们需要一个这样的中介:将输入参数从message中提出,并转化成object;同是将返回值从object形式转化成message。这样的中介就是:MessageFormatter。
和MessageInspector一样,client端和service的Formatter是不同的。client端叫做ClientMessageFormatter ,实现了System.ServiceModel.Dispatcher.IClientMessageFormatter interface;service端叫做DispatchMessageFormatter, 实现了System.ServiceModel.Dispatcher.IDispatchMessageInspector interface:
public interface IDispatchMessageFormatter
{
// Methods
void DeserializeRequest(Message message, object[] parameters);
Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result);
}
public interface IClientMessageFormatter
{
// Methods
object DeserializeReply(Message message, object[] parameters);
Message SerializeRequest(MessageVersion messageVersion, object[] parameters);
}
运行时真正使用到的MessageFormatter通过ClientOperation和DispatchOperation的Formatter属性指定。WCF提供的MessageFormatter都是基于DataContract serializer或者XML serializer的。如何现有的这些不能满足你的需求,你完全可以创建自定义的MessageFormatter,然后通过behavior将其赋值到具体的ClientOperation和DispatchOperation上。
注:并非所有的情况下都需要MessageFormamter来帮助我们从事format的工作。我们知道我们的API可以是基于Message对象的,也就是说我们的输入可以使一个message,返回值也可以是一个具体的message。这种情况下,我们是不需要MessageFormatter的。WCF实际上是通过ClientOperation或者DispatchOperation的SerializeRequest/DeserializeReply和DeserializeRequest/SerializeReply属性来判断是否需要对参数或者返回值进行format的。
7、 自定义ParameterInspector(Step14 & Step 16)
Security有这样的一个原则:不能完全信任来自用户或者访问者的输入。就像Asp.NET通过一个个validator control来保证用户输入的合法性一样,WCF也需要有这样的机制。而这样的功能是通过ClientOperation或者DispatchOperation的ParameterInspectors集合实现的。
当执行具体的service method之前,会遍历DispatchOperation ParameterInspectors集合中的每个ParameterInspector,并调用BeforeCall对输入参数进行验证;而当service method被真正执行后,会生成返回值或者输出参数,在这个时候对ParameterInspectors的遍历再次进行,不果这次调用的是AfterCall方法,AfterCall方法旨在对返回值或者输出参数进行验证。
所有的ParameterInspector均实现了System.ServiceModel.Dispatcher.IParameterInspector interface。
public interface IParameterInspector
{
// Methods
void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState);
object BeforeCall(string operationName, object[] inputs);
}
注: 大家可能已经注意到了,BeforeCall有一个返回值。这个返回值得目的在于同AfterCall进行批评。在调用AfterCall是,这个返回值将会传入第三个参数:correlationState。
通过自定义ParameterInspector对WCF进行扩展的最典型的应用莫过于Enterprise Library Validation Application Block和WCF的集成,我将在本系列后续的文章对此作介绍。
8 、自定义ErrorHandler(Step19)
无论是对于具体的项目开发也好,还是对Framework的开发也罢,对异常、错误的处理都是必须的。通过ErrorHandler对象,你可以很容易地实现对异常的处理。ChannelDispatcher中将一个ErrorHandler的集合定义在ErrorHandlers属性中。当出现exception的时候,会遍历这个ErrorHandlers集合中的每个ErrorHandler。调用HandleError方法和ProvideFault方法。
所有的ErrorHandler都实现了System.ServiceModel.Dispatcher.IErrorHandler interface:
public interface IErrorHandler
{
// Methods
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
}
一般地,将一个exception handling相关的操作定义在HandleError方法中,比如:对记录exception日志;而ProvideFault则使你能够自由地提供你自定义的error, 比如为了防止敏感数据外泄,exception replace(用一个新的exception替换原来的exception);为了使client端进行统一的exception handling进行exception wrap(用一个新的exception对原来的exception进行包装,一般地讲原来的exception作为新的exception的inner exception)。
通过自定义ErrorHandler实现对WCF的扩展的典型应用莫过于Enterprise Library Exception Handling Application与WCF的集成。
WCF后续之旅:
WCF后续之旅(1): WCF是如何通过Binding进行通信的
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher
WCF后续之旅(4):WCF Extension Point 概览
WCF后续之旅(5): 通过WCF Extension实现Localization
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I]
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II]
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]
WCF后续之旅(14):TCP端口共享
WCF后续之旅(15): 逻辑地址和物理地址
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
WCF后续之旅(17):通过tcpTracer进行消息的路由