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。比如:SingletonPerSession模式下就直接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都定义了两个特殊的属性:AddressFilterContractFilter,它们的类型继承了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进行消息的路由

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏精讲JAVA

Java 虚拟机 2 : Java 内存区域及对象

为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念。

13220
来自专栏开发技术

spring-boot-2.0.3不一样系列之源码篇 - SpringApplication的run方法(一)之SpringApplicationRunListener,绝对有值得你看的地方

  Springboot启动源码系列还只写了一篇,已经过去一周,又到了每周一更的时间了(是不是很熟悉?),大家有没有很期待了?我会尽量保证启动源码系列每周一更,...

20220
来自专栏大内老A

WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?

服务端只有抛出FaultException异常才能被正常地序列化成Fault消息,并实现向客户端传播。对于一般的异常(比如执行Divide操作抛出的Divide...

21580
来自专栏企鹅号快讯

Java后台编程初学者,这些常识你都知道吗?

Java编程中的一些常识,希望有心学习的可以多看一眼,如果你是高手欢迎指点文中小编的不足,感谢支持。以下这些编程常识均由小编结合个人水平以及搜寻相关资料整理编辑...

209100
来自专栏微信公众号:Java团长

Java虚拟机:Java内存区域及对象

为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念。

13020
来自专栏wOw的Android小站

[设计模式]之十二:状态模式

在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一...

9510
来自专栏Java大联盟

Java面试手册:线程专题 ③

13310
来自专栏Linyb极客之路

Java虚拟机:Java内存区域及对象

为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念。

14020
来自专栏向治洪

如何编写入门级redis客户端

概述 Redis是开源的、基于内存的数据结构存储系统,可用作数据库、缓存以及消息代理方面。Redis支持许多种数据结构,并内置了丰富的诸如冗余、脚本、事务、持久...

22070
来自专栏玄魂工作室

看代码学安全(8 )preg_replace函数之命令执行

--------------------------------------------------------------------------------...

28730

扫码关注云+社区

领取腾讯云代金券