WCF教程

WindowsCommunicationFoundation入门(PartOne
前言:WCF是微软基于SOAServiceOrientedArchitecture)推出的.Net平台下的框架
产品,它代表了软件架构设计与开发的一种发展方向,在微软的战略计划中也占有非常重要的地位。了解和掌握WCF,对于程序员特别是基于微软产品开发的程序员而言,是非常有必要的。对于WCF,笔者也是初窥门径,抱着学习的态度作这样的一个介绍。文中的内容主要参考了微软官方的文档、资料,以及众多介绍WCF的技术资料。这些资料主要都是英文,不便于国内程序员学习WCF。虽然本人才疏学浅,却愿意作这样的介绍者。由于自己仅是一个初学者,英文的功底也不够深厚,所以文中难免会有疏漏之处。同时,我也希望在文中尽量表达出自己的一些心得与见解,这就不免增加了出现错误的可能性。此外,由于WCF至今仍未有正式的版本,文中相关的技术描述以及代码会根据版本的不同而发生变化,所以我也只能尽量对此给与一定的说明。本文会是多篇文章拼凑在一起的系列,说是系列,但并没有严格的渐进关系,只是整体上希望能有一个相对全面的WCF入门介绍。此外,笔者也希望能通过此文抛砖引玉,这样也能让我WCF学习之旅更轻松一点。一、什么是WCF
根据微软官方的解释,WCF(之前的版本名为“Indigo”是使用托管代码建立和运行面向服务(ServiceOriented)应用程序的统一框架。它使得开发者能够建立一个跨平台的安全、可信赖、事务性的解决方案,且能与已有系统兼容协作。WCF是微软分布式应用程序开发的集大成者,它整合了.Net平台下所有的和分布式系统有关的技术,例如.NetRemotingASMXWSEMSMQ。以通信(Communiation范围而论,它可以跨进程、跨机器、跨子网、企业网乃至于Internet;以宿主程序而论,可以以ASP.NETEXEWPFWindowsFormsNTServiceCOM+作为宿主(HostWCF可以支持的协议包括TCPHTTP,跨进程以及自定义,安全模式则包括SAMLKerberosX509,用户/密码,自定义等多种标准与模式。也就是说,在WCF框架下,开发基于SOA的分布式系统变得容易了,微软将所有与此相关的技术要素都包含在内,掌握了WCF,就相当于掌握了叩开SOA大门的钥匙。
WCF是建立在.NetFramework2.0基础之上的,正式的版本应该会作为WindowsVista的核心部分而Release。然而,这并不代表WCF只能运行在WindowsVista下。只要安装了WinFXRuntimeComponents,在WindowsXPWindows2003操作系统下,仍然可以使用。VisualStudio2005中并没有包含WCF,但是当安装好了WinFXRuntime

Components后,我们就可以在VisualStudio2005环境下开发和创建WCF的程序了。目前最新的WCF版本是February2006CTP,下载页面是:
http://www.microsoft.com/downloads/details.aspx?FamilyId=F51C4D96-9AEA-474F-86D3-172BFA3B828B&displaylang=en。使用WCF需要用到一些相关的工具,如SvcUtil.exe,所以还需要下载WinFXRuntimeComponentsSDK,其下载页面是:http://www.microsoft.com/downloads/details.aspx?FamilyId=9BE1FC7F-0542-47F1-88DD-61E3EF88C402&displaylang=en。安装SDK可以选择网络安装或本地安装。如果是本地安装,文件大小为1.1G左右,是ISO文件。安装了SDK后,在programfiles录下,有microsoftSDK目录。
WCF是微软重点介绍的产品,因此也推出了专门的官方网站
http://windowscommunication.net),该网站有最新的WCF新闻发布,以及介绍WCF的技术文档和样例代码。
二、WCF的优势
DavidChappell所撰的《IntroducingWindowsCommunicationFoundation》一文中,用了一个活鲜鲜的例子,来说明WCF的优势所在。假定我们要为一家汽车租赁公司开发一个新的应用程序,用于租车预约服务。该租车预约服务会被多种应用程序访问,包括呼叫中心(CallCenter基于J2EE的租车预约服务以及合作伙伴的应用程序PartnerApplication如图所示:


呼叫中心运行在Windows平台下,是在.NetFramework下开发的应用程序,用户为公司员工。由于该汽车租赁公司兼并了另外一家租赁公司,该公司原有的汽车预约服务应用程序是J2EE应用程序,运行在非Windows操作系统下。呼叫中心和已有的汽车预约应用程序都运行Intranet环境下。合作伙伴的应用程序可能会运行在各种平台下,这些合作伙伴包括旅行社、航空公司等等,他们会通过Internet来访问汽车预约服务,实现对汽车的租用。
这样一个案例是一个典型的分布式应用系统。如果没有WCF,利用.Net现有的技术应该如何开发呢?
首先考虑呼叫中心,它和我们要开发的汽车预约服务一样,都是基于.NetFramework的应用程序。呼叫中心对于系统的性能要求较高,在这样的前提下,.NetRemoting是最佳的实现技术。它能够高性能的实现.Net.Net之间的通信。
要实现与已有的J2EE汽车预约应用程序之间的通信,只有基于SOAPWebService以实现此种目的,它保证了跨平台的通信;而合作伙伴由于是通过Internet来访问,利用ASP.NetWebService,即ASMX,也是较为合理的选择,它保证了跨网络的通信。由于涉及到网络之间的通信,我们还要充分考虑通信的安全性,利用WSEWebService

Enhancements)可以为ASMX提供安全的保证。
一个好的系统除了要保证访问和管理的安全,高性能,同时还要保证系统的可信赖性。因此,事务处理是企业应用必须考虑的因素,对于汽车预约服务而言,同样如此。.Net中,EnterpriseServiceCOM+提供了对事务的支持,其中还包括分布式事务DistributedTransactions不过对于EnterpriseService而言,它仅支持有限的几种通信协议。
如果还要考虑到异步调用、脱机连接、断点连接等功能,我们还需要应用MSMQMicrosoftMessageQueuing),利用消息队列支持应用程序之间的消息传递。
如此看来,要建立一个好的汽车租赁预约服务系统,需要用到的.Net分布式技术,包括.NetRemotingWebServiceCOM+等五种技术,这既不利于开发者的开发,也加大了程序的维护难度和开发成本。正是因应于这样的缺陷,WCF才会在.Net2.0中作为全新的分布式开发技术被微软强势推出,它整合了上述所属的分布式技术,成为了理想的分布式开发的解决之道。下图展示了WCF与之前的相关技术的比较:


从功能的角度来看,WCF完全可以看作是ASMX.NetRemotingEnterpriseServiceWSEMSMQ等技术的并集。(注:这种说法仅仅是从功能的角度。事实上WCF远非简单的并集这样简单,它是真正面向服务的产品,它已经改变了通常的开发模式。)因此,对于上述汽车预约服务系统的例子,利用WCF,就可以解决包括安全、可信赖、互操作、跨平台通信等等需求。开发者再不用去分别了解.NetRemotingASMX等各种技术了。

概括地说,WCF具有如下的优势:1、统一性
前面已经叙述,WCF是对于ASMX.NetRemotingEnterpriseServiceWSEMSMQ等技术的整合。由于WCF完全是由托管代码编写,因此开发WCF的应用程序与开发其它的.Net应用程序没有太大的区别,我们仍然可以像创建面向对象的应用程序那样,利用WCF来创建面向服务的应用程序。2、互操作性
由于WCF最基本的通信机制是SOAP,这就保证了系统之间的互操作性,即使是运行不同的上下文中。这种通信可以是基于.Net.Net间的通信,如下图所示:


可以跨进程、跨机器甚至于跨平台的通信,只要支持标准的WebService,例如J2EE应用服务器(如WebSphereWebLogic)。应用程序可以运行在Windows操作系统下,也可以运行在其他的操作系统,如SunSolarisHPUnixLinux等等。如下图所示:



3、安全与可信赖
WS-SecurityWS-TrustWS-SecureConversation均被添加到SOAP消息中,以用于用户认证,数据完整性验证,数据隐私等多种安全因素。
SOAPheader中增加了WS-ReliableMessaging允许可信赖的端对端通信。而建立在WS-CoordinationWS-AtomicTransaction之上的基于SOAP格式交换的信息,则支持两阶段的事务提交(two-phasecommittransactions)。
上述的多种WS-PolicyWCF中都给与了支持。对于Messaging而言,SOAPWebService的基本协议,它包含了消息头(header)和消息体(body。在消息头中,定义了WS-Addressing用于定位SOAP消息的地址信息,同时还包含了MTOM(消息传输优化机制,MessageTransmissionOptimizationMechanism)。如图所示:



4、兼容性
WCF充分的考虑到了与旧有系统的兼容性。安装WCF并不会影响原有的技术如ASMX.NetRemoting。即使对于WCFASMX而言,虽然两者都使用了SOAP,但基于WCF开发的应用程序,仍然可以直接与ASMX进行交互。
WindowsCommunicationFoundation入门(PartTwo
三、WCF的技术要素
作为基于SOAServiceOrientedArchitecture)的一个框架产品,WCF最重要的就是能够快捷的创建一个服务(Service)。如下图所示,一个WCFService由下面三部分构成:


1ServiceClass一个标记了[ServiceContract]Attribute的类,在其中可能包含多个方法。除了标记了一些WCF特有的Attribute外,这个类与一般的类没有什么区别。

2Host(宿主):可以是应用程序,进程如WindowsService等,它是WCFService运行的环境。
3Endpoints:可以是一个,也可以是一组,它是WCF实现通信的核心要素。
WCFService由一个Endpoints集合组成,每个Endpoint就是用于通信的入口,客户端和服务端通过Endpoint交换信息,如下图所示:

从图中我们可以看到一个Endpoint由三部分组成:AddressBindingContract便于记忆,我们往往将这三部分称为是EndpointABCs
AddressEndpoint的网络地址,它标记了消息发送的目的地。Binding描述的是如何发送消息,例如消息发送的传输协议(如TCPHTTP安全(如SSLSOAP消息安全)Contract则描述的是消息所包含的内容,以及消息的组织和操作方式,例如是one-wayduplexrequest/reply。所以Endpoint中的ABCs分别代表的含义就是:wherehowwhat。当WCF发送消息时,通过address知道消息发送的地址,通过binding知道怎样来发送它,通contract则知道发送的消息是什么。
WCF中,类ServiceEndpoint代表了一个Endpoint,在类中包含的EndpointAddressBindingContractDescription类型分别对应EndpointAddressBindingContract如下图:


EndpointAddress类又包含URIIdentity和可选的headers集合组成,如下图:


Endpoint安全的唯一性识别通常是通过其URI的值,但为了避免一些特殊情况造成URI的重复,又引入了Identity附加到URI上,保证了Endpoint地址的唯一性。至于可选的AddressHeader则提供了一些附加的信息,尤其是当多个Endpoint在使用同样的URI地址信息时,AddressHeader就非常必要了。
Binding类包含NameNamespaceBindingElement集合,如下图:



BindingName以及Namespace是服务元数据(service’smetadata)的唯一标识。BindingElement描述的是WCF通信时binding的方式。例如,SecurityBindingElement表示Endpoint使用SOAP消息安全方式,而ReliableSessionBindingElement表示Endpoint利用可信赖消息确保消息的传送。TcpTransportBindingElement则表示Endpoint利用TCP作为通信的传输协议。每种BindingElement还有相应的属性值,进一步详细的描述WCF通信的方式。
BindingElement的顺序也非常重要。BindingElement集合通常会创建一个用于通信的堆栈,其顺序与BindingElement集合中元素顺序一致。集合中最后一个bindingelement对应于通信堆栈的底部,而集合中的第一个bindingelement则对应于堆栈的顶端。入消息流的方向是从底部经过堆栈向上,而出消息流的方向则从顶端向下。因此,BindingElement集合中的bindingelement顺序直接影响了通信堆栈处理消息的顺序。幸运的是,WCF已经提供了一系列预定义的Binding,能够满足大多数情况,而不需要我们自定义Binding,殚精竭虑地考虑bindingelement的顺序。
Contract是一组操作(Operations)的集合,该操作定义了Endpoint通信的内容,每个Operation都是一个简单的消息交换(messageexchange),例如one-way或者request/reply消息交换。
ContractDescription用于描述WCFContracts以及它们的操作operations。在ContractDescription类中,每个Contractoperation都有相对应的

OperationDescription用于描述operation的类型,例如是one-way还是request/replyOperationDescription中还包含了MessageDecription集合用于描述messageWCF编程模型中,ContractDescription通常是在定义Contract的接口或类中创建。对于这个接口或类类型,标记以ServiceContractAttribute,而其Operation方法则标记以OperationContractAttribute。当然我们也可以不利用CLRattribute,而采用手工创建。Binding一样,每个Contract也包含有NameNamespace,用于在Service的元数据中作为唯一性识别。此外,Contract中还包含了ContractBehavior的集合,ContractBehavior类型可以用于修改或扩展contract的行为。类ContractDescription的组成如下图所示:

正如在ContractDescription中包含的IContractBehavior一样,WCF专门提供了行为Behavior,它可以对客户端和服务端的一些功能进行修改或者扩展。例如
ServiceMetadataBehavior用于控制Service是否发布元数据。相似的,securitybehavior用于控制安全与授权,transactionbehavior则控制事务。

除了前面提到的ContractBehavior,还包括ServiceBehaviorChannelBehaivorServiceBehavior实现了IServiceBehavior接口,ChannelBehaivor则实现了IChannleBehavior接口。
由于WCF需要管理的是服务端与客户端的通信。对于服务端,WCF提供了类
ServiceDescription用于描述一个WCFService,;而针对客户端,WCF管理的是发送消息时需要使用到的通道Channel,类ChannelDescription描述了这样的客户端通道。ServiceDescription类的组成如下图所示:

我们可以利用代码的方式创建ServiceDescription对象,也可以利用WCFAttribute,或者使用工具SvcUtil.exe。虽然可以显式的创建它,但通常情况下,它会作为运行中的Service一部分而被隐藏于后(我在后面会提到)。
ChannelDescription类的组成与ServiceDescription大致相同,但它仅仅包含了一个ServiceEndpoint,用于表示客户端通过通道通信的目标Endpoint。当然,施加到ChannelDescriptionBehavior也相应的为IChannelBehavior接口类型,如图所示:


定义一个WCFService非常简单,以HelloWorld为例,定义的Service可能如下:usingSystem.ServiceModel[ServiceContract]publicclassHelloWorld{
[OperationContract]publicvoidHello({
Console.WriteLine(“HelloWorld!”;}}
System.ServiceModel是微软为WCF提供的一个新的类库,以用于面向服务的程序设计。开发WCF应用程序时,需要先添加对System.ServiceModel的引用。WCF中的大部分类和接口也都是在命名空间System.ServiceModel下。

我们为HelloWorld类标记了[ServiceContract],这就使得该类成为了一个WCFService而其中的方法Hello(则因为标记了[OperationContract],而成为该Service的一个Operation
不过WCF推荐的做法是将接口定义为一个Service这使得WCFService具有更好的灵活性,毕竟对于一个接口而言,可以在同时有多个类实现该接口,这也就意味着可以有多个ServiceContract的实现。那么上面的例子就可以修改为:[ServiceContract]publicinterfaceIHello{
[OperationContract]voidHello(;}
而类HelloWorld则实现该IHello接口:publicclassHelloWorld:IHello{
publicvoidHello({
Console.WriteLine(“HelloWorld!”;}}
注意在实现了IHello接口的类HelloWorld中,不再需要在类和方法中标注ServiceContractAttributeOperationContractAttribute了。
前面我已经提过,一个WCFService必须有host作为它运行的环境。这个host可以是ASP.Net,可以是WindowsService,也可以是一个普通的应用程序,例如控制台程序。下面就是一个Host的实现:usingSystem.ServiceModelpublicclassHostApp{

staticvoidMain(string[]args{
ServiceHosthost=newServiceHost(typeof(HelloWorld,newUri(“http://localhost:8080/HelloService”;
host.AddServiceEndpoint(typeof(IHello,newBasicHttpBinding(,”Svc”;host.Open(;
Console.WriteLine(“StartYourService.”;Console.ReadKey(;host.Close(;}}
在这个HostApp中,我们为HelloWorld创建了一个ServiceHost对象。通过它就可以创建WCF运行时(Runtime),WCFRuntime是一组负责接收和发送消息的对象。ServiceHost可以创建SerivceDescription对象,利用SerivceDescriptionSercieHost为每一个ServiceEndpoint创建一个EndpointListenerServiceHost的组成如下图:


EndpointListener侦听器包含了listeningaddressmessagefilteringdispatch,它们对应ServiceEndpoint中的EndpointAddressContractBindingEndpointListener中,还包含了一个ChannelStack,专门负责发送和接收消息。
注意在创建ServiceHost时,传递的type类型参数,不能是interface。因此,我在这里传入的是typeof(HelloWorldServiceHost类的AddServiceEndpoint(方法实现了为Host

添加Endpoint的功能,其参数正好是Endpoint的三部分:AddressBingdingContract(此时的IHello即为ServiceContract,其方法HelloOperationContract)。ServiceHostOpen(方法用于创建和打开Service运行时,而在程序结束后我又调用了Close(方法,来关闭这个运行时。实际上以本例而言,该方法可以不调用,因为在应用程序结束后,系统会自动关闭该host。但作为一种良好的编程习惯,WCF仍然要求显式调用Close(方法,因为Service运行时其本质是利用Channel来完成消息的传递,当打开一个Service行时的时候,系统会占用一个Channel,调用完后,我们就需要释放对该通道的占用。当然我们也可以用using语句来管理ServiceHost资源的释放。
定义好了一个WCFService,并将其运行在Host上后,如何实现它与客户端的通信呢?典型的情况下,服务端与客户端均采用了WebServiceDescriptionLanguageWSDL),客户端可以通过工具SvcUtil.exe生成对应于该WCFServiceProxy代码,以完成之间的消息传递,如图所示:

SvcUtil.exe是由WinFxRuntimeComponentSDK所提供的,如果安装SDK正确,可以在其中找到该应用工具。生成客户端Proxy代码的方法很简单,首先需要运行服务端Service然后再命令行模式下运行下面的命令:
SvcUtilhttp://localhost:8080/HelloService
这样会在当前目录下产生两个文件output.csoutput.config。前者最主要的就是包含了一个实现了IHello接口的Proxy对象,这个代理对象名为HelloProxy,代码生成的结果如下:[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0"]
publicpartialclassHelloProxy:System.ServiceModel.ClientBase,IHello{

publicHelloProxy({}
publicHelloProxy(stringendpointConfigurationName:base(endpointConfigurationName{}
publicHelloProxy(stringendpointConfigurationName,stringremoteAddress:base(endpointConfigurationName,remoteAddress{}
publicHelloProxy(stringendpointConfigurationName,System.ServiceModel.EndpointAddressremoteAddress:base(endpointConfigurationName,remoteAddress{}
publicHelloProxy(System.ServiceModel.Channels.Bindingbinding,System.ServiceModel.EndpointAddressremoteAddress:base(binding,remoteAddress{}
publicvoidHello({
base.InnerProxy.Hello(;}}
(注:本程序在WinFx2006FebruaryCTP版本下运行通过)
至于后者,则是WCFService的配置信息,主要包含的是EndpointAddressBindingContract的配置(在后续文章我会详细介绍)。

现在客户端就可以直接使用HelloProxy对象,来完成与服务端的通信了:publicclassClientApp{
staticvoidMain(string[]args{
using(HelloProxyproxy=newHelloProxy({
proxy.Hello(;}
Console.ReadKey(;}}
除了可以使用SvcUtil工具产生客户端代码,同样我们也可以利用代码的方式来完成客户端。户端在发送消息给服务端时,其通信的基础是ServiceEndpointWCF提供了
System.ServiceModel.Description.ServiceEndpoint类,通过创建它来实现两端的通信。在前面,我还提到对于客户端而言,WCF管理的是发送消息时需要使用到的通道Channel”为此,WCF提供了ChannelFactory(其命名空间为System.ServiceModel.Channel),专门用于创建客户端运行时(runtime)。ChannelFactoryServiceHost相对应,它可以创ChannelDescription对象。与服务端ServiceHost不同的是,客户端并不需要侦听器,因为客户端往往是建立连接的发起方并不需要侦听进来的连接。因此客户端的ChannelStack会由ChannelDescription创建。
ChannelFactoryServiceHost都具有ChannelStack而服务端与客户端的通信又是通过channel来完成,这就意味着,利用ChannelFactory,客户端可以发送消息到服务端。而客户端本身并不存在Service对象,因此该ServiceProxy,是可以通过Channel来得到的。所以客户端的代码可以修改如下:usingSystem.ServiceModel;
usingSystem.ServiceModel.Description;usingSystem.ServiceModel.Channel

publicclassClientApp{
staticvoidMain(string[]args{
ServiceEndpointhttpEndpoint=new
ServiceEndpoint(ContractDescription.GetContract(typeof(IHello,newBasicHttpBinding(,new
EndpointAddress(“http://localhost:8080/HelloService/Svc”;using(ChannelFactoryfactory=newChannelFactory(httpEndPoint{
//创建IHello服务的代理对象;
IHelloservice=factory.CreateChannel(;service.Hello(;}
Console.ReadKey(;}}
(注:本程序在WinFx2006FebruaryCTP版本下运行通过)对于上面的代码,我们有两点需要注意:
1、采用这种方式,前提条件是客户端能够访问IHello接口。这也印证了之前我所叙述的最好使用interface来定义Service的好处。此外,为了保证部署的方便,有关Serviceinterface最好单独编译为一个程序集,便于更好的部署到客户端。2、客户端必须知道服务端binding的方式以及address
对于服务端而言,我们也可以直接在浏览器中打开该Service,在地址栏中输入http://localhost:8080/HelloService,如下图:

点击链接:http://localhost:8080/HelloService?wsdl,我们可以直接看到HelloServiceWSDL注意到在这里我并没有使用IIS实际上WCF内建了对httpsys的集成,允许任何应用程序自动成为HTTPlistener
WindowsCommunicationFoundation入门(PartThree
WindowsCommunicationFoundation之旅》系列之三示例代码下载:DuplexSample.rar四、ServiceContract编程模型
PartTwo中,我以“HelloWorld”为例讲解了如何定义一个Service。其核心就是为接口或类施加ServiceContractAttribute,为方法施加OperationContractAttribute。在Service的方法中,可以接受多个参数,也可以有返回类型,只要这些数据类型能够被序列化。这样一种

方式通常被称为本地对象,远程过程调用(local-object,Remoting-Procedure-Call)方式,它非常利于开发人员快速地进行Service的开发。
ServiceContract编程模型中,还有一种方式是基于MessageContract的。服务的方法最多只能有一个参数,以及一个返回值,且它们的数据类型是通过MessageContract自定义的消息类型。在自定义消息中,可以为消息定义详细的HeaderBody,使得对消息的交换更加灵活,也更利于对消息的控制。
一个有趣的话题是当我们定义一个Service时,如果一个private方法被施加了
OperationContractAttribute,那么对于客户端而言,这个方法是可以被调用的。这似乎与private对于对象封装的意义有矛盾。但是这样的规定是有其现实意义的,因为对于一个服务而言,服务端和客户端的需求往往会不一致。在服务端,该服务对象即使允许被远程调用,但本地调用却可能会因情况而异。如下面的服务定义:[ServiceContract]publicclassBookTicket{
[OperationContract]
publicboolCheck(Ticketticket{
boolflag;
//logictocheckwhethertheticketisnone;returnflag;}
[OperationContract]
privateboolBook(Ticketticket{
//logictobooktheticket}}
在服务类BookTicket中,方法CheckBook都是服务方法,但后者被定义成为private法。为什么呢?因为对于客户而言,首先会检查是否还有电影票,然而再预定该电影票。也就是

说这两项功能都是面向客户的服务,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有可能被查询电影票、预定电影票、出售电影票等业务逻辑所调用。Book方法,则只针对远程客户,只可能被远程调用。为了保证该方法的安全,将其设置为private,使得本地对象不至于调用它。
因此在WCF中,一个方法是否应该被设置为服务方法,以及应该设置为public还是private都需要根据具体的业务逻辑来判断。如果涉及到私有的服务方法较多,一种好的方法是利用设计模式的Façade模式,将这些方法组合起来。而这些方法的真实逻辑,可能会散放到各自的本地对象中,对于这些本地对象,也可以给与一定的访问限制,如下面的代码所示:internalclassBusinessObjA{
internalvoidFooA({}}
internalclassBusinessObjB{
internalvoidFooB({}}
internalclassBusinessObjC{
internalvoidFooC({}}
[ServiceContract]internalclassFaçade{
privateBusinessObjAobjA=newBusinessObjA(;privateBusinessObjBobjB=newBusinessObjB(;privateBusinessObjCobjC=newBusinessObjC(;[OperationContract]privatevoidSvcA({

objA.FooA(;}
[OperationContract]privatevoidSvcB({
objB.FooB(;}
[OperationContract]privatevoidSvcC({
objC.FooC(;}}
方法FooAFooBFooC作为internal方法,拒绝被程序集外的本地对象调用,但SvcASvcBSvcC方法,却可以被远程对象所调用。我们甚至可以将BusinessObjA
BusinessObjB等类定义为Façade类的嵌套类。采用这样的方法,有利于这些特殊的服务方法,被远程客户更方便的调用。
定义一个Service最常见的还是显式地将接口定义为Service这样的方式使得服务的定义更加灵活,这一点,我已在PartTwo中有过描述。当然,采用这种方式,就不存在前面所述的私有方法成为服务方法的形式了,因为在一个接口定义中,所有方法都是public的。
另外一个话题是有关服务接口的继承一个被标记了[ServiceContract]的接口,在其继承链上,允许具有多个同样标记了[ServiceContract]的接口。对接口内定义的OperationContract方法,则是根据聚合的原则,如下的代码所示:[ServiceContract]publicinterfaceIOne{
[OperationContract(IsOneWay=true]voidA(;}

[ServiceContract]publicinterfaceITwo{
[OperationContract]voidB(;}
[ServiceContract]
publicinterfaceIOneTwo:IOne,ITwo{
[OperationContract]voidC(;}
在这个例子中,接口IOneTwo继承了接口IOneITwo。此时服务IOneTwo暴露的服务方法应该为方法ABC
然而当我们采用Duplex消息交换模式(文章后面会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF要求服务接口IB在继承另一个服务接口IA时,IB的回调接口IBCallBack必须同时继承IACallBack,否则会抛出InvalidContractException异常。正确的定义如下所示:
[ServiceContract(CallbackContract=IACallback]interfaceIA{}
interfaceIACallback{}
[ServiceContract(CallbackContract=IBCallback]interfaceIB:IA{}
interfaceIBCallback:IACallback{}
五、消息交换模式(MessageExchangePatternsMEPS
WCF中,服务端与客户端之间消息的交换共有三种模式:Request/ReplyOne-WayDuplex

1Request/Reply
这是默认的一种消息交换模式,客户端调用服务方法发出请求(Request),服务端收到请求后,进行相应的操作,然后返回一个结果值(Reply)。
如果没有其它特别的设置,一个方法如果标记了OperationContract,则该方法的消息交换模式就是采用的Request/Reply方式,即使它的返回值是void当然,我们也可以将IsOneWay设置为false,这也是默认的设置。如下的代码所示:[ServiceContract]
publicinterfaceICalculator{
[OperationContract]intAdd(inta,intb;[OperationContract]intSubtract(inta,intb;}
2One-Way
如果消息交换模式为One-Way则表明客户端与服务端之间只有请求,没有响应。即使响应信息被发出,该响应信息也会被忽略。这种方式类似于消息的通知或者广播。当一个服务方法被设置为One-Way时,如果该方法有返回值,会抛出InvalidOperationException异常。要将服务方法设置为One-Way非常简单,只需要将OperationContractAttribute的属性IsOneWay设置为true就可以了,如下的代码所示:publicclassRadio{
[OperationContract(IsOneWay=true]privatevoidBroadCast(;}

3Duplex
Duplex消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。
要实现消息交换的Duplex,相对比较复杂。它需要定义两个接口,其中服务接口用于客户端向服务端发送消息,而回调接口则是从服务端返回消息给客户端,它是通过回调的方式来完成的。接口定义如下:服务接口:
[ServiceContract(Namespace="http://microsoft.servicemodel.samples/",Session=true,CallbackContract=typeof(ICalculatorDuplexCallback]publicinterfaceICalculatorDuplex{
[OperationContract(IsOneWay=true]voidClear(;
[OperationContract(IsOneWay=true]voidAddTo(doublen;
[OperationContract(IsOneWay=true]voidSubtractFrom(doublen;
[OperationContract(IsOneWay=true]voidMultiplyBy(doublen;
[OperationContract(IsOneWay=true]voidDivideBy(doublen;}
回调接口:
publicinterfaceICalculatorDuplexCallback{
[OperationContract(IsOneWay=true]voidEquals(doubleresult;

[OperationContract(IsOneWay=true]voidEquation(stringequation;}
注意在接口定义中,每个服务方法的消息转换模式均设置为One-Way此外,回调接口是被本地调用,因此不需要定义[ServiceContract]。在服务接口中,需要设置
ServiceContractAttributeCallbackContract属性,使其指向回调接口的类型type对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。PerCall方式下,服务对象是在方法被调用时创建,束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。本例的实现如下:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession]publicclassCalculatorService:ICalculatorDuplex{
doubleresult;stringequation;
ICalculatorDuplexCallbackcallback=null;publicCalculatorService({
result=0.0D;
equation=result.ToString(;
callback=OperationContext.Current.GetCallbackChannel(;}
publicvoidAddTo(doublen{
result+=n;
equation+="+"+n.ToString(;callback.Equals(result;

}
//Othercodenotshown.}
在类CalculatorService中,回调接口对象callback通过
OperationContext.Current.GetCallbackChannel<>(获取。然后在服务方法例如AddTo(中,通过调用该回调对象的方法,完成服务端向客户端返回消息的功能。
在使用Duplex时,Contract使用的Binding应该是系统提供的WSDualHttpBinding如果使用BasicHttpBinding,会出现错误。因此Host程序应该如下所示:publicstaticvoidMain(string[]args{
Uriuri=newUri("http://localhost:8080/servicemodelsamples";
using(ServiceHosthost=newServiceHost(typeof(CalculatorService,uri{
host.AddServiceEndpoint(typeof(ICalculatorDuplex,newWSDualHttpBinding(,"service.svc";host.Open(;
Console.WriteLine("Pressanykeytoquitservice.";Console.ReadKey(;}}
如果是使用配置文件,也应作相应的修改,如本例:

address="http://localhost:8080/servicemodelsamples/service.svc"binding="wsDualHttpBinding"
bindingConfiguration="DuplexBinding"contract="ICalculatorDuplex"/>





clientBaseAddress="http://localhost:8000/myClient/">


当服务端将信息回送到客户端后,对消息的处理是由回调对象来处理的,所以回调对象的实现应该是在客户端完成,如下所示的代码应该是在客户端中:
publicclassCallbackHandler:ICalculatorDuplexCallback{
publicvoidEquals(doubleresult{
Console.WriteLine("Equals({0}",result;}
publicvoidEquation(stringequation{
Console.WriteLine("Equation({0}",equation;}}
客户端调用服务对象相应的为:classClient{
staticvoidMain({
//ConstructInstanceContexttohandlemessageson

//callbackinterface.
InstanceContextsite=newInstanceContext(newCallbackHandler(;//Createaproxywithgivenclientendpointconfiguration.using(CalculatorDuplexProxyproxy=newCalculatorDuplexProxy(site,"default"{
doublevalue=100.00D;proxy.AddTo(value;value=50.00D;
proxy.SubtractFrom(value;//Othercodenotshown.
//Waitforcallbackmessagestocompletebefore//closing.
System.Threading.Thread.Sleep(500;//Closetheproxy.proxy.Close(;}}}
注意在Duplex中,会话创建的时机并不是客户端创建Proxy实例的时候,而是当服务对象的方法被第一次调用时,会话方才建立,此时服务对象会在方法调用之前被实例化,直至会话结束,服务对象都是存在的。
以上的代码例子在WinFXSDKSample中可以找到。不过该例子并不能直接反映出Duplex功能。通过前面的介绍,我们知道Duplex具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。因此,我分别实现了两个实例来展现Duplex在两方面的作用。

1)客户端与服务端双向通信功能——ChatDuplexWin
实例说明:一个类似于聊天室的小程序。利用Duplex支持客户端与服务端通信的特点,实现了客户端与服务端聊天的功能。服务接口和回调接口的定义如下:[ServiceContract(Namespace=
"http://www.brucezhang.com/WCF/Samples/ChatDuplex",Session=true,CallbackContract=typeof(IChatDuplexCallback]publicinterfaceIChatDuplex{
[OperationContract(IsOneWay=true]voidRequest(stringcltMsg;
[OperationContract(IsOneWay=true]voidStart(;}
publicinterfaceIChatDuplexCallback{
[OperationContract(IsOneWay=true]voidReply(stringsrvMsg;}
很明显,Request方法的功能为客户端向服务端发送消息,Reply方法则使服务端回送消息给客户端。服务接口IChatDuplex中的Start(方法,用于显示的建立一个会话,因为在这个方法中,我需要直接获取callback对象,使得服务端不必等待客户端先发送消息,而是可以利用callback对象主动先向客户端发送消息,从而实现聊天功能。实现类的代码如下:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession]publicclassChatDuplex:IChatDuplex{
publicChatDuplex({

m_callback=OperationContext.Current.GetCallbackChannel(;}
privateIChatDuplexCallbackm_callback=null;publicvoidRequest(stringcltMsg{
ChatRoomUtil.MainForm.FillListBox(string.Format("Client:{0}",cltMsg;}
publicvoidStart({
ChatRoomUtil.MainForm.SetIIMDuplexCallback(m_callback;}}
因为我要求在服务端界面中,能够将客户端发送来的消息显示在主窗体界面中。所以利用了全局变量MainForm,用来保存主窗体对象:publicstaticclassChatRoomUtil{
publicstaticServerFormMainForm=newServerForm(;}
而在服务端程序运行时,Application运行的主窗口也为该全局变量:Application.Run(ChatRoomUtil.MainForm;
要实现聊天功能,最大的障碍是当服务端收到客户端消息时,不能立即Reply消息,而应等待服务端用户输入回送的消息内容,方可以Reply也即是说,当客户端调用服务对象的Request方法时,不能直接调用callback对象。因此我利用Start(方法,将服务对象中获得的callback对象传递到主窗体对象中。这样,callback对象就可以留待服务端发送消息时调用了:publicpartialclassServerForm:Form{
privateIChatDuplexCallbackm_callback;
privatevoidbtnSend_Click(objectsender,EventArgse

{
if(txtMessage.Text!=string.Empty{
lbMessage.Items.Add(string.Format("Server:{0}",txtMessage.Text;if(m_callback!=null{
m_callback.Reply(txtMessage.Text;}
txtMessage.Text=string.Empty;}}
publicvoidFillListBox(stringmessage{
lbMessage.Items.Add(message;}
publicvoidSetIIMDuplexCallback(IChatDuplexCallbackcallback{
m_callback=callback;}
//Othercodenotshown;}
对于客户端的实现,相对简单,需要注意的是回调接口的实现:
publicclassChatDuplexCallbackHandler:IChatDuplexCallback{
publicChatDuplexCallbackHandler(ListBoxlistBox{
m_listBox=listBox;}
privateListBoxm_listBox;

publicvoidReply(stringsrvMsg{
m_listBox.Items.Add(string.Format("Server:{0}",srvMsg;}}
由于我自定义了该对象的构造函数,所以在实利化proxy时会有稍微区别:InstanceContextsite=newInstanceContext(newChatDuplexCallbackHandler(this.lbMessage;proxy=newChatDuplexProxy(site;proxy.Start(;
通过proxy对象的Start(方法,使得我们在建立proxy对象开始,就创建了会话,从而使得服务对象被实例化,从而得以运行下面的这行代码:
m_callback=OperationContext.Current.GetCallbackChannel(;
也就是说,在proxy对象建立之后,服务端就已经获得callback对象了,这样就可以保证服务端能够先向客户端发送消息而不会因为callbacknull,导致错误的发生。2)消息交换的异步回调功能——AsyncDuplexWin
实例说明:本实例比较简单,只是为了验证当回调对象被调用时,客户端是否可以被异步运行。调用服务对象时,服务端会进行一个累加运算。在运算未完成之前,客户端会执行显示累加数字的任务,当服务端运算结束后,只要客户端程序的线程处于Sleep状态,该回调对象就会被调用,然后根据用户选择是否再继续运行剩下的任务。本例中服务端为控制台应用程序,客户端则Windows应用程序。
例子中的接口定义非常简单,不再赘述,而实现类的代码如下:publicclassSumDuplex:ISumDuplex{
publicSumDuplex({
callback=OperationContext.Current.GetCallbackChannel(;

}
privateISumDuplexCallbackcallback=null;#regionISumDuplexMemberspublicvoidSum(intseed{
intresult=0;
for(inti=1;i<=seed;i++{
Thread.Sleep(10;
Console.WriteLine("nowat{0}",i;result+=i;}
callback.Equals(result;}
#endregion}
很显然,当客户端调用该服务对象时,会在服务端的控制台上打印出迭代值。
由于客户端需要在callback调用时,停止对当前任务的运行,所以需要用到多线程机制:publicdelegatevoidDoWorkDelegate(;publicpartialclassClientForm:Form{
publicClientForm({
InitializeComponent(;
InstanceContextsite=newInstanceContext(newSumDuplexCallbackHandler(this.lbMessage;proxy=newSumDuplexProxy(site;}
privateSumDuplexProxyproxy;

privateThreadthread=null;
privateDoWorkDelegatedel=null;privateintcounter=0;
privatevoidbtnStart_Click(objectsender,EventArgse{
proxy.Sum(100;
thread=newThread(newThreadStart(delegate({
while(true{
if(ClientUtil.IsCompleted{
if(MessageBox.Show("Gameover,Exit?","Notify",MessageBoxButtons.YesNo,
MessageBoxIcon.Question==DialogResult.Yes{
break;}}
if(counter>10000{
break;}
if(del!=null{del(;}
Thread.Sleep(50;}

};
del+=newDoWorkDelegate(DoWork;thread.Start(;}
privatevoidDoWork({
if(lbMessage.InvokeRequired{
this.Invoke(newDoWorkDelegate(DoWork;}else{
lbMessage.Items.Add(counter;lbMessage.Refresh(;counter++;}}
privatevoidClientForm_FormClosing(objectsender,FormClosingEventArgse{
if(thread!=null{
thread.Abort(;}}}
因为需要在多线程中对ListBox控件的items进行修改,由于该控件不是线程安全的,所以应使用该控件的InvokeRequired属性。此外,在线程启动时的匿名方法中,利用while(true控制当前线程的运行,并利用全局变量ClientUtil.IsCompleted判断回调对象是否被调用,如

果被调用了,则会弹出对话框,选择是否退出当前任务。这里所谓的当前任务实际上就是调用DoWork方法,ListBox控件的items中不断添加累加的counter值。注意客户端的回调对象实现如下:
classSumDuplexCallbackHandler:ISumDuplexCallback{
publicSumDuplexCallbackHandler(ListBoxlistBox{
m_listBox=listBox;}
privateListBoxm_listBox;
#regionISumDuplexCallbackMemberspublicvoidEquals(intresult{
ClientUtil.IsCompleted=true;
m_listBox.Items.Add(string.Format("Theresultis:{0}",result;m_listBox.Refresh(;}#endregion}
当客户端点击Start按钮,调用服务对象的Sum方法后,在服务端会显示迭代值,而客户端也开始执行自己的任务,向ListBox控件中添加累加数。一旦服务端运算完毕,就将运算结果通过回调对象传送到客户端,全局变量ClientUtil.IsCompleted将被设置为true。如果添加累加值的线程处于sleep状态,系统就会将结果值添加到ListBox控件中,同时会弹出对话框,决定是否继续剩下的任务。
注:本文示例的代码和实例均在Feb2006CTP版本下运行。WindowsCommunicationFoundation之旅(PartFour
WindowsCommunicationFoundation之旅》系列之四六、定义DataContract

我在介绍如何定义一个ServiceContract时,举了这样的一个例子,代码如下:[ServiceContract]publicclassBookTicket{
[OperationContract]
publicboolCheck(Ticketticket{
boolflag;
//logictocheckwhethertheticketisnone;returnflag;}
[OperationContract]
privateboolBook(Ticketticket{
//logictobooktheticket}}
ServiceBookTicket中,两个服务方法CheckBook的参数均为Ticket类型。这个类型是自定义类型,根据WCF的要求,该类型必须支持序列化的操作,方才可以在服务方法中作为消息被传递。
.Net中,除了基本类型如intlongdouble,以及枚举类型和String类型外,一个自定义的类型如要支持序列化操作,应该标记该类型为[Serializable],或者使该类型实现
ISerializable接口。而在WCF中,推荐的一种方式是为这些类型标记DataContractAttribute方法如下:[DataContract]publicclassTicket{
privatestringm_movieName;

[DataMember]publicintSeatNo;[DataMember]
publicstringMovieName{
get{returnm_movieName;}set{m_movieName=value;}}
[DataMember]
privateDateTimeTime;}
其中,[DataMember]是针对DataContract类的成员所标记的Attribute。与服务类中的OperationContractAttribute相同,DataMemberAttribute与对象的访问限制修饰符publicinternalprivate等)没有直接关系。即使该成员为private,只要标记了[DataMember],仍然可以被序列化。虽然DataMemberAttribute可以被施加给类型的字段和属性,但如果被施加到static成员时,WCF会忽略该DataMemberAttribute当我们为一个类型标注DataContractAttribute时,只有被显式标注了
DataMemberAttribute的成员方才支持序列化操作。这一点与SerializableAttribute大相径庭。一个被标记了SerializableAttribute的类型,在默认情况下,其内部的成员,不管是public还是private都支持序列化,除非是那些被施加了NonSerializedAttribute的成员。DataContractAttribute采用这种显式标注法,使得我们更加专注于服务消息的定义,只有需要被传递的服务消息成员,方才被标注DataMemberAttribute
如果DataContract类中的DataMember成员包含了泛型,那么泛型类型参数必须支持序列化,如下代码所示:[DataContract]publicclassMyGeneric{
[DataMember]

TtheData;}
在类MyGeneric中,泛型参数T必须支持序列化。如实例化该对象:MyGenericintObject=newMyGeneric(;MyGenericcustomObject=newMyGeneric(;
对象intObject由于传入的泛型参数为int基本类型,因此可以被序列化;而对象customObject是否能被序列化,则要看传入的泛型参数CustomType类型是否支持序列化。
DataContractNamespaceName来唯一标识,我们可以在DataContractAttributeNamespace属性、Name属性中进行设置。如未设置DataContractName属性,则默认的名字为定义的类型名。DataMember也可以通过设置Name属性,默认的名字为定义的成员名。如下代码所示:
namespaceMyCompany.OrderProc{
[DataContract]
publicclassPurchaseOrder{
//DataMember名字为默认的Amount;[DataMember]
publicdoubleAmount;
//Name属性将重写默认的名字Ship_to,此时DataMember名为Address;[DataMember(Name="Address"]publicstringShip_to;}
//Namespace为默认值:
//http://schemas.datacontract.org/2004/07/MyCompany.OrderProc//此时其名为PurchaseOrder而非MyInvoice[DataContract(Name="PurchaseOrder"]

publicclassMyInvoice{
//Codenotshown.}
//其名为Payment而非MyPayment
//Namespace被设置为http://schemas.example.com[DataContract(Name="Payment",
Namespace="http://schemas.example.com"]publicclassMyPayment{
//Codenotshown.}}
//重写MyCorp.CRM下的所有DataContractNamespace[assembly:ContractNamespace(ClrNamespace="MyCorp.CRM",
Namespace="http://schemas.example.com/crm"]namespaceMyCorp.CRM{
//此时Namespace被设置为http://schemas.example.com/crm.//名字仍然为默认值Customer[DataContract]publicclassCustomer{
//Codenotshown.}}

由于DataContract将被序列化以及反序列化,因此类型中成员的顺序也相当重要,在DataMemberAttribute中,提供了Order属性,用以设置成员的顺序。在WCF中对成员的序列化顺序规定如下:1、默认的顺序依照字母顺序;
2、如成员均通过Order属性指定了顺序,且顺序值相同,则以字母顺序;3、未指定Order属性的成员顺序在指定了Order顺序之前;
4、如果DataContract处于继承体系中,则不管子类中指定的Order值如何,父类的成员顺序优先。
下面的代码很好的说明了DataMember的顺序:[DataContract]publicclassBaseType{
[DataMember]publicstringzebra;}
[DataContract]
publicclassDerivedType:BaseType{
[DataMember(Order=0]publicstringbird;[DataMember(Order=1]publicstringparrot;[DataMember]publicstringdog;
[DataMember(Order=3]publicstringantelope;[DataMember]publicstringcat;
[DataMember(Order=1]publicstringalbatross;}
序列化后的XML内容如下:


因为成员zebra为父类成员,首先其顺序在最前面。catdog未指定Order,故在指定了Order的其它成员之前,两者又按照字母顺序排列。parrotalbatross均指定Order值为1因此也按照字母顺序排列在Order值为0bird之后。
判断两个DataContract是否相同,应该根据DataContractNamespaceName,以及DataMemberNameOrder来综合判断。例如下面代码所示的类CustomerPerson其实是同一个DataContract[DataContract]publicclassCustomer{
[DataMember]
publicstringfullName;[DataMember]
publicstringtelephoneNumber;}
[DataContract(Name=”Customer”]publicclassPerson{
[DataMember(Name="fullName"]privatestringnameOfPerson;privatestringaddress;

[DataMember(Name="telephoneNumber"]privatestringphoneNumber;}
再例如下面代码所示的类Coords1Coords2Coords3也是相同的DataContract,而类Coords4则因为顺序不同,因而与前面三个类是不同的:[DataContract(Name="Coordinates"]publicclassCoords1{
[DataMember]publicintX;[DataMember]publicintY;}
[DataContract(Name="Coordinates"]publicclassCoords2{
[DataMember]publicintY;[DataMember]publicintX;}
[DataContract(Name="Coordinates"]publicclassCoords3{
[DataMember(Order=2]publicintY;[DataMember(Order=1]publicintX;}
[DataContract(Name="Coordinates"]publicclassCoords4{
[DataMember(Order=1]publicintY;

[DataMember(Order=2]publicintX;}
DataContract处于继承体系时,还需要注意的是对象的多态问题。如果在服务端与客户端之间要传递消息,经常会涉及到类型的动态绑定。根据规定,如果消息的类型是子类类型,那么发送消息一方就不能传递基类类型。相反,如果消息类型是父类类型,那么发送消息一方就可以是父类本身或者其子类。从这一点来看,WCF的规定是与面向对象思想并行不悖的。但是可能存在的问题是,当消息类型定义为父类类型,而发送消息一方传递其子类时,服务端有可能对该子类类型处于未知状态,从而不能正常地反序列化。所以,WCFDataContract提供了KnownTypeAttribute,通过设置它来告知服务端可能存在的动态绑定类类型。举例来说,如果我们定义了这样三个类:[DataContract]publicclassShape{}
[DataContract(Name="Circle"]publicclassCircleType:Shape{}[DataContract(Name="Triangle"]publicclassTriangleType:Shape{}
然后在类CompanyLogo中定义Shape类型的字段,如下所示:[DataContract]
publicclassCompanyLogo{
[DataMember]
privateShapeShapeOfLogo;[DataMember]
privateintColorOfLogo;}
此时的CompanyLogo类由于正确的设置了[DataContract][DataMember],而Shape类型也是支持序列化的,因此该类型是可以被序列化的。然而一旦客户端在调用CompanyLogo

类型的对象时,为字段ShapeOfLogo设置其值为CircleTypeTriangleType类型的对象,就会发生反序列化错误,因为服务端并不知道CircleTypeTriangleType类型,从而无法进行正确的匹配。所以上述的CompanyLogo类的定义应修改如下:[DataContract]
[KnownType(typeof(CircleType][KnownType(typeof(TriangleType]publicclassCompanyLogo{
[DataMember]
privateShapeShapeOfLogo;[DataMember]
privateintColorOfLogo;}
类的继承如此,接口的实现也是同样的道理,如下例所示:publicinterfaceICustomerInfo{
stringReturnCustomerName(;}
[DataContract(Name="Customer"]publicclassCustomerType:ICustomerInfo{
publicstringReturnCustomerName({
return"noname";}}
[DataContract]
[KnownType(typeof(CustomerType]

publicclassPurchaseOrder{
[DataMember]ICustomerInfobuyer;[DataMember]intamount;}
由于PurchaseOrder中定义了ICustomerInfo接口类型的字段,如要该类能被正确的反序列化,就必须为类PurchaseOrder加上[KnownType(typeof(CustomerType]的标注。对于集合类型也有相似的规定。例如Hashtable类型,其内存储的均为object对象,但实际设置的值可能是一些自定义类型,此时也许要通过KnownType进行标注。例如在类
LibraryCatalog中,定义了Hashtable类型的字段theCatalog。该字段可能会设置为Book类型和Magazine类型,假定Book类型和Magazine类型均被定义为DataContract,则类LibraryCatalog的正确定义应如下所示:[DataContract]
[KnownType(typeof(Book][KnownType(typeof(Magazine]publicclassLibraryCatalog{
[DataMember]
System.Collections.HashtabletheCatalog;}
如果在一个DataContract中,定义一个object类型的字段。由于object类型是所有类型的父类,所以需要我们利用KnownType标明客户端允许设置的类型。例如类MathOperationData[DataContract]
[KnownType(typeof(int[]]publicclassMathOperationData

{
privateobjectnumberValue;[DataMember]publicobjectNumbers{
get{returnnumberValue;}set{numberValue=value;}}
//[DataMember]
//publicOperationOperation;}
属性Numbers其类型为object,而KnownType设置的类型是int[],因此可以接受的类型就包括:整型,整型数组以及List类型。如下的调用都是正确的:staticvoidRun({
MathOperationDatamd=newMathOperationData(;inta=100;md.Numbers=a;int[]b=newint[100];md.Numbers=b;
Listc=newList(;md.Numbers=c;}
但如果设置Number属性为ArrayList即使该ArrayList对象中元素均为int对象,也是错误的:
staticvoidRun(

{
MathOperationDatamd=newMathOperationData(;ArrayListd=newArrayList(;md.Numbers=d;}
一旦一个DataContract类型标注了KnownTypeAttribute,则该Attribute的作用域可以施加到其子类中,如下所示:[DataContract]
[KnownType(typeof(CircleType][KnownType(typeof(TriangleType]publicclassMyDrawing{
[DataMember]privateobjectShape;[DataMember]privateintColor;}
[DataContract]
publicclassDoubleDrawing:MyDrawing{
[DataMember]
privateobjectadditionalShape;}
虽然DoubleDrawing没有标注KnowTypeAttribute,但其字段additionalShape仍然可以被设置为CircleType类型或TriangleType类型,因为其父类已经被设置为KnowTypeAttribute

《WCF教程.doc》
将本文的Word文档下载,方便收藏和打印
推荐:
下载文档
热门推荐
相关推荐