您的位置:首页 > 其它

WCF后续之旅(6): 通过WCF Extension实现Context信息的传递

2010-04-30 12:56 597 查看
在上一篇文章中,我们讨论了如何通过CallContextInitializer实现Localization的例子,具体的做法是将client端的culture通过SOAPheader传到service端,然后通过自定义的CallContextInitializer设置当前方法执行的线程culture。在client端,当前culture信息是通过OperationContext.Current.OutgoingMessageHeaders手工至于SOAPHeader中的。实际上,我们可以通过基于WCF的另一个可扩展对象来实现这段逻辑,这个可扩展对象就是MessageInspector。我们今天来讨论MessageInspector应用的另外一个场景:如何通过MessageInspector来传递Context信息。

1. Ambient Context

在一个多层结构的应用中,我们需要传递一些上下文的信息在各层之间传递,比如:为了进行Audit,需要传递一些当前当前userprofile的一些信息。在一些分布式的环境中也可能遇到context信息从client到server的传递。如何实现这种形式的Context信息的传递呢?我们有两种方案:

一、将Context作为参数传递:将context作为API的一部分,context的提供者在调用context接收者的API的时候显式地设置这些Context信息,context的接收者则直接通过参数将context取出。这虽然能够解决问题,但决不是一个好的解决方案,因为API应该只和具体的业务逻辑有关,而context一般是与非业务逻辑服务的,比如Audit、Logging等等。此外,将context纳入API作为其一部分,将降低API的稳定性,比如,今天只需要当前user所在组织的信息,明天可能需求获取当前客户端的IP地址,你的API可以会经常变动,这显然是不允许的。

二、创建Ambient Context来保存这些context信息,AmbientContext可以在不同的层次之间、甚至是分布式环境中每个节点之间共享或者传递。比如在ASP.NET应用中,我们通过SessionSate来存储当前Session的信息;通过HttpContext来存储当前Httprequest的信息。在非Web应用中,我们通过CallContext将context信息存储在TLS(Thread LocalStorage)中,当前线程下执行的所有代码都可以访问并设置这些context数据。

2、Application Context

介于上面所述,我创建一个名为Application Context的AmbientContext容器,Application Context实际上是一个dictionary对象,通过key-valuepair进行context元素的设置,通过key获取相对应的context元素。ApplicationContext通过CallContext实现,定义很简单:

namespace Artech.ContextPropagation
public static ApplicationContext Current
namespace Artech.ContextPropagation

一般地,我们仅仅需要Context的单向传递,也就是从client端向service端传递,而不需要从service端向client端传递。不过回来应付将来潜在的需求,也许可能需要这样的功能:context从client端传向service端,service对其进行修改后需要将其返回到client端。为此,我们家了一个属性:IsBidirectional表明是否支持双向传递。

在BeforeSendRequest,我们将ApplicationContext.Current封装成一个MessageHeader, 并将此MessageHeader添加到request message 的header集合中,localname和namespace采用的是定义在ApplicationContext中常量:

public object BeforeSendRequest(ref Message request, IClientChannel channel)

如何支持context的双向传递,我们在AfterReceiveReply负责从reply message中接收从service传回的context,并将其设置成当前的context:

public void AfterReceiveReply(ref Message reply, object correlationState)

4、通过ContextInitializer实现对Context的接收

上面我们介绍了在client端通过ClientMessageInspector将context信息存储到request messageheader中,照理说我们通过可以通过DispatchMessageInspector实现对context信息的提取,但是考虑到我们设置context是通过CallContext来实现了,我们最好还是使用CallContextInitializer来做比较好一些。CallContextInitializer的定义,我们在上面一章已经作了详细的介绍了,在这里就不用多说什么了。

namespace Artech.ContextPropagation
namespace Artech.ContextPropagation

在ApplyClientBehavior中,创建我们的ContextAttachingMessageInspector对象,并将其放置到ClientRuntime的MessageInspectors集合中;在ApplyDispatchBehavior,将ContextReceivalCallContextInitializer对象放到每个DispatchOperation的CallContextInitializers集合中。

因为我们需要通过配置的方式来使用我们的ContextPropagationBehavior,我们还需要定义对应的BehaviorExtensionElement:

namespace Artech.ContextPropagation

我们IsBidirectional则可以通过配置的方式来指定。

6. Context Propagation的运用

我们现在将上面创建的对象应用到真正的WCF调用环境中。我们依然创建我们经典的4层结构:

namespace Artech.ContextPropagation.Contract
namespace Artech.ContextPropagation.Services

打印出ApplicationContext.Current.Count 的值,并加1。

Hosting的Config:

<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="contextPropagationBehavior">
<contextPropagationElement isBidirectional="true" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://127.0.0.1/service" behaviorConfiguration="contextPropagationBehavior"
binding="basicHttpBinding" contract="Artech.ContextPropagation.Contract.IContract"
name="service" />
</client>
<extensions>
<behaviorExtensions>
<add name="contextPropagationElement" type="Artech.ContextPropagation.ContextPropagationBehaviorElement, Artech.ContextPropagation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
</configuration>

Artech.ContextPropagation.Client

namespace Artech.ContextPropagation.Client

以及config:

<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="contextPropagationBehavior">
<contextPropagationElement isBidirectional="true" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://127.0.0.1/service" behaviorConfiguration="contextPropagationBehavior"
binding="basicHttpBinding" contract="Artech.ContextPropagation.Contract.IContract"
name="service" />
</client>
<extensions>
<behaviorExtensions>
<add name="contextPropagationElement" type="Artech.ContextPropagation.ContextPropagationBehaviorElement, Artech.ContextPropagation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
</system.serviceModel>
</configuration>

我们运行整个程序,你将会看到如下的输出结果:





可见,Context被成功传播到service端。再看看client端的输出:





由此可见,在service端设置的context的值也成功返回到client端,真正实现了双向传递。

P.S:SOA主张Stateless的service,也就是说每次调用service都应该是相互独立的。context的传递实际上却是让每次访问有了状态,这实际上是违背了SOA的原则。所以,如何对于真正的SOA的设计与架构,个人觉得这种方式是不值得推荐的。但是,如何你仅仅是将WCF作为传统的分布式手段,那么这可能会给你的应用带了很大的便利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐