您的位置:首页 > 其它

WCF后续之旅(5): 通过WCF Extension实现Localization

2010-04-30 12:55 483 查看
在上一篇文章中, 我列出了WCF一系列的可扩展对象和元素,并简单介绍了他们各自的功能、适合的场景和具体解决的问题。从本篇开始我将通过一个个具体的例子来介绍如何利用这些扩展点对WCF进行扩展,从而解决一些我们在实现的项目开发中可能出现的问题。

今天,我们将讨论如何通过WCFextension实现多语言、本地化的功能。我们模拟这样的一个场景:我们现在有一个支持多语言的项目,假设通过支持英文(en-US)和简体中文(zh-CN)。我们需要创建一个service为整个系统提供message。对于这个messageservice,简单起见,我们将基于不同的culture的message存储于不同的Resource文件中,客户端通过访问service来获取基于它自己本地culture的message。比如,如果某一个客户端当前的culture是en-US,那么会得到英文的message,如果是zh-CN将会得到简体中文的message。

我们很多人会说,在获取message的时候将client端本地的culture作为API的参数传递到service端,service再根据相应的culture从对应的resource文件中获取message不就可以了吗?这样做不是不可以,但是不过优雅。从业务逻辑和非业务逻辑的分离来讲是不是一个好的解决方案,因为从某种意义上讲,culture信息是业务无关的,不适合作为API的一部分,API应该只和具体的业务逻辑相关联。

今天给出的解决方式基于这样的实现原理:在Client端,当调用我们的messageservice的时候,当前culture被自动放到message header里传到service端;在service端,该culture信息自动地被取出,并将service端的当前线程的UIculture设置成该值,那么service只需要根据当前线程的culture去取message就可以了。此外考虑到我们改变线程culture可能带来的不可预知的影响,在方法执行完毕将culture重置。

在这里我们先来实现service端的功能:如何从message header中取出culture,并设置当前线程culture。至于Client端的实现,我们将在另一个场景中进行单独介绍。

如何看过前一篇文章的朋友,也许会记得,在列出的8大dispatchingsystem可扩展对象中,有一个对象很适合我们今天的多语言的场景:CallContextInitializer。顾名思义,CallContext表示基于当前线程的关于Call stack的上下文信息,这样的信息本存放在TLS(Thread LocalStorage)中。CallContextInitializer就是用于去初始化这些context的。实际上,除了callcontext的初始化工作之外,CallContextInitializer还可以用于call context的清理工作。

1、Message Service

在正式介绍CallContextInitializer之前,我们闲来介绍一下我们的message service。对于message service的模拟,我们仍然采用我们传统的4层结构:Contract、Service、Hosting和Client。

对于Contract,仅仅是下面一个简单的interface:

namespace Artech.Messages.Contract
namespace Artech.Messages.Service
namespace Artech.Messages.Hosting
<configuration>
<system.serviceModel>
<services>
<service name="Artech.Messages.Service.MessageService">
<endpoint binding="basicHttpBinding" contract="Artech.Messages.Contract.IMessage" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/messageservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

这是Client端的Configuration:

<configuration>
<system.serviceModel>
<client>
<endpoint address="http://127.0.0.1/messageservice" binding="basicHttpBinding"
contract="Artech.Messages.Contract.IMessage" name="messageservice" />
</client>
</system.serviceModel>
</configuration>
以及Client端的Code:

namespace Artech.Messages.Client

在这里做一些简单的介绍:通过ChannelFactory创建Channel(proxy)对象,利用此Channel(proxy)创建OperationContextScope(OperationContextScope和OperationContext的关系就如同TransactionScope和Transaction的关系一样,相当于定义OperationContext的作用范围)。在此OperationContextScope作用范围内创建MessageHeader,内容为当前线程的UICulture,Localname和Namespace为定义的常量。将messageheader放到OutgoingMessageHeaders集合中,通过proxy对象调用messageservice获得对应的service,由于第一次调用使用的是默认的culture(en-US),我们希望返回的结果是英文,而第二次service invocation的culture为zh-CN,所以我们希望返回的结果是中文。

2、创建CallContextInitializer

为了实现Localization,我们先创建一个CallContextInitializer,我们姑且叫它CultureSettingCallContextInitializer。所有的CallContextInitializer都实现接口:System.ServiceModel.Dispatcher.ICallContextInitializer。

public interface ICallContextInitializer

该结构定义了两个方法:BeforeInvoke和AfterInvoke,允许你在真正的service方法执行前和执行后对CallContext进行初始化和清理。如何你希望在BeforeInvoke创建的对象能够被AfterInvoke,你可以将该对象作为BeforeInvoke 的返回值,在执行AfterInvoke的时候,该值将作为其中的参数。

这是CultureSettingCallContextInitializer的定义:

namespace Artech.CallContextInitializers
namespace Artech.CallContextInitializers

实际上只有一句有意思的code,将CultureSettingCallContextInitializer对象设置到应用了OperationBehavior

对应的DispatchOperation对象上:dispatchOperation.CallContextInitializers.Add(new CultureSettingCallContextInitializer());

那么我们就可以将此CultureSettingBehaviorAttribute直接应用到Contract的Operation上、或者Service的对应的method上。比如我们应用到Contract的GetMessage上,那么它将影响到所有实现了该contract的所有service。

namespace Artech.Messages.Contract

这时候我们运行程序,将会得到如何的输出:

namespace Artech.CallContextInitializers

有效的代码也是在ApplyDispatchBehavior中,我们通过遍历endpointDispatcher.DispatchRuntime.Operations集合,将每个DispatchOperation的CallContextInitializers中加上我们的CultureSettingCallContextInitializer对象。

EndpointBehavior只能通过Configuration的方式使用,我们还需要为此创建一个BehaviorExtensionElement:CultureSettingBehaviorElement。

namespace Artech.CallContextInitializers

那么我们就可以根据配置文件来应用我们的自定义的EndpointBehavior了:

<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="cultureSettingBehavior">
<cultureSettingElement />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="cultureSettingElement" type="Artech.CallContextInitializers.CultureSettingBehaviorElement, Artech.CallContextInitializers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="Artech.Messages.Service.MessageService">
<endpoint behaviorConfiguration="cultureSettingBehavior" binding="basicHttpBinding"
contract="Artech.Messages.Contract.IMessage" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/messageservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

在contract中,将CultureSettingBehaviorAttribute去掉,我们一样可以得到同上面一样的输出结果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐