WCF开发指南(自编)

 

 

  WCF程序

Microsoft提出.NET战略以来, 先后推出了一系列产品和技术, 这些产品和技术为我们在.NET平台下建立企业级的分布式应用提供了很大的 便利。这些技术和产品包括:.NET  RemotingXML WebSerivceWSE(2.0,3.0)Enterprise Service MSMQ ......

我们知道,和一个相对独立的应用不同,我们开发一个分布式应用, 尤其是开发一个企业级的分布式应用, 我们需要考虑较多的东西。比如我们要考虑数据在不同的应用之间传递时采取什么样的机制, 这种数据传递是否是安全的,可靠的;如何在分布式的环境下进行异常处理;如何把分别在 不同应用中执行的操作纳入同一个事务……

对于我们上面提到的这些 问题, 这些都是开发分布式应用考虑的典型的问题。值得庆幸的是,Microsoft开发的分布式的产品能够部分的解决这些问题。.NET Remoting 为我们在.NET平台下提供了非常好的解决方案(我个人认为,.NET Remoting.NET平台下最为成熟的分布式技术。比如相较于另一个使用更为广泛的技术XML Web Service,它具有一些自己独特的特性:可以使用不同的传输层协议进行通信——Http & TCP;可以使用不同的消息编码方式——Bianry & Text XML);可以寄宿到IIS和任何一种托管的应用下 ——Console Application WinForm Application Windows Service……Server端可以通过双向通信回调(Callback)客户端的操作;……XML Web Service为使我们实现跨平台的系统能够集成显得如此简单。随着技术的不断发展,相关的技术规范(WS-* Specification)不断完善, XML Web Service现在已经成为使用最为广泛的分布式技术了。XML Web Service能够得到如此广泛的推广,这得得益于Microsoft先后两次推出的Web Service Enhancement WSE 2.0 WSE 3.0)。如果没有WSE, 单纯的asmx下的如此的担保和不可靠。WSEWeb Service解决了几大难题:SecurityReliable Messagingtransaction Handling以及大数据的有效传输。 MSMQ作为一种简单而有效的机制为不同应用之间数据的传递提供了保障。

其实,通过合理利用上面这些分布式的技术完全可以为我们建立的一套适合不同层次需要的分布式构架。但这里面仍然存在一些问题,那就是上面这些技术和产品只能解决某一方面的问题; 比如.NET Remoting虽然在.NET平台下是一个很好的依靠, 但是考虑到他不能提供不同平台之间的互操作性。另外,这些技术适合用了完全不同的编程方式,使得我们很难从容地从其中一种转移到另一种上来。基于这些原因, 我们需要一套全新的技术整合以上都这些技术, 于是我们有了今天的WCF——Windows Communication FoundationWCF建立一套框架,是我们通过一致的编程模式,使用不同的技术构建我们的分布式应用。 

虽然很早开始接触WCF,但所学的总是零零碎碎。现在开始系统地研究WCF,希望与大家一同分享我的一些所得, 同时希望能通过这样的一个机会与大家一些探讨WCF,不对的地方希望大家指正。

一开始我们先建立一个简单程序看WCF如何工作:

1.1立整个应用的简单构架 smalples(WCF"Smaples"Artech.WCFService)

整个构架如图所示,这个Solution5Project组成:Artech.WCFService.Contract;  Artech.WCFService.ServiceArtech.WCFService.HostingArtech.WCFService.Clienthttp://localhost/WCFService

Artech.WCFService.Contract: Class Library Project,用来保存ContractService ContactMessage ContractData Contract), 之所以把Contract独立出来的原因是考虑到他同时被Server——Service本身和Service HostingClient端使用。(现在很多的参考书,包括MSDN都使用ServiceModel Metadata Utility Tool (Svcutil.exe)这样的一个工具来访问ServiceMetadata Endpoint来生成我们的客户段代码,这些代码就包括Service Contract(一般是一个Interface),实现了这个ContractProxy Class(一个集成自System.ServiceModel.CientBase的一个Class)和相应的Configuration。 这个工具确实给我提供了很大的方便。但我不推荐使用这样的方法(我天生不倾向对于这些代码生成器),因为我觉得, 在Contract可得的情况下-比如ServiceClient都是自己开发,让ServiceClient实现的Contract是同一个Contract能够保证一致性。这个Project引用System.ServiceModel DLL

Artech.WCFService.ServiceClass Library ProjectService的业务逻辑, 这个Project引用Artech.WCFService.Contract ProjectSystem.ServiceModel DLL

Artech.WCFService.HostingConsole Application, 用于以Self-Hosting的方式Host Service。这个Project引用Artech.WCFService.ContractArtech. Project WCFService.ServiceProjectSystem.ServiceModel DLL

Artech.WCFService.ClientConsole Application, 用以模拟现实中的调用Service的Clinet。这个Project引用Artech.WCFService.Contract Project System.ServiceModel DLL

 http://localhost/WCFService: Web Site Project, 用于模拟如何把Service HostIIS中。这个Project引用Artech.WCFService.ContractArtech.WCFService.ServiceSystem.ServiceModel DLL

 

1.2建Service Contract

在这个例子中我们建立一个简单的案例,做一个计算器, 假设我们只要求他做简单的加法运算就可以了。在Artech.WCFService.Contract添加一个interface,名称叫做ICalculator

使一个Interface成为Service Contract的方法很简单,就是把ServiceContractAttribute应用到这个interface上,并在代表单个Operation的方法上应用OperationContractAttribute。这个使用Custom Attribute的编程模式被称为声明式的编程(Declarative)方式, 他在.NET Framework 1.1以前用到的地方还不是太多,在.NET Framework 2.0, 尤其是NET Framework 3.0中,这种方式已经变得随处可见了。

我们可以把Contract定义成一个Interface,也可以把它定义到一个Class——这个Class中既包涵Service本身又作为一个Contract而存在。但我推荐使用第一种方法——SeriveContract相分离。

WCF中,Contract的功能实际上就定义一个Service包含哪些可用的Operation, 以及的每个Opertaion的方法签名。从消息交换(Message Exchange)的角度讲,Contract定义了调用相应的Serive采取的消息交换的模式(Message Exchange Pattern - MEP),我们经常使用的MEP包括三种:Oneway, Request/Response,和Duplex。因为调用Service的过程实际就是消息交换的过程, 以常见的Request/Response为例。Client调用某个方面远程访问Service,所有的输入参数被封装到Request Soap Message并被发送到Service端, Service端监听到这个Soap Request,创建相应的Service Object并调用相关的操作,最后将Result(这可以是Return ValueReference ParameterOutput Parameter)封装成Soap Message发送回Client端。这里需要注意,如果采用的是Request/Response的模式,即使相应的操作没有Return ValueReference ParameterOutput Parameter(它被标记为void),Service仍然会返回一个空的Soap MessageClient端。

1.3 创建Service

前面我们已经创建了我的Artech.WCFService.Contract。其实我们从Contract这个单词上讲, 它就是一种契约,一种承诺。 他表明在上面签了字你就的履行Contract上义务。Service就是这样一个需要履行Contract义务的人。在这个例子中, ContractInterface的方式定义的一些Operation。作为Service, 在Contract上签字的方式就是实现这样的一个Interface。 下面的Service得到code, 很简单。

1.4Hosting Service

就像Remoting一样,我们继承自System.MarshalByRefObject 的对象必须Host到某一个运行的进程中, 他才开始监听来自Client端的请求,当Client才能通过Proxy远程的调用,Remoting Infrastructure监听到来自Client端的请求,他会激活相应的remote Object(我们只考虑Server Activate Object——SAO)。实际上对于WCF Service也需要一个Host环境才有其发挥作用的舞台。就像Remoting一样,你可以使用任何一种Managed Application——Console ApplicationWinForm ApplicationASP.NET Application——作为它的Host环境。 你甚至可以用它HostWindows Service中和IIS中(后面我将会讲到如何做)。

我们知道WCF中,Client端和Service端是通过Endpoint来通信的,Endpoint有包含3个部分,经典地称为ABC.

A代表Address,它包含一个URI,它指明Service存在于网络的某个地方,也就是说它为Client断指明在什么地方去找到这个Service。很多人认识Address仅仅只是一个具有IdentityURI,实际上不然, Address不止于此, 它还包含一些Header,这些信息在某些情况下对于如何寻址有很大的意义(比如在client的最终Service之间还有一些Intermediary节点的情况下)。 在.NET中, AddressSystem.ServiceModel.EndpointAddress 来表示。

B代表BindingBinding封装了所有ClientService段消息交换的通信细节。比如他定义了通信应该采用的Transport-比如我们是因该采用Http, TCPNamed Pipe或者是MSMQ;通信的内容应该采取怎样的编码——比如是Text/XMLBinary还是MTOM。以上这些都得一个Binding所必须定义的内容, 此外,Binding 还可以定义一些其他的有关通信的内容, 比如SecurityReliable Messaging, Session, Transaction等等。正因为Binding对于通信的重要性,只有Service端的BindingClientBinding相互匹配的时候,他们之间在可以相互通信。如何使Client Binding 匹配Service Binding呢?最简单也是最直接的方法就是使用相同的BindingWCF为我们定义了一系列的System Defined Binding,这些BindingTransportInteroperabilitySecuritySession Support,以及Transaction Support方面各有侧重。基本上WCF为我们定义的这些Binding 已经够我们使用的了,如果,实在满足不了要求, 你还可以建立自己的Custom Binding

C 代表Contract这在上面已经提及,这里不再累赘。

Host的本质就是把一个Service 置于一个运行中的进程中,并以Endpoint的形式暴露出来,并开始监听来自Client端的请求。这里值得注意的是,同一个Service可以注册若干不同的Endpoint,这样不同的Client就可以以不同的方式来访问同一个Service.比如,同一个IntranetClient可以以TCP的方式访问Service,另一个存在已Internet中的Client则只能以Http的方式访问。

1.5.创建Client

到现在为止,Service端的工作已经完成,当你启动Hosting的时候,一个可用的Service就已经存在了。现在所做的事情是如何创建我们的客户段程序去使用这个Service。几乎所有的WCF的书,其中包括MSDN都是叫你如何使用Service Utility这样的一个工具来帮你生成客户端代码和配置信息。为了让我们能够清晰的Client的整体内容, 我们现在选择手工的方式来编写这样的部分代码。

几乎所有分布式的调用都有这样的一个概念,调用的具体实现被封装在Server端,Client不可能也不需要了解这个具体实现,它所关心的就是我如何去调用,也就是说Cient需要的不是Service的实现,而是一个interfaceWCF也是一样,Client不需要了解Service的具体实现,它只需要获得Service Contract,已经如何与Service通信就足够了。说到Contract和通信,我们很自然地会想到Endpoint,不错,Endpoint恰恰给我们提供这两个方面的内容。所以到现在我们可以这样说,这样在Client建立和Serivce端相匹配的EndpointClient就可以调用它所希望的Service。前面提到Endpoint包括三个部分, Address BindingContract那我们现在来看看Client如何获得这3要素的信息。

SystemServiceModel 命名空间里,定义了一个类abstract class ClientBase<TChannel>,给我们调用Service提供极大的便利。我们只要是我们的Client继承这样一个类,并为它指定Endpoint的三要素就一切OK了,下面我们来看看我们可以以那些方式来指定这些内容

1 Conract:我们看到了ClientBase是一个Generic的类,我们在创建一个继承自这个类的时候必须给它指定特定的TChannel.我们可以把Contract对应的类型作为Clientgeneric类型。

2 BindingAddress:和Service端的Endpoint一样,我们可以把相关的信息放在我们的Client端代码里面,也可以放在ClientConfig里面。那个这些数据如何应用要我们创建的派生自ClientBase的类的对象上呢。其实很简单,ClientBase给我们定义了若干重载的构造函数,我们只要定义我们相应的构造函数应简单地调用基类的构造函数。下面列出了ClientBase定义的全部的构造函数

protected ClientBase():这个构造函数没有任何的参数,它用于Endpoint的信息全部存放于Config

protected ClientBase(InstanceContext callbackInstance):指定一个Callback instance用于Service回调Client代码,这用Deplex Communication

protected ClientBase(string EndpointConfigurationName):指定一个ID,它标识configuration 文件中定义的某一个Endpoint。这个方法在使用不同的Endpoint调用同一个Service的情况下用到的比较多。

ClientBase(Binding Binding, EndpointAddress remoteAddress);显示的指定Binding Address

ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName)

ClientBase(string EndpointConfigurationName, EndpointAddress remoteAddress)

ClientBase(string EndpointConfigurationName, string remoteAddress)
ClientBase(InstanceContext callbackInstance, Binding Binding, EndpointAddress remoteAddress)

ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, EndpointAddress remoteAddress)

ClientBase(InstanceContext callbackInstance, string EndpointConfigurationName, string remoteAddress)

 

 

2):Endpoint Overview

 

WCF实际上是构建了一个框架,这个框架实现了在互联系统中各个Application之间如何通信。使得DevelopersArchitect在构建分布式系统中,无需在考虑如何去实现通信相关的问题,更加关注与系统的业务逻辑本身。而在WCF Infrastructure中,各个Application之间的通信是由Endpoint来实现的。

2.1 Endpoint的结构

Endpoint包含以下4个对象:

Address: Address通过一个URI唯一地标识一个Endpoint,并告诉潜在的WCF service的调用者如何找到这个Endpoint。所以Address解决了Where to locate the WCF Service

Binding: Binding实现在ClientService通信的所有底层细节。比如ClientService之间传递的Message是如何编码的——text/XML, binaryMTOM;这种Message的传递是采用的哪种Transport——TCP, Http, Named Pipe, MSMQ; 以及采用怎样的机制解决Secure Messaging的问题——SSLMessage Level Security。所以Binding解决的是How to communicate with service

Contract: Contract的主要的作用是暴露某个WCF Service所提供的所有有效的Functionality。从Message Exchange的层面上讲,Contract实际上是抱每个Operation转化成为相对应的Message Exchange Pattern——MEPRequest/Response One-way Duplex)。所以Contract解决的是What functionalities do the Service provide

Behavior: Behavior的主要作用是定制Endpoint在运行时的一些必要的Behavior。比如Service 回调ClientTimeoutClient采用的Credential type;以及是否支持Transaction等。

当我们Host一个WCF Service的时候,我们必须给他定义一个或多个Endpoint,然后service通过这个定义的Endpoint进行监听来自Client端的请求。当我们的Application需要调用这个Service的时候,因为Client Service是通过Endpoint的进行通信的, 所以我们必须为我们的Application定义Client端的Endpoint。只有当ClientEndpointService端某个Endpoint相互匹配(Service端可以为一个Service定义多个Endpoint),Client端的请求才能被Service端监听到。也就是说,我们只有在Client具有一个与Service端完全匹配的Endpoint,我们才能调用这个Service。而这种匹配是比较严格的,比如从匹配Address方面,Client端和Service端的Endpoint Address不仅仅在URI上要完全匹配Service 他们的Headers也需要相互匹配。对于Binding, 一般地,Client需要有一个与Service端完全一样的Binding,他们之间才能通信。 

2.2 Sample (WCF"Smaples"Artech.WCFService)

首先给一个Sample,以便我们对在WCF Service Aplication中如何定义Endpoint有一个感性的认识。整个Solution的结构参照下图

 

 

2.3 如何在Application中定义Endpoint

对于Self-HostService,绝大部分的Endpoint相关的信息都具有两种定义方式——Managed Code Configuration。而对于把Service HostIIS中的情况, Endpoint的信息一般虚拟根目录下的Web.Config中定义。一般的我们我们不推荐使用代码的方式Host和调用Service,这主要是基于以下的理由。首先我们开发的环境往往与部署的环境不尽相同,才用configuration的方式是的我们可以在部署的时候通过修改配置文件以适应新的需要。其次,对于不要出现的新的需要,比如对于一个原来只供Internet内部使用的Service,我们一般会定义一个基于TCPEndpoint,现在出现来自于Internet的潜在用户,我们只需要通过修改Config文件的方式为这个Service添加一个新的基于HttpEndpoint就可以了。 Endpoint的信息写在config文件中的优势在于,修改config文件的内容是不需要重新编译和重新部署的。相应的定义方式清参照以上的Sample

首先我们创建一个ServiceHost对象calculatorSerivceHost,同时指定对用的Service Type 信息(typeof(GeneralCalculatorService))。WCF Infrastructure为在配置文件在Services Section寻找是否有相对用的service定义。在这个例子中,他会找到一个name属性为Artech.WCFService.Service.GeneralCalculatorServiceService。然后他会根据定义在Service中的Endpoint定义为calculatorSerivceHost添加相应的Endpoint 如果有对应的Endpoint Behavior设置存在于配置文件中,这些Behavior也会设置到改Endpoint中。最后调用Open方法,calculatorSerivceHost开始监听来自Client端的请求。

2.4 Address

每一个Endpoint都必须有一个AddressAddress定位和唯一标志一个Endpoint。在Managed code 中,AddressSystem.ServiceModel.EndpointAddress对象来表示。下面是一个Adress的结构:

URI:指定的EndpointLocationURI对于Endpoint是必须的。

Identity:当另一个Endpoint与此Endpoint进行消息交互时,可以获取该IdentityAuthenticate正在与之进行消息交互的Endpoint是否是它所希望的。Identity对于endpoint是可选的。

HeadersAddress可以包含一些可选的Headers 这些header最终会加到相应的Soap MessageHeader中。Header存放的多为Address相关的信息,用于进行Addressing Filter 

Address的主要作用就是同过UriService提供一个监听Address。但在某些特殊的场景中,我们可以借助AddressHeaders提供一些扩展的功能。在大多数的情况下Client可以直接访问Service,换句话说,如果我们把Message 传递的路径看成是以系列连续的节点(Node)的话,Message直接从Client所在的节点(Node)传递到最终的Service的节点。但在某些情况下,考虑的实现负载平衡,安全验证等因素,我们需要在Client和最终的Service之间加入一些中间节点(Intermediaries),这些中间节点可以在Message到达最终Service Node之前作一些工作,比如为了实现负载平衡,它可以把Message Request分流到不同的节点——Routing;为了在Message达到最终Node之前,验证Client是否是一个合法的请求,他可以根据Message存储的Credential的信息验证该请求——Authentication
这些Intermediaries操作的一般不会是Message Body的内容(大多数情况下他们已经被加密),而是Message Header内容。他们可以修改Header的内容,也可以加入一些新的Header。所以为了实现Routing,我们需要在Message加入一些Addressing相关的内容,为了实现Authentication我们需要加入Client Credential的信息, 而这些信息都放在Header中。实际上你可以把很多内容加到Header中。
我们可以通过config文件加入这些Header

2.5 Binding

WCF,顾名思义就是实现了分布式系统中各Application之间的Communication的问题。上面我们说过, ClientService之间的通信完全有他们各自的Endpoint的担当。Address解决了寻址的问题,通过AddressClient知道在哪里可以找到它所希望的Service。但是知道了地址,只是实现的通信的第一步。

对于一个基于SOA的分布式系统来说,各Application之间的通信是通过Message Exchange来实现的。如何实现在各个Application之间 进行Message的交互,首先需要考虑的是采用怎样的Transport,是采用Http呢,还是采用TCP或是其他,比如Named PipeMSMQ。其次需要考虑的是Message应该采取怎样的编码,是text/XML呢,还是Binary,或是MTOM;此外,对于一个企业级的分布式应用,SecurityRobustness是我们必须考虑的问题——我们应该采用Transport LevelSecuritySSL)还是Message LevelSecurity;如何确保我们的Message的传递是可靠的(Reliable Messaging; 如何把在各application中执行的操作纳入同一个事物中(Transaction)。而这一些都是Binding需要解决的问题。所以我们可以说Binding实现了ClientService通信的所有底层细节。

WCF中,Binding一个Binding Element的集合,每个Binding Element解决Communication的某一个方面。所有的Binding Element大体上可以分为以下3类:

1 Transport Binding Element:实现CommunicationTransport选取,每个Binding必须包含一格Transport Element

2 Encoding Binding Element:解决传递数据的编码的问题,每个Binding必须包含一个Encoding Element,一般由Transport Binding Element来提供。

3 Protocol Binding Element:解决SecurityReliable MessagingTransaction的问题。

下边这个表格列出了Binding中的各个层次结构。

Layer

Options

Required

Transactions

TransactionFlowBindingElement

No

Reliability

ReliableSessionBindingElement

No

Security

SecurityBindingElement

No

Encoding

Text, Binary, MTOM, Custom

Yes

Transport

TCP, Named Pipes, HTTP, HTTPS, MSMQ, Custom

 

 

3):在WCF中实现双向通信(Bi-directional Communication

 

昨天写了一篇Remoting中如何实现双向通信的文章《[原创].NET Remoting: 如何通过Remoting实现双向通信(Bidirectional Communication) 》,作为对比,今天我们来讨论一下WCF的双向通信。

为了使我们能够更好地对比双向通信在Remoting中和WCF中的实现,我们的Sample采用一样的业务逻辑——调用一个数学计算的远程调用,除了传递相应的操作数之外,我们还传递一个对象,这个对象可以在Server端中回调 (Callback) 把运算结果在Client端显示出来。
可以通过下面的URL下载源代码:

构建整个Solution的整体构架。

Smaples (WCF"Smaples"Artech.WCFService)

 

1.在一个分布式的环境中,Client能够调用Service,它必须知道ServiceContract Contract定义了Service暴露给外界的所有可用的Operation,以及这些Operation的签名(Signature.至于Service中定义的Opertion采用怎样的实现,Client不需要了解。这也是在WCF中把Service Contract与具体的Service Implementation相互分离的一个重要原因——我们把Contract单独提取出来,把他暴露给Client,从而可以避免把过多的暴露业务逻辑的实现。

2.在一个分布式的环境中,Serer端和Client并不是一成不变的,他们是可以相互转化的。提供服务的就是Server,消费Service的就是Client。在这个例子中,当Artech.WCFService.Client调用HostArtech.WCFService.Hosting中的DuplexCalculatorService(定义在 Artech.WCFService.Service中),Artech.WCFService.ClientClient,而Server端的执行环境是Artech.WCFService.Hosting。而当Calculator Service回调(CallbackClient的逻辑把运算结果显示出来时候,因为Callback的逻辑是在Artech.WCFService.Client中执行的,所以Artech.WCFService.Client成了Server,而CalculatorCallbackHandler(定义在 Artech.WCFService.Client中)成了真正的Service

3.我们已经说过Client能够调用Service,它必须知道ServiceContract。所以DuplexCalculatorService能过Callback Artech.WCFService.Client,它也必须知道回调操作的ContractWCF通过在ServiceContractAttribute中的CallbackContrac参数在制定。

这里有以下几点需要注意的:

1 必须把并发模式ConcurrencyMode设置为ConcurencyMode. Reentrant 或者ConcurencyMode.Multiple。要弄清种种的原因,我们先来看看在本例中的具体的消息交互的情况(假设我们的调用Duplex Calculator Service 和回调都采用Request/ResponseMessage Excahnge Pattern,时间上一般这种情况我们应该采用One-wayME):


首先Client调用Duplex Calculator Service, Service RequestClientServiceService开始执行运算,运算完成后Callback Client将运算结构在Client端显示出来,这个过程中ServiceClient发送一个Callback Message,等Client完成Callback操作后,会向Service端发送一个Callback Response(实际上是一个空的Message——以为Callback操作没有返回值),Service收到Callback Response之后,会执行后续的操作,等所有的操作执行完毕,会发送Service Response(这里也是一个空的Message)到Client

现在我们 来看看为什么在建立Duplex Service的时候要把并发模式设为ConcurencyMode. Reentrant 或者ConcurencyMode.Multiple。在默认的并发模式下(ConcurencyMode.Single),WCF为了保证其线程安全性(Thread Safety),在整个调用Service的过程中,InstanceContext会被WCF锁住(Lock)。一本Sample为例,从ClientService发送Service Request 到手的Server发回的Service Resqonse,整个InstanceContext会在Server端被锁住, 由于在Client执行的Callback操作使用的是同一个InstanceContext 这样形成了一个死锁(DeadLock——Calculator Service必须等Callback操作完成之后才能执行后续的操作,而Callback操作必须等待InstanceContext被解锁(Unlock)才能执行,然而InstanceContext却被Calculator Service锁住。

ConcurencyModeConcurencyMode. Reentrant 或者ConcurencyMode.Multiple的时候。当Serivice向外调用某个操作(outgoing call)的时候,或者说在向外发送一个Outgoing Message的时候,WCF会解锁(UnlockInstanceContext。以本例为例,Service 回调Client的时候,原来被锁住的InstanceContext会被解锁。这样Callback操作就可以利用InstanceContext来执行。

2 Service可以通过OperationContext.Current.GetCallbackChannel<T>() 来或者Client在调用Calculator Service时指定的Callback Object。其中T一般被指定为Callback Contract对应的Type 

ICalculatorCallback callback = OperationContext.Current.GetCallbackChannel<ICalculatorCallback>();

这里有以下几点需要注意的:

1 在调用Duplex Calculator Service的时候,我们需要指定执行回调的Callback对象。在WCF中,Callback对象用一个InstanceContext对象来表示。而他在DuplexCalculatorClient的构造函数中指定。

 public DuplexCalculatorClient(InstanceContext callbackInstance)
            : base(callbackInstance)
         { }

2 Client调用Duplex Calculator ServiceService端需要注册相应的Channel来监听来自Client的请求。同理,Service回调ClientClient也需要相应的Channel来监听来自Service的回调。这个Channel通过下面的方式注册。   

    

<wsDualHttpBinding>
          <binding name="wsDualBinding_IDuplexCalculator" clientBaseAddress="http://localhost:6666/myClient/" />
</wsDualHttpBinding>

到现在为止我们已经完成了所有的Program,我们来运行一下。

1         运行Artech.DuplexRemoting.Hosting


2         运行Artech. WCFService.Client


Duplex Calculator  Service Host IIS

1         http://localhost/WCFService中添加于Artech.WCFService.ServiceDuplexCalculatorService相对应的SVC文件。

DuplexCalculatorService.svc

<%@ ServiceHost Language="C#" Debug="true" Service="Artech.WCFService.Service.DuplexCalculatorService" %>

这样我们可以不需要Hosting的情况下通过这样的Uri访问Duplex Calculator Servicehttp://localhost/Artech.WCFService/ DuplexCalculatorService.svc 

在不起用Hosting的情况下运行Artech.WCFService.Client,我们一样可以得到相同的结果

 

4WCF中的序列化(Serialization

 

4.1 SOA Message


Windows Communication Foundation (WCF) 是基于面向服务架构(Service Orientation Architecture——SOA)的一种理想的分布式技术(Distributed Technology, 相信在今后在建立基于SOA企业级别的解决方案和进行系统集成方面将会大有作为。一个基于SOA结构的互联系统(Connected System)通常由若干相互独立的子系统(Sub-System)组成,这些子系统可能一个独立的Application,也可能是由若干Application相互集成共同完成一组相关的任务的小系统。这些子系统以一种有效的方式组合、集成为我们听过一种具有综合功能的解决方案。

在一个基于SOA的分布式系统中,各个子系统相互独立又相互关联。说它们的相互独立是因为他们各个都是一个个自治的系统(Autonomous System),可以实行各自的版本策略和部署策略,而这种版本的部署上的变动通常不应该引起系统中其他部分的变动。说它们又彼此关联,则是因为一个子系统所需要的功能往往由其他某个子系统来实现,我们把实现功能的一方称为Service 的提供者(Provider),把使用Service的一方称为客户(Client)。Client依赖于Service的调用,而不失依赖于Service的实现,只要Service的调用方式没有发生改变,Service的实现变动对于Service的使用者来说是完全透明的。在WCF中,我们把Service的调用相关的提取出来即为我们经常说的ContractService的提供者和Client之间共享的是Service Contract——而不传统OO概念下的Type。把相对稳定的Service Contract和经常变动的Service Implementation相互分布早就了我们互联系统的松耦合性(Loosely Couple)。

前面我们简单介绍了SOA系统的基本特征——子系统之间的松耦合性(Loosely Couple);各个子系统的自治性(Autonomous);共享Contract。此外SOA还有其他的一些特征,最重要的一个特征就它是一个基于MessageMessage-Based)的系统。子系统之间的相互交互由Message来实现。ClientService的提供者发送一个Soap Message来访为他所需要的ServiceService的提供者监听到来自Client的请求,创建相应的Service对象,执行相关的操作,把执行的结果(Result)以Message 的形式发回给对应的Client。所以我们可以说子系统之间的相互交互本质上是一种消息的交互过程(Message Exchange)。不同的交互方式对应不同的Message Exchange Pattern——MEP

理解了SO的基本原理,我们来看看WCF,从WCF的全称来分析——Windows Communication Foundation,顾名思义,他就是解决分布式互联系统中各相互独立的子系统如何交互的问题,换句话说,它实际上就是提供 一个基础构架(Infrastructure)实现Application的通信问题。我们前边已经提到,各个子系统之间是通过XML Message进行交互的,所以我们可以 WCF看成是一个完全处理XML Message的构架,WCF的所有的功能都是围绕着Message来展开的——如何把一个Service的调用转或称一个Message ExchangeService Contract;如何实现一般的.NET对象和能够容纳于XML Message中的XML Infoset之间的转化(SerializationDeserialization);如何实现承载数据的XML Infoset和能够用于网络传递的字节流(Byte Stream)之间的相互转化(EncodingDeconding);如何保证置于Message中数据的一致性和防止被恶意用户窃取以及验证调用Service和通过Service的合法性(SecurityConfidentialityIntegrityAuthentication——CIA);如何保证Message被可靠地被传达到所需的地方(Reliable Messaging);以及如何把若干次Service调用——本质上是若干次Message Exchange纳入到一个单独的ConversationSession Support Transaction Support……

在分布式系统中,一个Application与另一个Application之间进行交互,必然需要携带数据。前面我们说了,系统交互完全是应Message的方式进行的,MessageXML,当然置于Message中的数据也应该是XMLXML Infoset)。如何处理这些交互的数据,我们可能首先想到的就是直接处理XML,我们可以在XML级别通过相关的XML技术——XSDXPathXSLT来操作数据。但是要使我们处理后的XML需要和要求的完全一致,这样的工作无疑是非常枯燥乏味而且费时费力的。而我们最擅长的就是使用.NET对象来封装我们的数据。如何使我们创造的对象能够有效地转化成结构化的XML Infoset,就是今天我们要讲的内容——Serialization

Serialization V.S. Encoding

Serialization可以看成是把包含相同内容的数据从一种结构 (.NET Object) 转换成另一种结构 (XML) 要实现在两种不同结构之间的转化,这两种结构之间必须存在一种MappingSerialization的是实现由序列化器(Serializer)来负责。而Serializer则利用某种算法(Arithmetic)来提供这种Mapping。我们知道对于一个Managed Type的结构信息——比如它的所有成员的列表,每个成员的Type、访问限制,以及定在每个成员上的属性,作为原数据被存贮在Assembly的原数据表中,这些原数据可以通过反射的机制获得。而XML的结构一般利用XSD来定义。所以 WCF中的Serialization可以看成是Serializer通过反射的机制分析对象所对应的Type的原数据,从而提供一种算法实现Managed TypeXSD的转化。

很多刚刚接触WCF的人往往不能很好地区分SerializationEncoding。我们的.NET Object通过Serialization转化成XML Infoset但是要使我们的数据能够通过网络协议在网络上传递,必须把生成的XML Infoset转化成字节流(Byte Stream)。所以Encoding关注的是XML Infoset到字节流(Byte Stream)这一段转化的过程。在WCF中,有3中不同的方式可供选择:BinaryTextMTOMMessage Transmit Optimized Mechanism)。Binary具有最好的PerformanceText具有最好的互操作性,MTOM则有利于大量数据的传送。

我们可以这样来理解SerializationEncodingSterilization是基于Service Contract——而实际上它也是定义在Service Contract中,是放在我们的Code中;而Encoding一般由Binding提供,它是和Service无关的,我们一般在Configuration中根据实际的需要选择我们合适的EncodingWCFSerializationEncoding相互分离是有好处的,Serialization手部署环境的影响相对不大,具有相对的通用性,而Encoding则关系到访问Service的性能以及互操作性等方面,和部署环境紧密相关。比如对于一个在一个Intranet内部使用的系统,往往处于提高Performance考虑,我们一般是使用TCP Transport结合Binary,可能在某一天出现的来自于Internet的潜在的调用,我们不得不改用Http作为Transport,并使用Text Encoding。由于Encoding是可配置的,所以在这种情况下,我们只需要改变Configuration文件就可以了。

DataContractSerializer

Serialization 是通过Serializer来完成的,在WCF中,我们有3种不同的Serializer——DataContractSerializer(定义在System.RunTime.Serializtion namespace中)、XMLSerializer(定义在System.XML.Serialization namespace)和NetDataContractSerializer (定义在System.XML.Serialization namespace) 。他们不同的方式实现.NET ObjectSerialization。由于DataContractSerializerNetDataContractSerializer基本上没有太大的区别,我们只讨论DataContractSerializerXMLSerializer。其中DataContractSerializerWCF默认的Serializer,如果没有显式定采用另外一种SerializerWCF会创建一个DataContractSerializer 序列化NET Object。首先我们来讨论DataContractSerializer采用怎样的一种Mapping方式来把.NET Object转化成XML。我们照例用实验来说明问题。

我们创建两个类DataContractProductDataContractOrder用于表示产品和订单两个实体,读者完全可以命知道描述的内容,这里不作特别的介绍。 

  using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
    [DataContract]
    public class DataContractProduct
     {
        Private Fields#region Private Fields
        private Guid _productID;
        private string _productName;
        private string _producingArea;
        private double _unitPrice;
        #endregion
        Constructors#region Constructors
        public DataContractProduct()
         {
            Console.WriteLine("The constructor of DataContractProduct has been invocated!");
        }

        public DataContractProduct(Guid id, string name, string producingArea, double price)
         {
            this._productID = id;
            this._productName = name;
            this._producingArea = producingArea;
            this._unitPrice = price;
        }

        #endregion
        Properties#region Properties
        [DataMember]
        public Guid ProductID
         {
            get  { return _productID; }
            set  { _productID = value; }
        }

        [DataMember]
        public string ProductName
         {
            get  { return _productName; }
            set  { _productName = value; }
        }

        [DataMember]
        private string ProducingArea
         {
            get  { return _producingArea; }
            set  { _producingArea = value; }
        }

        [DataMember]
        public double UnitPrice
         {
            get  { return _unitPrice; }
            set  { _unitPrice = value; }
        }

        #endregion
    }
}

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
    [DataContract]
    [KnownType(typeof(DataContractOrder))]
    public class DataContractOrder
     {
        private Guid _orderID;
        private DateTime _orderDate;
        private DataContractProduct _product;
        private int _quantity;
        Constructors#region Constructors
        public DataContractOrder()
         {
            this._orderID = new Guid();
            this._orderDate = DateTime.MinValue;
            this._quantity = int.MinValue;

            Console.WriteLine("The constructor of DataContractOrder has been invocated!");
        }

        public DataContractOrder(Guid id, DateTime date, DataContractProduct product, int quantity)
         {
            this._orderID = id;
            this._orderDate = date;
            this._product = product;
            this._quantity = quantity;
        }
        #endregion
        Properties#region Properties
        [DataMember]
        public Guid OrderID
         {
            get  { return _orderID; }
            set  { _orderID = value; }
        }
        [DataMember]
        public DateTime OrderDate
         {
            get  { return _orderDate; }
            set  { _orderDate = value; }
        }
        [DataMember]
        public DataContractProduct Product
         {
            get  { return _product; }
            set  { _product = value; }
        }
        [DataMember]
        public int Quantity
         {
            get  { return _quantity; }
            set  { _quantity = value; }
        }
        #endregion
        public override string ToString()
         {
            return string.Format("ID: {0}"nDate:{1}"nProduct:"n"tID:{2}"n"tName:{3}"n"tProducing Area:{4}"n"tPrice:{5}"nQuantity:{6}",
                this._orderID, this._orderDate, this._product.ProductID, this._product.ProductName, this._product.ProducingArea, this._product.UnitPrice, this._quantity);
        }
    }
}

使用DataContractSerializer序列化.NET Object。相关的Type必须运用System.Runtime.Serialization. DataContractAttribute, 需要序列化的成员必须运用System.Runtime.Serialization. DataMemberAttribute。为了使我们能够了解DataContract默认的Mapping机制,我们暂时不在DataContractAttributeDataMemberAttribute设置任何参数。下面我们 来编写具体的Serialization的代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Diagnostics;

namespace Artech.WCFSerialization
{
    class Program
     {
        static string _basePath = @"E:"Projects"Artech.WCFSerialization"Artech.WCFSerialization"";

        static void Main(string[] args)
         {
            SerializeViaDataContractSerializer();
        }
        static void SerializeViaDataContractSerializer()
         {
            DataContractProduct product = new DataContractProduct(Guid.NewGuid(), "Dell PC", "Xiamen FuJian", 4500);
            DataContractOrder order = new DataContractOrder(Guid.NewGuid(), DateTime.Today, product, 300);
            string fileName = _basePath + "Order.DataContractSerializer.xml";
            using (FileStream fs = new FileStream(fileName, FileMode.Create))
             {
                DataContractSerializer serializer = new DataContractSerializer(typeof(DataContractOrder));
                using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs))
                 {
                    serializer.WriteObject(writer, order);
                }
            }
            Process.Start(fileName);
        }
    }
}

代码很简单,这里不作特别介绍。我们现在只关心生成XML是怎样的结构: 

这里我们总结出以下的Mapping关系:

1        Root Element为对象的Type Name——DataContractOrder

2        TypeNamespace会被加到XML根节点的Namespacehttp://schemas.datacontract.org/2004/07/Artech.WCFSerialization

3        对象的所有成员以XML Element的形式而不是以XML Attribute的形式输出。

4        所以对象在XML的输出顺序是按照字母排序。

5        所有成员的Elelement 名称为成员名称。

6        不论成员设置怎样的作用域(public,protectedinternal,甚至市Private),

所有运用了DataMemberAttribute的成员均被序列化到XML——private string ProducingArea

7        Type和成员必须运用DataContractAttributeDataMemberAttribute才能被序列化。

 

上面这些都是默认的Mapping关系,在通常情况下我们用默认的这种Mapping往往不能满足我们的需求,为了把.NET序列化成我们需要的XML 结构(比如我们的XmL必须于我们预先定义的XSD一致),我们可以在这两个AttributeDataContractAttributeDataMemberAttribute)制定相关的参数来实现。具体做法如下。

 

1        Root Element可以通过DataContractAttribute中的Name参数定义。

2        Namespace可以通过DataContractAttribute中的NameSpace参数定义。

3        对象的成员只能以XML Element的形式被序列化。

4        对象成员对应的XML ElementXML出现的位置可以通过DataMemberAttributeOrder参数来定义。

5        对象成员对应的Element的名称可以通过DataMemberAttribute中的Name定义。

6        如果不希望某个成员输出到XML中,可以去掉成员对应的DataMemberAttribute Attribute

此外DataMemberAttribute还有连个额外的参数:

1         IsRequired:制定该成员为必须的,如果通过工具生成XSD的话,对应的ElementminOccur=“1”

2        EmitDefaultValue:制定是否输入没有赋值的成员(值为默认值)是否出现在XML中。

基于上面这些,现在我们修改我们的DataContractOrderDataContractProduct

  using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.WCFSerialization
{
    [DataContract(Name = "product", Namespace = "http://Artech.WCFSerialization/Samples/Product")]
    public class DataContractProduct
     {
        Private Fields#region Private Fields
        private Guid _productID;
        private string _productName;
        private string _producingArea;
        private double _unitPrice;
        #endregion
        Constructors#region Constructors
        public DataContractProduct()
         {
            Console.WriteLine("The constructor of DataContractProduct has been invocated!");
        }

        public DataContractProduct(Guid id, string name, string producingArea, double price)
         {
            this._productID = id;
            this._productName = name;
            this._producingArea = producingArea;
            this._unitPrice = price;
        }

        #endregion
        Properties#region Properties
        [DataMember(Name ="id",Order = 1)]
        public Guid ProductID
         {
            get  { return _productID; }
            set  { _productID = value; }
        }

        [DataMember(Name = "name", Order = 2)]
        public string ProductName
         {
            get  { return _productName; }
            set  { _productName = value; }
        }

        [DataMember(Name = "producingArea", Order = 3)]
        internal string ProducingArea
         {
            get  { return _producingArea; }
            set  { _producingArea = value; }
        }

        [DataMember(Name = "price", Order = 4)]
        public double UnitPrice
         {
            get  { return _unitPrice; }
            set  { _unitPrice = value; }
        }

        #endregion
    }
}

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
namespace Artech.WCFSerialization
{
    [DataContract(Name ="order",Namespace="http://Artech.WCFSerialization/Samples/Order")]
    public class DataContractOrder
     {
        private Guid _orderID;
        private DateTime _orderDate;
        private DataContractProduct _product;
        private int _quantity;

        Constructors#region Constructors
        public DataContractOrder()
         {
            this._orderID = new Guid();
            this._orderDate = DateTime.MinValue;
            this._quantity = int.MinValue;

            Console.WriteLine("The constructor of DataContractOrder has been invocated!");
        }    [KnownType(typeof(DataContractOrder))]


        public DataContractOrder(Guid id, DateTime date, DataContractProduct product, int quantity)
         {
            this._orderID = id;
            this._orderDate = date;
            this._product = product;
            this._quantity = quantity;
        }
        #endregion

        Properties#region Properties
        [DataMember(Name ="id",Order =1)]
        public Guid OrderID
         {
            get  { return _orderID; }
            set  { _orderID = value; }
        }
        [DataMember(Name = "date", Order = 2)]
        public DateTime OrderDate
         {
            get  { return _orderDate; }
            set  { _orderDate = value; }
        }
        [DataMember(Name = "product", Order = 3)]
        public DataContractProduct Product
         {
            get  { return _product; }
            set  { _product = value; }
        }
        [DataMember(Name = "quantity", Order = 4)]
        public int Quantity
         {
            get  { return _quantity; }
            set  { _quantity = value; }
        }
        #endregion
        public override string ToString()
         {
            return string.Format("ID: {0}"nDate:{1}"nProduct:"n"tID:{2}"n"tName:{3}"n"tProducing Area:{4}"n"tPrice:{5}"nQuantity:{6}",
                this._orderID, this._orderDate, this._product.ProductID, this._product.ProductName, this._product.ProducingArea, this._product.UnitPrice, this._quantity);
        }
    }
}

再次进行序列化,看看得到的XML是什么样子?

<order xmlns="http://Artech.WCFSerialization/Samples/Order" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <id>994b42c4-7767-4ed4-bdf8-033e99c00a64</id>
  <date>2007-03-09T00:00:00+08:00</date>
  <product xmlns:a="http://Artech.WCFSerialization/Samples/Product">
    <a:id>137e6c34-3758-413e-8f8a-83f26f78a174</a:id>
    <a:name>Dell PC</a:name>
    <a:producingArea>Xiamen FuJian</a:producingArea>
    <a:price>4500</a:price>
  </product>
  <quantity>300</quantity>
</order>

注:对于DataContract Serializer,这里有两点需要我们注意的:

1         由于Serialization是对数据的不同结构或形态的转化,在转化过程中必须预先知道两种数据相关的原数据(Metadata)。而对于每个.NET对象来说,它的数据结果存放在他所对应的Assembly的原数据表中(Metadata Table),这些原数据表定义的每个定义在该Assembly中的Type的成员定义——包括成员的Type,访问作用域,成员名称,以及运用在成员上的所有Attribute。原则上,只要知道.NET对象对应的Type,我们就可以通过反射(Reflection)的机制分析出该对象的结构。在该例子中,Serializer要序列化DataContractOrder的对象,必须首先知道该对象所属的Type——这个Type通过构造函数传递给Serializer。但是DataContractOrder定义了一个特殊的成员Product,他属于我们的自定义TypeDataContractProduct(这里需要特别指出的是对于.NET的基元类型——Primary Type和一半的常用Type——比如DateTimeGuid等等,Serializer可以自动识别,所以不用特别指定),Serializer是不会识别这个对象的,所以我们需要在定义DataContractOrder的时候运用KnownType Attribute来这个Type

2         因为在传统的分布式应用中,我们广泛地采用Serializable Attribute来表明该对象是可以序列化的,DataContract Serializer对这种机制也是支持的。

上面我们讲了Serialization,它的本质就是把.NETObject转化具有一定结构的XML Infoset被序列化的成的XML Infoset记过Encoding被进一步转化成适合在网络上传递的字节流。当这些字节流从一个Application传递到另一个Application,由于我们的程序的业务逻辑处理的是一个个的.NET对象,所以在目标Application, 会以一个相反的过程把接收到的字节流重构成为和原来一样的.NET Object——目标Application对接收到的字节流记过Decoding转化成XML Infoset,然后通过创建和用于序列化过程一致的Serializer通过Deserialization重建一个.NET Object。所以这就对Serializer提出了要求——它必须为Managed Type的结构和XML的结构提供可逆性的保证——我们把一个.NET Object序列化成一组XML,然后对这组XML进行反序列化重建的对象必须和原来一致。

现在我们在验证这种一致性。在上面的Sample中,我们创建了一个DataContractOrder对象,对它进行序列化并把生成的XML保存的一个文件里面(Order.DataContractSerializer.xml),现在我们都读取这个文件的内容,把它反序列化成DataContractOrder 对象,看它的内容是否和原来一样。下面是Deserialization的逻辑。

static void DeserializeViaDataContractSerializer()
         {
            string fileName = _basePath + "Order.DataContractSerializer.xml";
            DataContractOrder order;
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
             {
                DataContractSerializer serializer = new DataContractSerializer(typeof(DataContractOrder));
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
                 {
                    order = serializer.ReadObject(reader) as DataContractOrder;
                }
            }
            Console.WriteLine(order);
            Console.Read();
        }

调用这个方法,通过在控制台输出DataContractOrder的内容,我们可以确定,通过Deserialization生成的DataContractOrder 对象和原来的对象具有一样的内容。

4.2XMLSerializer

提到XMLSerializer,我想绝大多数人都知道这是asmx采用的Serializer。首先我们还是来看一个例子,通过比较Managed Type的结构和生成的XML的结构来总结这种序列化方式采用的是怎样的一种Mapping方式。和DataContractSerialzer Sample一样,我们要定义用于序列化对象所属的Type——XMLOrderXMLProduct,他们和相面对应的DataContractOrderDataContractProduct具有相同的成员。

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.WCFSerialization
{
    public class XMLProduct
     {
        Private Fields#region Private Fields
        private Guid _productID;
        private string _productName;
        private string _producingArea;
        private double _unitPrice;
       
        Constructors#region Constructors
        public XMLProduct()
         {
            Console.WriteLine("The constructor of XMLProduct has been invocated!");
        }

        public XMLProduct(Guid id, string name, string producingArea, double price)
         {
            this._productID = id;
            this._productName = name;
            this._producingArea = producingArea;
            this._unitPrice = price;
        }

        #endregion

        Properties#region Properties
        public Guid ProductID
         {
            get  { return _productID; }
            set  { _productID = value; }
        }

        public string ProductName
         {
            get  { return _productName; }
            set  { _productName = value; }
        }

        internal string ProducingArea
         {
            get  { return _producingArea; }
            set  { _producingArea = value; }
        }

        public double UnitPrice
         {
            get  { return _unitPrice; }
            set  { _unitPrice = value; }
        }

       #endregion

    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace Artech.WCFSerialization
{
    public class XMLOrder
     {
        private Guid _orderID;
        private DateTime _orderDate;
        private XMLProduct _product;
        private int _quantity;
        Constructors#region Constructors
        public XMLOrder()
         {
            this._orderID = new Guid();
            this._orderDate = DateTime.MinValue;
            this._quantity = int.MinValue;

            Console.WriteLine("The constructor of XMLOrder has been invocated!");
        }

        public XMLOrder(Guid id, DateTime date, XMLProduct product, int quantity)
         {
            this._orderID = id;
            this._orderDate = date;
            this._product = product;
            this._quantity = quantity;
        }
        #endregion
        Properties#region Properties
        public Guid OrderID
         {
            get  { return _orderID; }
            set  { _orderID = value; }
        }

        public DateTime OrderDate
         {
            get  { return _orderDate; }
            set  { _orderDate = value; }
        }

        public XMLProduct Product
         {
            get  { return _product; }
            set  { _product = value; }
        }

        public int Quantity
         {
            get  { return _quantity; }
            set  { _quantity = value; }
        }
        #endregion
        public override string ToString()
         {
            return string.Format("ID: {0}"nDate:{1}"nProduct:"n"tID:{2}"n"tName:{3}"n"tProducing Area:{4}"n"tPrice:{5}"nQuantity:{6}",
                this._orderID,this._orderDate,this._product.ProductID,this._product.ProductName,this._product.ProducingArea,this._product.UnitPrice,this._quantity);
        }
    }
}

编写SerializationCode.
static void SerializeViaXMLSerializer()
         {
            XMLProduct product = new XMLProduct(Guid.NewGuid(), "Dell PC", "Xiamen FuJian", 4500);
            XMLOrder order = new XMLOrder(Guid.NewGuid(), DateTime.Today, product, 300);
            string fileName = _basePath + "Order.XmlSerializer.xml";
            using (FileStream fs = new FileStream(fileName, FileMode.Create))
             {
                XmlSerializer serializer = new XmlSerializer(typeof(XMLOrder));
                using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs))
                 {
                    serializer.Serialize(writer, order);
                }
            }
            Process.Start(fileName);
        }

调用上面定义的方法,生成序列化的XML

<?xml version="1.0" encoding="utf-8"?>
<XMLOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <OrderID>b695fd18-9cd7-4792-968a-0c0c3a3962c2</OrderID>
    <OrderDate>2007-03-09T00:00:00+08:00</OrderDate>
    <Product>
        <ProductID>23a2fe03-d0a0-4ce5-b213-c7e5196af566</ProductID>
        <ProductName>Dell PC</ProductName>
        <UnitPrice>4500</UnitPrice>
    </Product>
    <Quantity>300</Quantity>
</XMLOrder>

这里我们总结出以下的Mapping关系:

Root Element被指定为类名。

不会再Root Element中添加相应的Namaspace

对象成员以XML Element的形式输出。

对象成员出现的顺利和在Type定义的顺序一致。

只有Public Field和可读可写得Proppery才会被序列化到XML——比如定义在XMLProduct中的internal string ProducingArea没有出现在XML中。

Type定义的时候不需要运用任何Attribute

以上这些都是默认的Mapping关系,同DataContractSerializer一样,我们可以通过在Type以及它的成员中运用一些Attribute来改这种默认的Mapping

Root Element名称之后能为类名。

可以在Type上运用XMLRoot,通过Namaspace参数在Root Element指定Namespace

可以通过在类成员上运用XMLElement AttributeXMLAttribute Attribute指定对象成员转化成XMLElement还是XMLAttribute。并且可以通过NameSpace参数定义Namespace

可以在XMLElement或者XMLAttribute Attribute 通过Order参数指定成员在XML出现的位置。

可以通过XmlIgnore attribute阻止对象成员被序列化。

基于上面这些,我们重新定义了XMLProductXMLOrder

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
namespace Artech.WCFSerialization
{
    public class XMLProduct
     {
        Private Fields#region Private Fields
        private Guid _productID;
        private string _productName;
        private string _producingArea;
        private double _unitPrice;
        #endregion
        Constructors#region Constructors
        public XMLProduct()
         {
            Console.WriteLine("The constructor of XMLProduct has been invocated!");
        }

        public XMLProduct(Guid id, string name, string producingArea, double price)
         {
            this._productID = id;
            this._productName = name;
            this._producingArea = producingArea;
            this._unitPrice = price;
        }

        #endregion
        Properties#region Properties
        [XmlAttribute("id")]
        public Guid ProductID
         {
            get  { return _productID; }
            set  { _productID = value; }
        }

        [XmlElement("name")]
        public string ProductName
         {
            get  { return _productName; }
            set  { _productName = value; }
        }
        [XmlElement("producingArea")]
        public string ProducingArea
         {
            get  { return _producingArea; }
            set  { _producingArea = value; }
        }

        [XmlElement("price")]
        public double UnitPrice
         {
            get  { return _unitPrice; }
            set  { _unitPrice = value; }
        }

#endregion
    }
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Artech.WCFSerialization
{
    [XmlRoot(Namespace = "http://artech.wcfSerialization/Samples/Order")]
    public class XMLOrder
     {
        private Guid _orderID;
        private DateTime _orderDate;
        private XMLProduct _product;
        private int _quantity;
        Constructors#region Constructors
        public XMLOrder()
         {
            this._orderID = new Guid();
            this._orderDate = DateTime.MinValue;
            this._quantity = int.MinValue;

            Console.WriteLine("The constructor of XMLOrder has been invocated!");
        }

        public XMLOrder(Guid id, DateTime date, XMLProduct product, int quantity)
         {
            this._orderID = id;
            this._orderDate = date;
            this._product = product;
            this._quantity = quantity;
        }
        #endregion
        Properties#region Properties
        [XmlAttribute("id")]
        public Guid OrderID
         {
            get  { return _orderID; }
            set  { _orderID = value; }
        }
        [XmlElement(ElementName = "date",Order = 3)]
        public DateTime OrderDate
         {
            get  { return _orderDate; }
            set  { _orderDate = value; }
        }
        [XmlElement(ElementName = "product", Order = 1, Namespace = "Http://Artech.WCFSerialization/Samples/Product")]
        public XMLProduct Product
         {
            get  { return _product; }
            set  { _product = value; }
        }

        [XmlElement(ElementName = "quantity", Order = 2)]
        public int Quantity
         {
            get  { return _quantity; }
            set  { _quantity = value; }
        }
        #endregion
        public override string ToString()
         {
            return string.Format("ID: {0}"nDate:{1}"nProduct:"n"tID:{2}"n"tName:{3}"n"tProducing Area:{4}"n"tPrice:{5}"nQuantity:{6}",
                this._orderID,this._orderDate,this._product.ProductID,this._product.ProductName,this._product.ProducingArea,this._product.UnitPrice,this._quantity);
        }
    }
}
重新进行一次Serialization。我们可以得到下面的XML

<?xml version="1.0" encoding="utf-8"?>
<XMLOrder id="9a0bbda4-1743-4398-bc4f-ee216e02695b" xmlns="http://artech.wcfSerialization/Samples/Order" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <product id="4e3aabe5-3a51-4000-9fd8-d821d164572a" xmlns="Http://Artech.WCFSerialization/Samples/Product">
    <name>Dell PC</name>
    <producingArea>Xiamen FuJian</producingArea>
    <price>4500</price>
  </product>
  <quantity>300</quantity>
  <date>2007-03-09T00:00:00+08:00</date>
</XMLOrder>

分析完XMLSerializerSerialization功能,我们照例来分析它的反向过程—Deserialization。下面的DeserializationCode
static void DeserializeViaXMLSerializer()
         {
           string fileName = _basePath + "Order.XmlSerializer.xml";
           XMLOrder order;
           using (FileStream fs = new FileStream(fileName, FileMode.Open))
             {
                XmlSerializer serializer = new XmlSerializer(typeof(XMLOrder), "http://artech.WCFSerialization/Samples");
                using (XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas()))
                 {
                    order= serializer.Deserialize(reader) as XMLOrder;
                }
            }

            Console.WriteLine(order);
            Console.Read();
}

调用DeserializeViaXMLSerializer,得到下面的Screen Shot。下面显示的Order对象的信息和我们利用DataContractSerializaer进行Deserialization是的输出没有什么两样。不过有趣的是上面多出了两行额外的输出:The constructor of XMLProduct has been invocated! The constructor of XMLOrder has been invocated。而这个操作实际上是定义在XMLProductXMLOrder的默认(无参)构造函数里的。所此我们可以得出这样的结论——XMLSerializer进程Deserialization,会调用的默认(无参)构造函数来初始化对象。 

DataContractSerializer V.S. XMLSerializer

上面我们分别分析了两种不同的Serializer,现在我们来简单总结一下他们的区别:

特性

XMLSerializer

DataContractSerializer

默认Mapping

所有Public Field和可读可写Property

所有DataMember FiledProperty

是否需要Attribute

不需要

DataContract DataMember或者Serializable

成员的默认次序

Type中定义的顺序

字母排序

兼容性

.asmx

Remoting

Deserialzation

调用默认构造函数

不会调用

 

5):Service Contract中的重载(Overloading

 

对于.NET重载(Overloading——定义不同参数列表的同名方法(顺便提一下,我们但可以在参数列表上重载方法,我们甚至可以在返回类型层面来重载我们需要的方法——页就是说,我们可以定义两个具有相同参数列表但不同返回值类型的两个同名的方法。不过这种广义的Overloading不被我们主流的.NET 语言所支持的——C#, VB.NET, 但是对于IL来说,这这种基于返回值类型的Overloading是支持的)。相信大家听得耳朵都要起老茧了。我想大家也清楚在编写传统的XML Web Service的时候,Overloading是不被支持的。

原因很简单,当我们用某种支持.NET的高级语言写成的程序被相应的编译器编译成Assembly的过程中,不单单是我们的Source Code会被变成IL Code,在Assembly中还会生成相应的原数据Metadata——这些Metadata 可以被看看是一张张的Table。这些Table存储了定义了主要3个方面的信息——构成这个Assembly文件的信息;在Assembly中定义的Type及其相关成员的信息;本引用的Assembly Type的信息。这些完备的Metadata成就了Assembly的自描述性(Self-Describing),也只是有了这些Metadata,使.NET可以很容易地根据方法参数的列表甚至是返回值得类型来判断调用的究竟了那个方法。

而对于XML Web Service,它的标准实际上是基于XML的,近一步说,一个XML Web Service是通过一个一段XML来描述的,而这个描述XML Web ServiceXML,我们称之为WSDLWeb Service Description Language)。在WSDL中,Web Service的一个方法(Method)对应的是一个操作(Operation),Web Service 所有的Operation定义在WSDL中的portType Section我们可以参照下面一段XML,它是从一个完整的WSDL中截取下来的。我们可以看到,portType包含了Web Service定义的所有Operation,每个Operation由一个operation XML Element表示。看过我前面Blog的读者应该知道,从消息交换(Message Exchange)的层面上讲,一个Operation实际上体现的是一种消息交换的模式(Message Exchange Pattern——MEP)。所以我们完全可以通过一定消息交换的输入消息(Input Message)和输出(Output Message )定义一个Operation。而WSDL也是这样做的。(这里顺便提一下,Output Message部仅仅对应一个方法的Return Value,还包括表明ref outParameter)。除了定义进行消息交互的Message的格式(一般通过XSD)之外,每个Operation还应该具有一个能够为一标识该OperationID,这个ID通过name XML Attribute来定义。通常的情况下,OperationName使用Web Service的方法名——这就是在传统XML Web Service不可以使用Overloading的原因。

<wsdl:portType name="ICalculator">
  <wsdl:operation name="AddWithTwoOperands">
    <wsdl:input wsaw:Action="http://tempuri.org/ICalculator/AddWithTwoOperands" message="tns:ICalculator_AddWithTwoOperands_InputMessage" />
    <wsdl:output wsaw:Action="http://tempuri.org/ICalculator/AddWithTwoOperandsResponse" message="tns:ICalculator_AddWithTwoOperands_OutputMessage" />
  </wsdl:operation>
  <wsdl:operation name="AddWithThreeOperands">
    <wsdl:input wsaw:Action="http://tempuri.org/ICalculator/AddWithThreeOperands" message="tns:ICalculator_AddWithThreeOperands_InputMessage" />
    <wsdl:output wsaw:Action="http://tempuri.org/ICalculator/AddWithThreeOperandsResponse" message="tns:ICalculator_AddWithThreeOperands_OutputMessage" />
  </wsdl:operation>
</wsdl:portType>

XML Web ServiceWCF也面临一样的问题——我觉得我们可以把WCF看成.NET平台下新一代的Web Service。虽然现有XML Web Service现在具有广泛的使用——尤其在构建跨平台性的分布是应用和进行系统集成上面,但是从Microsoft已经明确提出WSE 3.0将是最后一个VersionWSE,所以,现有的Web Service将会全面的过渡到WCFWCF到底是什么东西,我在前面的文章中不断地提出这个问题,在这里我们从 另外一个方面来看待WCF。我们知道W3C定义了一系列关于WS的规范Specification,成为WS-* Specification。这一系列的Specification定义了建立在XMLSOAP标准之上的基于如何将一个可互操作系统(Interoperable System)的各个方面的标准,比如WS-MessagingWS-SecurityWS-Transaction等等。而WCF则可以看成是这一整套Specification的实现。但是这种实现最终还是落实到我们.NET编程上。我们可以把WS-Specification和我们的基于.NET语言的编程看成是两种截然不同的编程模型(Programming Model)。WCF的功能则是把这两种不同的编程模型统一起来,实现他们之间的一个Mapping——可以把WCF看成一个Adapter

回到我们的Overloading上面来,Overloading.NET Framework原生支持的。通过Overloading,我们可以使用同名的方法来定义不同的操作,从而使我们的Code显得更加优雅(Elegant)。要是OverloadingWCF中可以使用,WCF必须提供这样的一个Mapping——是被重载的具有相同方法的的方法Mapping到不同的Operation上。而提供着一个功能的就是ServiceContract。下面我们来结合一个Sample来看如何在WCF中使用Overloading

沿用我们的Calculator的应用,现在我们做一个加法器,它具有两个Operation——两书相加和三数相加。这两个方法都用一个名称Add

1.下面是Solution的结构。不像前面的结构,这这里我们没有把Service Contract单独提取出来,供ClientService供用。因为我们现在模拟的是,Service完全由一个外部的第三方提供,Service 已经确定,不能根据Client的具体要求来修改ServiceSource Code这里下载。

 

 

 

6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案

 

几个星期之前写了一篇关于如何通过WCF进行 双向通信的文章([原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication ),在文章中我提供了一个如果在Console Application 调用Duplex WCF ServiceSample。前几天有个网友在上面留言说,在没有做任何改动得情况下,把 作为ClientConsole Application 换成Winform Application,运行程序的时候总是出现Timeout的错误。我觉得这是一个很好的问题,通过这个问题,我们可以更加深入地理解WCF的消息交换的机制。

1.问题重现

首先我们来重现这个错误,在这里我只写WinForm的代码,其他的内容请参考我的文章。Client端的Proxy ClassDuplexCalculatorClient)的定义没有任何变化。我们先来定义用于执行回调操作(Callback)的类——CalculatorCallbackHandler.cs。代码很简单,就是通过Message Box的方式显示运算的结果。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Artech.DuplexWCFService.Contract;
using System.ServiceModel;

namespace Artech. WCFService.Client
{
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class CalculatorCallbackHandler : ICalculatorCallback
     {
        ICalculatorCallback Members#region ICalculatorCallback Members
        public void ShowResult(double x, double y, double result)
         {
            MessageBox.Show(string.Format("x + y = {2} where x = {0} and {1}", x, y, result),"Result", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        #endregion
    }
}

接着我们来设计我们的UI,很简单,无需多说。


代码如下

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Artech. WCFService.Client
{
    public partial class Form1 : Form
     {
        private DuplexCalculatorClient _calculator;
        private double _op1;
        private double _op2;
        public Form1()
         {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
         {
            this._calculator = new DuplexCalculatorClient(new System.ServiceModel.InstanceContext(new CalculatorCallbackHandler()));
        }
        private void Calculate()
         {
            this._calculator.Add(this._op1, this._op2);
        }
        private void buttonCalculate_Click(object sender, EventArgs e)
         {
            if (!double.TryParse(this.textBoxOp1.Text.Trim(), out this._op1))
             {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            if (!double.TryParse(this.textBoxOp2.Text.Trim(), out this._op2))
             {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            try
             {
                this.Calculate();
            }
            catch (Exception ex)
             {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            } 
        }
    }
}

启动Host,然后随启动Client,在两个Textbox中输入数字23Click Calculate按钮,随后整个UI被锁住,无法响应用户操作。一分后,出现下面的错误。


我们从上面的Screen Shot中可以看到这样一个很有意思的现象,运算结果被成功的显示,显示,但是有个Exception被抛出:”This request operation sent to http://localhost:6666/myClient/4f4ebfeb-5c84-45dc-92eb-689d631b337f did not receive a reply within the configured timeout (00:00:57.7300000). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.”

2.原因分析

在我开始分析为什么会造成上面的情况之前,我要申明一点:由于找不到任何相关的资料,以下的结论是我从试验推导出来,我不能保证我的分析是合理的,因为有些细节我自己都还不能自圆其说,我将在后面提到。我希望有谁对此了解的人能够指出我的问题, 我将不胜感激。

我们先来看看整个调用过程的Message Exchange过程,通过前面相关的介绍,我们知道WCF可以采用三种不同的Message Exchange PatternMEP——One-wayRequest/ResponseDuplex。其实从本质上讲,One-wayRequest/Response是两种基本的MEP, Duplex可以看成是这两种MEP的组合——两个One-way,两个Request/Response或者是一个One-way和一个Request/Response。在定义Service Contract的时候,如果我们没有为某个Operation显式指定为One-way (IsOneWay = true), 那么默认采用Request/Response方式。我们现在的Sample就是由两个Request/Response MEP组成的Duplex MEP


从上图中我们可以很清楚地看出真个Message Exchange过程,Client调用Duplex Calculator ServiceMessage先从Client传递到ServiceService执行Add操作,得到运算结果之后,从当前的OperationContext获得Callback对象,发送一个Callback 请求道Client(通过在Client注册的Callback Channelhttp://localhost:6666/myClient)。但是,由于Client端调用Calculator Service是在主线程中,我们知道一个UI的程序的主线程一直处于等待的状态,它是不会有机会接收来自Service端的Callback请求的。但是由于Callback Operation是采用Request/Response方式调用的,所以它必须要收到来自ClientReply来确定操作正常结束。这实际上形成了一个Deadlock,可以想象它用过也不能获得这个Reply,所以在一个设定的时间内(默认为1分钟),它会抛出Timeout Exception, Error Message就像下面这个样子。

”This request operation sent to http://localhost:6666/myClient/4f4ebfeb-5c84-45dc-92eb-689d631b337f did not receive a reply within the configured timeout (00:00:57.7300000). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.”

3.解决方案

方案1:多线程异步调用

既然WinForm的主线程不能接受ServiceCallback,那么我们就在另一个线程调用Calculator Service,在这个新的线程接受来自ServiceCallback

于是我们改变Client的代码:

private void buttonCalculate_Click(object sender, EventArgs e)
         {
            if (!double.TryParse(this.textBoxOp1.Text.Trim(), out this._op1))
             {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            if (!double.TryParse(this.textBoxOp2.Text.Trim(), out this._op2))
             {
                MessageBox.Show("Please enter a valid number","Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
                this.textBoxOp1.Focus();
            }
            try
             {
                Thread newThread = new Thread(new ThreadStart(this.Calculate));
                newThread.Start();        
            }
            catch (Exception ex)
             {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }             
        }

通过实验证明,这种方式是可行的。

方案2:采用One-way的方式调用Service Callback,既然是因为Exception发生在不同在规定的时间内不能正常地收到对应的Reply,那种我就 允许你不必收到Reply就好了——实际上在本例中,对于Add方法,我们根本就不需要有返回结果,我们完全可以使用One-way的方式调用Operation。在这种情况下,我们只需要改变DuplexCalculatorCalculatorCallbackService Contract定义就可以了。

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.DuplexWCFService.Contract
{
    [ServiceContract(CallbackContract = typeof(ICalculatorCallback))]
    public interface IDuplexCalculator
     {
        [OperationContract(IsOneWay =true)]
        void Add(double x, double y);
    }
}

Message Exchange的角度讲,这种方式实际上是采用下面一种消息交换模式(MEP):

进一步地,由于Callback也没有返回值,我们也可以把Callback操作也标记为One-way

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace Artech.DuplexWCFService.Contract
{
    //[ServiceContract]
    public interface ICalculatorCallback
     {
        [OperationContract(IsOneWay = true)]
        void  ShowResult(double x, double y, double result);
    }
}

那么现在的Message Exchange成为下面一种方式:

实现证明这两种方式也是可行的。
4 .疑问

虽然直到现在,所有的现象都说得过去,但是仍然有一个问题不能得到解释:如果是因为Winform的主线程不能正常地接受来自ServiceCallback才导致了Timeout Exception,那为什么Callback操作能过正常执行呢?而且通过我的实验证明他基本上是在抛出Exception的同时执行的。(参考第2个截图)

 

7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承

 

当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解。其中很大一部分人甚至认为面向服务将是面向对象的终结,现在的面向对象将会被面向服务完全代替。在开始本Blog之前,我先来谈谈我对SOAOO的区别,首先申明,这只是一家之言,欢迎大家批评指正,并且关于SO的谈论不是本Blog的主题,只是主题的引子,在这里只是简单讨论而已

OOSO之间具有共同的部分,在运用的领域上存在交集,只有在基于他们交集层面上谈论谁是谁非才有意义,下面是我对SOOO的区别。

OO关注的是如何通过对实体属性和行为的封装来重塑模拟软件环境的真实实体。对SO关注的则是对现实生活的某个任务、功能的实现,说得通俗点,就是如果做好一件事情。所以对象是对DataBehavior的封装,而Service则只是体现了一个FunctionalityService是粗粒度的,这样才能导致Contract的稳定;Service一般是Stateless的,给定什么样的输入,就会有对应的确定的输出;ServiceAutonomous,基本上的实现封装在Service本身之中,这样可以使它对其它的Service产生最小的依赖。所以对Service建模是一件不太容易的事情:Service不能太大,否则实现起来不容易,还会增加对外界的依赖;也不能太小,否则整合这个Service的成本会令你望而却步。

Service是可组合的(Composable),通过整合相关的单独的,完成某个单个Task或者Activity的小的Service,可以很容易产生一个大的Service,这个Service可以完成一个Functionality的整个流程。比如我们现在把一个Task描述成一个Work flow,如果采用SO的原理来建模,我们可以把组成这个Workflow的单个Activity设计成一个service, 然后根据具体Workflow的特点(比如可能是一个简单的Sequential workflow,也可能是一个基于State machineworkflow)加上相关的条件很容易的把这些Service整合起来,实际上通过整合集成,我们生成一个新的Service。对OO,由于他面对的是一个的Object,具体在分布式中是一个个的Distributed object,要建立一个Composable object则很难(实际上这样也没有什么意义)。

OO的概念中,一个Object的属性往往就是另一个Object,一个Function的实现往往要调用另一个Object的方法,而且这种层次结构可以无限延伸。这样就会导致真个Object体系变得异常脆弱,经常造成牵一发动全身的状况。用一个很时髦的词语来表达的,就是紧耦合(Tightly couple),Object之间的强依赖关系促成了这种紧耦合的、脆弱的体系结构。而OS则不一样,由于 构成Service体系的单个Service是自治的,Service之间的调用(调用的这个词语容易让人联想到RPC,如果找一个比较贴切的词语来代替,我觉得consume比较合适)是基于Contract的,Service之间Communication是通过Message的(Message不仅仅是Data的封装,还是对整个调用的描述,同时基于XMLMessage描述和Message的传递都是符合一个开放的标准的),所有这些成就了SO的松耦合(Loosely couple)。

说了这么多,可能大家都觉得我都是在赞扬SO,都贬低OO。其实不然,上面所说的3个方面都是在讲应用的构建,而不是具体的编程模式。我想表达的是,曾经盛行的基于OO的理论,在企业应用构架方面,已经不能满足我们的需要了,我们迫切希望一种全新的理论来指导我们进行企业应用的构架和集成,而这个理论非SO不可。

而在编程模型层面,OO仍然是不可替代的编程模式。所以OO应用于Programming,而SO则更多地运用在Architecture。既然是这样,我们必须有一种调和剂来调和这两个运用不同原理的两个层面的差异,实现他们之间的无缝的结合。比如如何来对继承,多态,重载等基于OO行为的支持。在这方面,WCF为我们提供了很好的解决方案。所以我说WCF不但是为基于SOA的应用架构提供了技术支持,还通过相关的机制完成我们提出的这个调和剂的使命。

在上一篇文章[原创]我的WCF之旅(5):面向服务架构(SOA)对面向对象编程(OOP)的支持——如何实现Service Contract的重载(Overloading中,我们谈到了WCF如何实现了对Overloading的支持,在这里我们通过一个Sample来讨论WCF对继承的支持。这个Sample中,我们通过一个WCF Service实现了提供天气信息的功能,或者说,我们实现了一个用作天气预报的WCF Service

1    我们照例来看看真个Solution 的结构:


整个Solution由以下4project构成:

 

 

8):WCF中的SessionInstancing Management

 

我们知道,WCFMS基于SOA建立的一套在分布式环境中各个相对独立的Application进行Communication的构架。他实现了最新的基于WS-*规范。按照SOA的原则,相对独自的业务逻辑以service的形式封装,调用者通过Messaging的方式调用Service对于承载着某个业务功能的实现的Service应该具有Context无关性、甚至是Solution无关性,也就是说个构成Serviceoperation不应该绑定到具体的调用上下文,对于任何调用,具有什么样的输入,就会有与之对应的输出。因为SOA的一个最大的目标就是尽可能地实现重用,只有具有Context无关性/Solution无关性,Service才能实现最大限度的重用。此外ServiceContext无关性/Solution无关性还促进了另一个重要的面向服务的特征的实现:可组合性,把若干相关细粒度的Service封装成一个整体业务流程的Service

在一个C/SClient/Service)场景中,Context无关性体现在ClientService的每次调用都是完全不相关的。但是在有些情况下,我们却希望系统为我们创建一个Session来保留某个ClientService的进行交互的状态。所以,像Web Service一样,WCF也提供了对Session的支持。对于WCF来说,ClientService之间的交互都通过Soap Message来实现的,每次交互的过程就是一次简单的Message Exchange。所以从Messaging的角度来讲,WCFSession就是把某个把相关的Message Exchange纳入同一个Conversation。每个Session用一个Session ID来唯一标识。

WCF中的SessionASP.NETSession

WCF中,Session属于Service Contract的范畴,是一个相对抽象的概念,并在Service Contract定义中通过SessionModel参数来实现。他具有以下几个重要特征:

Session的创建和结束都有来自Client端的调用来实现

我们知道,在WCFClient通过创建的Proxy对象来和service的交互,在默认的支持Session的情况下,Session和具体的Proxy对象绑定在一起,当Client通过调用Proxy的某个方法来访问Service的时候,Session被初始化,直到Proxy被关闭,Session被终止,我们可以通过下面两种方式来关闭Proxy

调用System.ServiceModel. ICommunicationObject对象(我们一般通过System.ServiceModel. ChannelFactory对象的CreateChannel方法获得)的Close方法。

调用System.ServiceModel. ClientBase对象(我们一半通过继承它来实现我们为某个特定的Service创建Proxy类)的Close方法。

此外,我们也可以人为地指定通过调用Service的某个operation来初始化、或者终止Session。我们一般通过System.ServiceModel. OperationContractAttributeIsInitiatingIsTerminating参数来指定初始化和终止SessionOperation

WCF保证处于某个Session中传递的Message按照他发送的次序被接收

WCF并没有为Session的支持而保存相关的状态数据。

说道WCF中的Session,我们很自然地联想到ASP.NET中的Session。实际上,他们之间具有很大的差异:

ASP.NETSession总是在Server端初始化的。

ASP.NET并不提供Ordered Message Delivery的担保。

ASP.NET是通过在Serer以某种方式保存State来实现对Session的支持的,比如保存在Web Server的内存中,保存在State Server甚至是SQL Server中。

WCF中的Session的实现和Instancing Management

在上面我们说了,虽然WCF支持Session,但是并没有相关的状态信息被保存在某种介质中。WCF是通过怎样的方式来支持Session的呢?这就是我们本节要讲的Instancing Management

对于Client来说,它实际上不能和Service进行直接交互,它只能通过客户端创建的Proxy来间接地实现和service的交互。Session的表现体现在以下两种方式:

Session的周期和Proxy的周期绑定,这种方式体现为默认的Session支持。

Session的周期绑定到开始和终止Session的方法调用之间的时间内,这种方式体现在我们在定义Operation Contract时通过IsInitiatingIsTerminating显式指定开始和终止SessionOperatoin

我们很清楚,真正的逻辑实现是通过调用真正的Service instance中。在一个分布式环境中,我们把通过Client的调用来创建最终的Service Instance的过程叫做Activation。在Remoting中我们有两种Activation方式:Server ActivationSingletonSingleCall),Client Activation。实际上对WCF也具有相似的Activation。不过WCF不仅仅创建对应的Service Instance,而且还构建相关的Context, 我们把这些统称为Instance Context。不同的Activation方式在WCF中体现为的Instance context model。不同的Instance Context Mode体现为ProxyService 调用和Service Instance之间的对应关系。可以这么说,Instance Context Mode决定着不同的Session表现。在WCF中,支持以下3中不同级别的Instance Context Mode

PerCallWCF为每个Serivce调用创建 一个Service Instance,调用完成后回收该Instance。这种方式和Remoting中的SingleCall相似。

PerSession:在Session期间的所有Service调用绑定到某一个Service InstanceSession被终止后,Service Instance被回收。所以在Session结束后使用同一个Proxy进行调用,会抛出Exception。这种方式和Remoting中的CAO相似。

Singleton:这种方式和RemotingSingelton相似。不过它的激活方式又有点特别。当为对应的Service type进行Host的时候,与之对应的Service Instance就被创建出来,此后所有的Service调用都被forward到该Instance

WCF的默认的Instance Context ModePerSession,但是对于是否对Session的支持,Instancing的机制有所不同。如果通过以下的方式定义ServiceContract使之不支持Session,或者使用不支持SessionBinding(顺便说一下,Session的支持是通过建立Sessionful Channel来实现的,但是并不是所有的Binding都支持Session,比如BasicHttpBinding就不支持Session),WCF实际上会为每个Service调用创建一个Service Instance,这实质上就是PerCallInstance Context Mode,但我为什么会说默认的是PerSession呢?我个人觉得我们可以这样地来看看SessionSession按照本意就是ClientService之间建立的一个持续的会话状态,不过这个Session状态的持续时间有长有短,可以和Client的生命周期一样,也可以存在于某两个特定的Operation调用之间,最短的则可以看成是每次Service的调用,所以按照我的观点,PerCall也可以看成是一种特殊的Session(我知道会有很多人不认同我的这种看法。)

   [ServiceContract(SessionMode = SessionMode.NotAllowed)]

Simple (WCF"Smaples"SessionfulCalculator)

接下来我们来看看一个简单的Sample,相信大家会对SessionInstancing Management会有一个深入的认识。这个Sample沿用我们Calculator的例子,Solution的结构如下,4Project分别用于定义SeviceContractService ImplementationHostingClient

 

Hosting的输出结果,这是在刚刚启动HostingClient尚未启动时的Screenshot


在这之前我们都是Client通过Proxy调用相应的Service之后,Service Instance才开始创建,但是对于InstanceContextMode.SingleService Instance却早在Service TypeHost的时候就已经被创建了。

现在启动Client

 

 

9):如何在WCF中使用tcpTrace来进行Soap Trace

 

无论对于Web Service还是WCFClientService之间交互的唯一形式是通过发送和接收Soap Message。在我们对Web ServiceWCF进行深入学习的时候,借助一些Soap Trace 工具对Soap Message进行深入剖析是非常有必要的。在这些工具之中,我觉得最好用的就是Microsoft Soap Toolkit中的Soap Trace UtilitytcpTrace我们今天就来讲讲如何在WCF中使用tcpTrace这个工具。

首先我们来讲讲tcpTrace实现的基本原理。说的简单点TcpTrace就是一个监听/转发器(Listening/Forwarding)。当我们启动这个工具的时候,通过设置它监听的Port,和它将要转发的HostPortDestination Server& Destination Port),随后它就开始在本机的Listening Port开始监听,如果这时候一个针对该Listening Port Http Request,它就会把Request的内容取下来展现在我们的面前,随后将该Request转发到我们预先设定的HostPort

对于WCF来说,如果Client要访问Service,一般情况下交互的只有ClientServiceSoap Message直接从ClientService。但是在某些情况,我们需要在ClientService之间加入一些额外的节点,我们把这些额外的节点Intermediary Node。我们可以通过这些Intermediary Node实现一些额外的功能,比如把不同的Request forward到不同的Server从而实现负载平衡(Load Balance)。按照面向服务的原则,服务具有高度的自治性(Automation),Soap Message一旦被Service发送出去,就不能再被该Service所控制,所以Soap来说,它需要具有高度的自描述性(Self-Describing),它自身必须包含所有必须的控制信息来指导任何接收到该Soap的节点如何去处理它。SOAP的无限扩展的Header在实现此功能上可谓功不可没,原则上任何控制信息都可以放在Soap Header之中,Header的可扩展性也使一系列的WS-* Specification的实现 成为可能。对于每次的Message Exchange来说,寻址(Addressing)是首先需要解决的问题,在Intermediary Node的场景中,实际上涉及到两个Address,其中一个是最终Service EndpointAddress,另一个则是实际接收该SoapIntermediary NodeAddress。在WCF中通过ClientViaBehavior实现这样的功能,我将在 后面讲到。而我们今天所介绍的通过tcpTrace来获取Soap的情况下,tcpTrace实际是就是充当了Intermediary Node的角色。

我们现在就来介绍如果使用tcpTrace

假设我们在Local host有一个Calculator Service, EndpointAddressUri为:http://localhost:8888/CalculatorPort8888)。为了使大家有一个具体的认识,我给出了HostServiceconfiguration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="Artech.ExceptionHandling.Service.CalculatorService">
                <endpoint binding="wsHttpBinding" contract="Artech.ExceptionHandling.Contract.ICalculator" address="http://localhost:8888/Calculator" />                
            </service>
        </services>
    </system.serviceModel>
</configuration>

在一般的情况下,Client具有下面一段对应的ConfigurationPort8888

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:8888/Calculator"                binding="wsHttpBinding" contract="Artech.ExceptionHandling.Contract.ICalculator"
                name="defualtEndpoint" />
        </client>
    </system.serviceModel>
</configuration>

上面实际上是Client直接和Service进行交互的方式。现在我们需要做的是,先把Soap发送给tcpTracetcpTrace进行Soap trace之后再把Soap Message传到真正的Service。就需要一个特殊的Client端的Endpoint BehaviorClientViaBehavior。假设tcpTrace进行监听的Port8080,那么Client实现了ClientViaBehaviorconfiguration将会是如下的样子:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="calculatorEndpointBehavior">
                    <clientVia viaUri="http://localhost:8080/Calculator" />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint address="http://localhost:8888/Calculator" behaviorConfiguration="calculatorEndpointBehavior"
                binding="wsHttpBinding" contract="Artech.ExceptionHandling.Contract.ICalculator"
                name="defualtEndpoint" />
        </client>
    </system.serviceModel>
</configuration>

我们现在就可以来进行Soap Trace了,现在我们启动tcpTrace。进行如下的设置,Destination ServerDestination PortService Endpoint对应的HostPort。我们甚至还可以通过Log文件把Trace保存起来。


然后先后运行ServiceClient,你将会在tcpTrace上看到他所截获的RequestResponse的内容:


而且相应的内容被记录到我们指定的Log文件中:

 

10): 如何在WCF进行Exception Handling

 

在任何Application的开发中,对不可预知的异常进行troubleshooting时,异常处理显得尤为重要。对于一般的.NET系统来说,我们简单地借助try/catch可以很容易地实现这一功能。但是对于 一个分布式的环境来说,异常处理就没有那么简单了。按照面向服务的原则,我们把一些可复用的业务逻辑以Service的形式实现,各个Service处于一个自治的环境中,一个Service需要和另一个Service进行交互,只需要获得该Service的描述(Description)就可以了(比如WSDLSchemaStrategy)。借助标准的、平台无关的通信构架,各个Service之间通过标准的Soap Message进行交互。Service DescriptionStandard Communication InfrastructureSoap Message based Communication促使各Service以松耦合的方式结合在一起。但是由于各个Service是自治的,如果一个Service调用另一个Service,在服务提供方抛出的Exception必须被封装在Soap Message中,方能被处于另一方的服务的使用者获得、从而进行合理的处理。下面我们结合一个简单的Sample来简单地介绍我们可以通过哪些方式在WCF中进行Exception Handling

一、传统的Exception Handling

我们沿用我们一直使用的Calculator的例子和简单的4层构架:

 

http://www.cnblogs.com/artech/archive/2007/06/15/784090.html

 

11):再谈WCF的双向通讯-基于Http的双向通讯 V.S. 基于TCP的双向通讯

 

在一个基于面向服务的分布式环境中,借助一个标准的、平台无关的Communication Infrastructure,各个Service通过SOAP Message实现相互之间的交互。这个交互的过程实际上就是Message Exchange的过程。WCF支持不同形式的Message Exchange,我们把这称之为Message Exchange PatternMEP, 常见的MEP包括: Request/ReplyRequest/ForgetOne-way)和Duplex。通过采用Duplex MEP,我们可以实现在ServiceCallback Client的操作。虽然WCF为我们实现底层的通信细节,使得我们把精力转移到业务逻辑的实现,进行Transport无关的编程,但是对底层Transport的理解有利于我们根据所处的具体环境选择一个合适的Transport。说到Transport WCF 经常使用的是以下4个:HttpTCPNamed PipeMSMQ由于不同协议自身的差异,他们对具体MEP的支持方式也会不同,我们今天就来谈谈HttpTCPDuplex的支持。

一、Sample(WCF"Smaples"DuplexWCF)

为了使大家对在WCF如何实现双向通信(Bidirectional Communication)有一个直观的理解,我们先来看一个简单的Sample。我们照例采用下面的4层结构和Calculator的例子:

 

通过运行程序:

2. 基于Http的双向通讯V.S.基于TCP的双向通讯

由于HttpTCP在各自协议上的差异,他们实现双向通信的发式是不同的。

Http是一个应用层的协议,它的主要特征就是无连接和无状态(connectless & stateless )。它采用传统的Request/Reply的方式进行通信,Client发送Http Request请求Server的某个资源,Server端接收到该Http Request, 回发对应的Http Response。当Client端接收到对应的Response,该Connection会关闭。也就是说ClientServerConnection仅仅维持在发送Request到接收到Response这一段时间内。同时,每次基于Http connection是相互独立,互不相干的,当前connection无法获得上一次connection的状态。为了保存调用的的状态信息,ASP.NET通过把状态信息保存在Server端的方式实现了对Session的支持,具体的做法是:ASP.NET为每个Session创建一个Unique ID,与之关联一个HttpSessionState对象,并把状态信息保存在内存中或者持久的存储介质(比如SQL Server)中。而WCF则采用另外的方式实现对Session的支持:每个Session关联到某个Service Instance上。

回到我们WCF双向通信的问题上,当Client调用Service之前,会有一个EndpointClient端被创建,用于监听Service端对它的RequestClientService的调用会建立一个ClientServerConnection,当Service在执行操作过程中需要Callback对应的Client,实际上会建立另一个ServiceClientHttp connection。虽然我们时候说WCF为支持双向通信提供Duplex Channel,实际上这个Duplex channel是由两个Request/Reply Channel组成的。

而对于TCP/IP簇中的传输层协议TCP,它则是一个基于Connection的协议,在正式进行数据传输的之前,必须要在ClientServer之后建立一个ConnectionConnection的建立通过经典的“3次握手来实现。TCP天生就具有Duplex的特性,也就是说当Connection被创建之后,从ClientSever,和从ServerClient的数据传递都可以利用同一个Connection来实现。对于WCF中的双向通信,Client调用ServiceService Callback Client使用的都是同一个Connection、同一个Channel。所以基于TCPDuplex Channel才是真正意义上的Duplex Channel

 

12):使用MSMQ进行Reliable Messaging

 

一、为什么要使用MSMQ

在一个分布式的环境中,我们往往需要根据具体的情况采用不同的方式进行数据的传输。比如在一个Intranet内,我们一般通过TCP进行高效的数据通信;而在一个Internet的环境中,我们则通常使用Http进行跨平台的数据交换。而这些通信方式具有一个显著的特点,那就是他们是基于Connection的,也就是说,交互双方在进行通信的时候必须保证有一个可用的Connection存在于他们之间。而在某些时候,比如那些使用拨号连接的用户、以及使用便携式计算机的用户,我们不能保证在他们和需要访问的Server之间有一个的可靠的连接,在这种情况下,基于Messaging Queue的连接就显得尤为重要了。我们今天就来谈谈在WCF中如何使用MSMQ

MSMQ不仅仅是作为支持客户端连接工具而存在,合理的使用MSMQ可以在很大程度上提升系统的PerformanceScalability我们先来看看MSMQ能给我们带来怎样的好处:

1MSMQ是基于Disconnection

MSMQ通过Message Queue进行通信,这种通信方式为离线工作成为了可能。比如在介绍MSMQ时都会提到的Order Delivery的例子:在一个基于B2C的系统中,订单从各种各样的客户传来,由于 客户的各异性,不能保证每个客户在每时每刻都和用于接收订单的Server保持一个可靠的连接,我们有时候甚至允许客户即使在离线的情况下也可以递交订单(虽然订单不能发送到订单的接收方,但是我们可以通过某种机制保证先在本地保存该订单,一旦连接建立,则马上向接收方递交订单),而MSMQ则有效地提供了这样的机制:Server端建立一个Message Queue来接收来个客户的订单,客户端通过向该Message Queue发送承载了订单数据的Message实现订单的递交。如果在客户离线的情况下,他仍然可以通过客户端程序进行订单递交的操作,存储着订单数据的Message会被暂时保存在本地的Message Queue中,一旦客户联机,MSMQMessage从中取出,发送到真正的接收方,而这个动作对于用户的透明的。

2MSMQ天生是One-way、异步的

MSMQ中,Message始终以One-way的方式进行发送,所以MSMQ具有天生的异步特性。所以MSMQ使用于那些对于用户的请求,Server端无需立即响应的场景。也就是说Server对数据的处理无需和Client的数据的发送进行同步,它可以独自地按照自己的Schedule进行工作。这可以避免峰值负载。比如Server端可以在一个相对低负载的时段(比如深夜)来对接收到的Order进行批处理,而无需一天24小时一直进行Order的监听、接收和处理。

3MSMQ能够提供高质量的Reliable Messaging

我们知道,在一般的情况下,如果Client端以异步的方式对Service进行调用就意味着:Client无法获知Message是否成功抵达Service端;也不会获得Service端执行的结果和出错信息。但是我们仍然说MSMQ为我们提供了可靠的传输(Reliable Messaging),这主要是因为MSMQ为我们提供一些列Reliable Messaging的机制:

超时机制(Timeout):可以设置发送和接收的时间,超出该时间则被认为操作失败。

确认机制(Acknowledgement):当Message成功抵达Destination Queue,或者被成功接收,向发送端发送一个Acknowledgement message用以确认操作的状态。

日志机制(Journaling):当Message被发送或接收后,被Copy一份存放在Journal Queue中。

此外,MSMQ还提供了死信队列(Dead letter Queue)用以保存发送失败的message。这一切保证了保证了Reliable Messaging

二、 MSMQWCF的运用

WCF中,MSMQ提供的数据传输功能被封装在一个Binding中,提供WCF Endpoint之间、以及Endpoint和现有的基于MSMQApplication进行通信的实现。为此WCF为我们提供了两种不同的built-in binding

NetMsmqBinding:从提供的功能和使用 方式上看,NetMsmqBinding和一般使用的binding,比如basicHttpBindingnetTcpBinding没有什么区别:在两个Endpoint之间实现了数据的通信,所不同的是,它提供的是基于MSMQReliable Messaging。从变成模式上看,和一般的binding完全一样。

MsmqIntegrationBinding:从命名上我们可以看出,MsmqIntegrationBinding主要用于需要将我们的WCF Application和现有的基于MSMQApplication集成的情况。MsmqIntegrationBinding实现了WCF Endpoint和某个Message Queue进行数据的通信,具体来说,就是实现了单一的向某个Message Queue 发送Message,和从某个Message Queue中接收Message的功能。从编程模式上看,也有所不同,比如Operation只接收一个MsmqMessage<T>的参数。

这是ClientService通信的图示:

 

三、MSMQTransaction

MSMQ提供对Transaction的支持。在一般的情况下,MSMQ通过Message Queue Transaction实现对Transaction的原生的支持,借助Message Queue Transaction,可以把基于一个或多个Message Queue的相关操作纳入同一个Transaction中。

Message Queue Transaction仅仅限于基于Message Queue的操作,倘若操作涉及到另外一些资源,比如SQL Server, 则可以使用基于DTC的分布式Transaction

对于WCFMSMQ,由于ClientService的相对独立(可能Client发送MessageService处理Message会相隔很长一段时间),所以ClientService的操作只能纳入不同的Transaction中,如下图。

四、Sample1NetMsmqBinding(WCF"Smaples"QueuedService)

我们首先做一个基于NetMsmqBinding Sample,实现的功能就是我们开篇所提出的Order Delivery。我们说过,NetMsmqBinding和一般的binding在实现的功能和变成模式上完全一样。下面是我们熟悉的4层结构:

 

先后运行HostClientHost端有下面的输出:

 

 

13):创建基于MSMQResponsive Service

 

一、One-way MEP V.S. Responsible Service

我们知道MSMQ天生就具有异步的特性,它只能以One-wayMEPMessage Exchange Pattern)进行通信。ClientService之间采用One-way MEP的话就意味着Client调用Service之后立即返回,它无法获得Service的执行结果,也无法捕捉Service运行的Exception。下图简单表述了基于MSMQWCF ServiceClientService的交互。


但是在有些场景 中,这是无法容忍的。再拿我在上一篇文章的Order Delivery的例子来说。ClientService提交了Order,却无法确认该Order是否被Service正确处理,这显然是不能接受的。我们今天就来讨论一下,如何创建一个Responsive Service来解决这个问题:Client不再是对Service的执行情况一无所知,它可以获知Order是否被Service正确处理了。

二、 Solution

虽然我们的目的很简单:当ClientService递交了Order之后,能以某种方式获知Order的执行结果;对于Service端来说,在正确把OrderMessage Queue中获取出来、并正确处理之后,能够向Order的递交者发送一个Acknowledge Message。为了简单起见,这个Acknowledge Message包含两组信息:

Order No.: 被处理的Order的一个能够为一标志它的ID

Exception: 如果处理失败的Exception,如果成功处理为null

要在WCF中实现这样的目的,对于Request/Reply MEP来说是简单而直接的:ClientService递交Order,并等待ServiceResponseService在处理接收到Order之后直接将处理结果 返回给Client就可以了。但是我们说过MSMQ天生就是异步的,我们只有采取一种间接的方式实现曲线救国

我们的解决方案是:在每个Client Domain也创建一个基于MSMQ的本地的WCF Service,用于接收来自Order处理端发送的Acknowledge Message。对于处理Order Service来说,在正确处理Order之后,想对应的Client发送Acknowledge Message。下图简单演示整个过程:


三、Implementation

了解了上面的Solution之后,我们来看看该Solution在真正实现过程中有什么样的困难。对于处理OrderService来说,在向Client端发送Acknowledge Message的时候,它必须要知道该Order对应的ClientResponse ServiceMSMQAddress以及其他和Operation相关的Context信息(在这里我们不需要,不过考虑到扩展性,我们把包括了addressContext的信息 封装到一个了Class中,在这里叫做:OrderResponseContext)。而这些Context却不能在Configuration中进行配置,因为他可以同时面临着很多个Client:比如每个Client用于接收Response Message Queueaddress都不一样。所以这个OrderResponseContext必须通过对应的Client来提供。基于此,我们具有两面两种解决方式:

方式一、修改Service Contract,把OrderResponseContext当成是Operation的一个参数

这是我们最容易想到的,比如我们原来的Operation这样定义:

namespace Artech.ResponsiveQueuedService.Contract
{
    [ServiceContract]
    [ServiceKnownType(typeof(Order))]
    public interface IOrderProcessor
     {
        [OperationContract(IsOneWay = true)]
        void Submit(Order order);
    }
}

现在变成:

namespace Artech.ResponsiveQueuedService.Contract
{
    [ServiceContract]
    [ServiceKnownType(typeof(Order))]
    public interface IOrderProcessor
     {
        [OperationContract(IsOneWay = true)]
        void Submit(Order order, OrderResponseContext responseContext);
    }
}
虽然这种方式看起来不错,但是却不值得推荐。在一般情况下,我们的Contract需要是很稳定的,一经确定就不能轻易更改,因为Contract是被交互的多方共同支持的,牵一发动全身;此外,从Service Contract代表的是Service的一个Interface,他是对业务逻辑的抽象、和具体实现无关,而对于我们的例子来说,我们仅仅是定义一个递交OrderOperation,从业务逻辑来看,OrderResponseContext和抽象的业务逻辑毫无关系。基于此,我们需要寻求一种和Service Contract无关的解决方式:

方式二、将OrderResponseContext放到Soap Message Header

其实我们要解决的问题很简单,就是要把OrderResponseContext的信息置于Soap Message中发送到Service。而我们知道,SoapHeader具有极强的可伸缩性,原则上,我们可以把任何控制信息置于Header中。基于WCF的编程模式很容易地帮助我们实现对Soap Header的插入和获取:

我们可以通过下面的方式获得当前Operation ContextIncoming Message HeadersOutgoing Message Headers

OperationContext.Current.IncomingMessageHeaders
OperationContext.Current.OutgoingMessageHeaders

如果我们要把一个OrderResponseContext 对象插入到当前Operation ContextOutgoing Message Headers中,我们可以通过下面的代码来实现:

OrderResponseContext context = new OrderResponseContext();
MessageHeader<OrderResponseContext> header = new MessageHeader<OrderResponseContext>( context);
OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader("name", "namespace"));

相应的,我们可以通过下面的代码从Outgoing Message Headers OrderResponseContext的数据获取的内容:

OrderResponseContext context = OperationContext.Current.IncomingMessageHeaders.GetHeader<OrderResponseContext>("name", "namespace"));

四、Sample(WCF"Smaples"ResponsiveQueuedService)

我们照例给出一个完整的Sample,下面是整个Solution的结构:


除了一贯使用的4层结构(Contract-Service-Hosting-Client),还为ResponseService增加了下面两层:

Localservice: 作为Client DomainResponseService

LocalHostingHost Localservice

我们现在运行一下整个程序,看看最终的输出结果:

Client


Order Processing


Order Response

Reference
Build a Queued WCF Response Service

 

posted on 2009-06-28 20:30  brake  阅读(580)  评论(0编辑  收藏  举报

我的最愛 聯繫我們 無障礙