Windows Communication Foundation介绍

24 篇文章 0 订阅

Windows Communication Foundation介绍(一)

参阅: WCF 介绍(一) | WCF 介绍(二) | WCF 介绍(三) | WCF 介绍(四) 前言:WCF是微软基于SOA(Service Oriented Architecture)推出的.Net平台下的框架产品,它代表了软件架构设计与开发的一种发展方向,在微软的战略计划中也占有非常重要的地位。了解和掌握WCF,对于程序员特别是基于微软产品开发的程序员而言,是非常有必要的。

一、什么是WCF?

根据微软官方的解释,WCF是使用托管代码建立和运行面向服务(Service Oriented)应用程序的统一框架。它使得开发者能够建立一个跨平台的、安全、可信赖、事务性的解决方案,且能与已有系统兼容协作。WCF是微软分布式应用程序开发的集大成者,它整合了.Net平台下所有的和分布式系统有关的技术,例如.Net Remoting、ASMX、WSE和MSMQ。以通信(Communiation)范围而论,它可以跨进程、跨机器、跨子网、企业网乃至于Internet;以宿主程序而论,可以以ASP.NET,EXE,WPF,Windows Forms,NT Service,COM+作为宿主(Host)。WCF可以支持的协议包括TCP,HTTP,跨进程以及自定义,安全模式则包括SAML,Kerberos,X509,用户/密码,自定义等多种标准与模式。也就是说,在WCF框架下,开发基于SOA的分布式系统变得容易了,微软将所有与此相关的技术要素都包含在内,掌握了WCF,就相当于掌握了叩开SOA大门的钥匙。 WCF是建立在.Net Framework 2.0基础之上的,包含在.NET 3.0/3.5当中。2005中并没有包含WCF,但是当安装好了WinFX Runtime Components后,我们就可以在Visual Studio 2005环境下开发和创建WCF的程序了。 WCF是微软重点介绍的产品,因此也推出了专门的官方网站( http://windowscommunication.net),该网站有最新的WCF新闻发布,以及介绍WCF的技术文档和样例代码。

二、WCF的优势

在David Chappell所撰的《Introducing Windows Communication Foundation》一文中,用了一个活鲜鲜的例子,来说明WCF的优势所在。假定我们要为一家汽车租赁公司开发一个新的应用程序,用于租车预约服务。该租车预约服务会被多种应用程序访问,包括呼叫中心(Call Center),基于J2EE的租车预约服务以及合作伙伴的应用程序(Partner Application),如图所示:
uploads/200708/06_135030_wcf01.gif
呼叫中心运行在Windows平台下,是在.Net Framework下开发的应用程序,用户为公司员工。由于该汽车租赁公司兼并了另外一家租赁公司,该公司原有的汽车预约服务应用程序是J2EE应用程序,运行在非Windows操作系统下。呼叫中心和已有的汽车预约应用程序都运行在Intranet环境下。合作伙伴的应用程序可能会运行在各种平台下,这些合作伙伴包括旅行社、航空公司等等,他们会通过Internet来访问汽车预约服务,实现对汽车的租用。 这样一个案例是一个典型的分布式应用系统。如果没有WCF,利用.Net现有的技术应该如何开发呢? 首先考虑呼叫中心,它和我们要开发的汽车预约服务一样,都是基于.Net Framework的应用程序。呼叫中心对于系统的性能要求较高,在这样的前提下,.Net Remoting是最佳的实现技术。它能够高性能的实现.Net与.Net之间的通信。 要实现与已有的J2EE汽车预约应用程序之间的通信,只有基于SOAP的Web Service可以实现此种目的,它保证了跨平台的通信;而合作伙伴由于是通过Internet来访问,利用ASP.Net Web Service,即ASMX,也是较为合理的选择,它保证了跨网络的通信。由于涉及到网络之间的通信,我们还要充分考虑通信的安全性,利用WSE(Web Service Enhancements)可以为ASMX提供安全的保证。 一个好的系统除了要保证访问和管理的安全,高性能,同时还要保证系统的可信赖性。因此,事务处理是企业应用必须考虑的因素,对于汽车预约服务而言,同样如此。在.Net中,Enterprise Service(COM+)提供了对事务的支持,其中还包括分布式事务(Distributed Transactions)。不过对于Enterprise Service而言,它仅支持有限的几种通信协议。 如果还要考虑到异步调用、脱机连接、断点连接等功能,我们还需要应用MSMQ(Mcrosoft Message Queuing)利用消息队列支持应用程序之间的消息传递。 如此看来,要建立一个好的汽车租赁预约服务系统,需要用到的.Net分布式技术,包括.Net Remoting、Web Service,COM+等五种技术,这既不利于开发者的开发,也加大了程序的维护难度和开发成本。正是因应于这样的缺陷,WCF才会在.Net 2.0中作为全新的分布式开发技术被微软强势推出,它整合了上述所属的分布式技术,成为了理想的分布式开发的解决之道。下图展示了WCF与之前的相关技术的比较:
uploads/200708/06_135034_wcf02.gif
从功能的角度来看,WCF完全可以看作是ASMX,.Net Remoting,Enterprise Service,WSE,MSMQ等技术的并集。(注:这种说法仅仅是从功能的角度。事实上WCF远非简单的并集这样简单,它是真正面向服务的产品,它已经改变了通常的开发模式。)因此,对于上述汽车预约服务系统的例子,利用WCF,就可以解决包括安全、可信赖、互操作、跨平台通信等等需求。开发者再不用去分别了解.Net Remoting,ASMX等各种技术了。 概括地说,WCF具有如下的优势: 1、统一性 前面已经叙述,WCF是对于ASMX,.Net Remoting,Enterprise Service,WSE,MSMQ等技术的整合。由于WCF完全是由托管代码编写,因此开发WCF的应用程序与开发其它的.Net应用程序没有太大的区别,我们仍然可以像创建面向对象的应用程序那样,利用WCF来创建面向服务的应用程序。 2、互操作性 由于WCF最基本的通信机制是SOAP,这就保证了系统之间的互操作性,即使是运行不同的上下文中。这种通信可以是基于.Net到.Net间的通信,如下图所示:
uploads/200708/06_135039_wcf03.gif
可以跨进程、跨机器甚至于跨平台的通信,只要支持标准的Web Service,例如J2EE应用服务器(如WebSphere,WebLogic)。应用程序可以运行在Windows操作系统下,也可以运行在其他的操作系统,如Sun Solaris,HP Unix,Linux等等。如下图所示:
uploads/200708/06_135043_wcf04.gif
3、安全与可信赖 WS-Security,WS-Trust和WS-SecureConversation均被添加到SOAP消息中,以用于用户认证,数据完整性验证,数据隐私等多种安全因素。 在SOAP的header中增加了WS-ReliableMessaging允许可信赖的端对端通信。而建立在WS-Coordination和WS-AtomicTransaction之上的基于SOAP格式交换的信息,则支持两阶段的事务提交(two-phase commit transactions)。 上述的多种WS-Policy在WCF中都给与了支持。对于Messaging而言,SOAP是Web Service的基本协议,它包含了消息头(header)和消息体(body)。在消息头中,定义了WS-Addressing用于定位SOAP消息的地址信息,同时还包含了MTOM(消息传输优化机制,Message Transmission Optimization Mechanism)。如图所示:
uploads/200708/06_135047_wcf05.gif

4、兼容性 WCF充分的考虑到了与旧有系统的兼容性。安装WCF并不会影响原有的技术如ASMX和.Net Remoting。即使对于WCF和ASMX而言,虽然两者都使用了SOAP,但基于WCF开发的应用程序,仍然可以直接与ASMX进行交互。 注:本部分内容主要来源于David Chappell,《Introducing Windows Communication Foundation》 参阅 WCF 介绍(一) WCF 介绍(二) WCF 介绍(三) WCF 介绍(四)

Windows Communication Foundation介绍(二)

示例下载(VS2005 下编写) 参阅: WCF 介绍(一) | WCF 介绍(二) | WCF 介绍(三) | WCF 介绍(四)

三、WCF的技术要素

作为基于SOA(Service Oriented Architecture)的一个框架产品,WCF最重要的就是能够快捷的创建一个服务(Service)。如下图所示,一个WCF Service由下面三部分构成:

uploads/200708/06_141701_wcf06.gif

1、Service Class:一个标记了[ServiceContract]Attribute的类,在其中可能包含多个方法。除了标记了一些WCF特有的Attribute外,这个类与一般的类没有什么区别。 2、Host(宿主):可以是应用程序,进程如Windows Service等,它是WCF Service运行的环境。 3、Endpoints:可以是一个,也可以是一组,它是WCF实现通信的核心要素。 WCF Service由一个Endpoints集合组成,每个Endpoint就是用于通信的入口,客户端和服务端通过Endpoint交换信息,如下图所示:

uploads/200708/06_141710_wcf07.gif

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

uploads/200708/06_141715_wcf08.gif

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

uploads/200708/06_141721_wcf09.gif

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

uploads/200708/06_141727_wcf10.gif

Binding的Name以及Namespace是服务元数据(service’s metadata)的唯一标识。BindingElement描述的是WCF通信时binding的方式。例如,SecurityBindingElement表示Endpoint使用SOAP消息安全方式,而ReliableSessionBindingElement表示Endpoint利用可信赖消息确保消息的传送。TcpTransportBindingElement则表示Endpoint利用TCP作为通信的传输协议。每种BindingElement还有相应的属性值,进一步详细的描述WCF通信的方式。 BindingElement的顺序也非常重要。BindingElement集合通常会创建一个用于通信的堆栈,其顺序与BindingElement集合中元素顺序一致。集合中最后一个binding element对应于通信堆栈的底部,而集合中的第一个binding element则对应于堆栈的顶端。入消息流的方向是从底部经过堆栈向上,而出消息流的方向则从顶端向下。因此,BindingElement集合中的binding element顺序直接影响了通信堆栈处理消息的顺序。幸运的是,WCF已经提供了一系列预定义的Binding,能够满足大多数情况,而不需要我们自定义Binding,殚精竭虑地考虑binding element的顺序。 Contract是一组操作(Operations)的集合,该操作定义了Endpoint通信的内容,每个Operation都是一个简单的消息交换(message exchange),例如one-way或者request/reply消息交换。 类ContractDescription用于描述WCF的Contracts以及它们的操作operations。在ContractDescription类中,每个Contract的operation都有相对应的OperationDescription,用于描述operation的类型,例如是one-way,还是request/reply。在OperationDescription中还包含了MessageDecription集合用于描述message。 在WCF编程模型中,ContractDescription通常是在定义Contract的接口或类中创建。对于这个接口或类类型,标记以ServiceContractAttribute,而其Operation方法则标记以OperationContractAttribute。当然我们也可以不利用CLR的attribute,而采用手工创建。 与Binding一样,每个Contract也包含有Name和Namespace,用于在Service的元数据中作为唯一性识别。此外,Contract中还包含了ContractBehavior的集合,ContractBehavior类型可以用于修改或扩展contract的行为。类ContractDescription的组成如下图所示:

uploads/200708/06_141732_wcf11.gif

正如在ContractDescription中包含的IContractBehavior一样,WCF专门提供了行为Behavior,它可以对客户端和服务端的一些功能进行修改或者扩展。例如ServiceMetadataBehavior用于控制Service是否发布元数据。相似的,security behavior用于控制安全与授权,transaction behavior则控制事务。 除了前面提到的ContractBehavior,还包括ServiceBehavior和ChannelBehaivor。ServiceBehavior实现了IServiceBehavior接口,ChannelBehaivor则实现了IChannleBehavior接口。 由于WCF需要管理的是服务端与客户端的通信。对于服务端,WCF提供了类ServiceDescription用于描述一个WCF Service,;而针对客户端,WCF管理的是发送消息时需要使用到的通道Channel,类ChannelDescription描述了这样的客户端通道。 ServiceDescription类的组成如下图所示:

uploads/200708/06_141738_wcf12.gif

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

uploads/200708/06_141743_wcf13.gif

定义一个WCF Service非常简单,以SayHello为例,定义的Service可能如下:

  1. using System.ServiceModel   
  2.   
  3.     [ServiceContract]   
  4.     public class Service1   
  5.     {   
  6.         public string SayHello(string name)   
  7.         {   
  8.             return "Hello: " + name;   
  9.         }   
  10.     }  

System.ServiceModel是微软为WCF提供的一个新的类库,以用于面向服务的程序设计。在开发WCF应用程序时,需要先添加对System.ServiceModel的引用。WCF中的大部分类和接口也都是在命名空间System.ServiceModel下。 我们为Service1类标记了[ServiceContract],这就使得该类成为了一个WCF Service,而其中的方法SayHello()则因为标记了[OperationContract],而成为该Service的一个Operation。 不过WCF推荐的做法是将接口定义为一个Service,这使得WCF Service具有更好的灵活性,毕竟对于一个接口而言,可以在同时有多个类实现该接口,这也就意味着可以有多个Service Contract的实现。那么上面的例子就可以修改为:

  1. [ServiceContract()]   
  2. public interface IService1   
  3. {   
  4.     [OperationContract]   
  5.     string SayHello(string name);   
  6. }  

而类Service1则实现该IService1接口:

  1. public class Service1 : IService1   
  2. {   
  3.     public string SayHello(string name)   
  4.     {   
  5.         return "Hello: " + name;   
  6.     }   
  7. }  

注意在实现了IService1接口的类Service1中,不再需要在类和方法中标注ServiceContractAttribute和OperationContractAttribute了。 前面我已经提过,一个WCF Service必须有host作为它运行的环境。这个host可以是ASP.Net,可以是Windows Service,也可以是一个普通的应用程序,例如控制台程序。下面就是一个Host的实现:

  1. using System.ServiceModel   
  2.   
  3.     class HostApp   
  4.     {   
  5.         static void Main(string[] args)   
  6.         {   
  7.             MyServiceHost.StartService();   
  8.             Console.WriteLine("服务已经启动...");   
  9.             Console.Read();   
  10.             MyServiceHost.StopService();             
  11.         }   
  12.     }   
  13.   
  14.     internal class MyServiceHost   
  15.     {   
  16.         internal static ServiceHost myServiceHost = null;   
  17.   
  18.         internal static void StartService()   
  19.         {   
  20.             //Consider putting the baseAddress in the configuration system   
  21.             //and getting it here with AppSettings   
  22.             Uri baseAddress = new Uri("http:localhost:8080/service1");   
  23.   
  24.             //Instantiate new ServiceHost    
  25.             myServiceHost = new ServiceHost(typeof(WCFServiceLibrary2.Service1), baseAddress);   
  26.   
  27.             //Open myServiceHost   
  28.             myServiceHost.Open();   
  29.         }   
  30.   
  31.         internal static void StopService()   
  32.         {   
  33.             //Call StopService from your shutdown logic (i.e. dispose method)   
  34.             if (myServiceHost.State != CommunicationState.Closed)   
  35.                 myServiceHost.Close();   
  36.         }   
  37.     }  

在这个HostApp中,我们为Server1创建了一个ServiceHost对象。通过它就可以创建WCF运行时(Runtime),WCF Runtime是一组负责接收和发送消息的对象。ServiceHost可以创建SerivceDescription对象,利用SerivceDescription,SercieHost为每一个ServiceEndpoint创建一个EndpointListener。ServiceHost的组成如下图:

uploads/200708/06_141748_wcf14.gif

EndpointListener侦听器包含了listening address,message filtering和dispatch,它们对应ServiceEndpoint中的EndpointAddress,Contract和Binding。在EndpointListener中,还包含了一个Channel Stack,专门负责发送和接收消息。 注意在创建ServiceHost时,传递的type类型参数,不能是interface。因此,我在这里传入的是typeof(HelloWorld)。ServiceHost类的AddServiceEndpoint()方法实现了为Host添加Endpoint的功能,其参数正好是Endpoint的三部分:Address,Bingding和Contract。(此时的IHello即为ServiceContract,其方法Hello为OperationContract)。 ServiceHost的Open()方法用于创建和打开Service运行时,而在程序结束后我又调用了Close()方法,来关闭这个运行时。实际上以本例而言,该方法可以不调用,因为在应用程序结束后,系统会自动关闭该host。但作为一种良好的编程习惯,WCF仍然要求显式调用Close()方法,因为Service运行时其本质是利用Channel来完成消息的传递,当打开一个Service运行时的时候,系统会占用一个Channel,调用完后,我们就需要释放对该通道的占用。当然我们也可以用using语句来管理ServiceHost资源的释放。 定义好了一个WCF Service,并将其运行在Host上后,如何实现它与客户端的通信呢?典型的情况下,服务端与客户端均采用了Web Service Description Language(WSDL),客户端可以通过工具SvcUtil.exe生成对应于该WCF Service的Proxy代码,以完成之间的消息传递,如图所示:

uploads/200708/06_141754_wcf15.gif

SvcUtil.exe是由WinFx Runtime Component SDK所提供的,如果安装SDK正确,可以在其中找到该应用工具。生成客户端Proxy代码的方法很简单,首先需要运行服务端Service。然后再命令行模式下运行下面的命令: svcutil.exe http://localhost:8080/service1?wsdl 这样会在当前目录下产生两个文件service1.cs和output.config。前者最主要的就是包含了一个实现了Service1接口的Proxy对象,这个代理对象名为Service1Client,代码生成的结果如下:

  1. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]   
  2. [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IService1")]   
  3. public interface IService1   
  4. {   
  5.        
  6.     [System.ServiceModel.OperationContractAttribute(Action="http:tempuri.org/IService1/SayHello", ReplyAction="http:tempuri.org/IService1/SayHelloResponse")]   
  7.     string SayHello(string name);   
  8. }   
  9.   
  10. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]   
  11. public interface IService1Channel : IService1, System.ServiceModel.IClientChannel   
  12. {   
  13. }   
  14.   
  15. [System.Diagnostics.DebuggerStepThroughAttribute()]   
  16. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]   
  17. public partial class Service1Client : System.ServiceModel.ClientBase<IService1>, IService1   
  18. {   
  19.        
  20.     public Service1Client()   
  21.     {   
  22.     }   
  23.        
  24.     public Service1Client(string endpointConfigurationName) :    
  25.             base(endpointConfigurationName)   
  26.     {   
  27.     }   
  28.        
  29.     public Service1Client(string endpointConfigurationName, string remoteAddress) :    
  30.             base(endpointConfigurationName, remoteAddress)   
  31.     {   
  32.     }   
  33.        
  34.     public Service1Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :    
  35.             base(endpointConfigurationName, remoteAddress)   
  36.     {   
  37.     }   
  38.        
  39.     public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :    
  40.             base(binding, remoteAddress)   
  41.     {   
  42.     }   
  43.        
  44.     public string SayHello(string name)   
  45.     {   
  46.         return base.Channel.SayHello(name);   
  47.     }   
  48. }  

至于后者,则是WCF Service的配置信息,主要包含的是Endpoint中Address,Binding以及Contract的配置(在后续文章我会详细介绍)。 现在客户端就可以直接使用Service1Client对象,来完成与服务端的通信了:

  1. class ClientApp   
  2. {   
  3.     static void Main(string[] args)   
  4.     {   
  5.         Service1Client client = new Service1Client();   
  6.   
  7.         // 使用 "client" 变量在服务上调用操作。   
  8.         Console.WriteLine("请输入你的名字: ");   
  9.         string name = Console.ReadLine();   
  10.         Console.WriteLine(client.SayHello(name));   
  11.   
  12.         // 始终关闭客户端。   
  13.         client.Close();   
  14.   
  15.         Console.ReadLine();   
  16.   
  17.     }   
  18. }  

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

  1. using System.ServiceModel;   
  2. using System.ServiceModel.Description;   
  3. using System.ServiceModel.Channel   
  4.   
  5.     class ClientApp   
  6.     {   
  7.         static void Main(string[] args)   
  8.         {   
  9.             ServiceEndpoint httpEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService1)), new WSHttpBinding(), new EndpointAddress("http:localhost:8080/service1"));   
  10.   
  11.             using (ChannelFactory<IService1> factory = new ChannelFactory<IService1>(httpEndpoint))   
  12.             {   
  13.                 //创建IHello服务的代理对象;      
  14.                 IService1 service = factory.CreateChannel();   
  15.   
  16.                 Console.WriteLine("请输入你的名字: ");   
  17.                 string name = Console.ReadLine();   
  18.                 Console.WriteLine(service.SayHello(name));   
  19.             }   
  20.             Console.ReadKey();   
  21.         }     
  22.     }  

对于上面的代码,我们有两点需要注意: 1、采用这种方式,前提条件是客户端能够访问IHello接口。这也印证了之前我所叙述的最好使用interface来定义Service的好处。此外,为了保证部署的方便,有关Service的interface最好单独编译为一个程序集,便于更好的部署到客户端。 2、客户端必须知道服务端binding的方式以及address。 对于服务端而言,我们也可以直接在浏览器中打开该Service,在地址栏中输入http://localhost:8080/service1,如下图:

uploads/200708/06_161535_wcf16.gif

点击链接:http://localhost:8080/service1?wsdl,我们可以直接看到Service1的WSDL。注意到在这里我并没有使用IIS,实际上WCF内建了对httpsys的集成,允许任何应用程序自动成为HTTP listener。 示例下载 参考: 1、David Chappell,Introducing Windows Communication Foundation 2、Aaron Skonnard,Learn The ABCs Of Programming Windows Communication Foundation 3、Microsoft Corporation,Windows Communication Foundation Architecture Overview 参阅 WCF 介绍(一) WCF 介绍(二) WCF 介绍(三) WCF 介绍(四)

Windows Communication Foundation介绍(三)

示例下载(Orcas 下编写) 参阅: WCF 介绍(一) | WCF 介绍(二) | WCF 介绍(三) | WCF 介绍(四)

四、Service Contract编程模型

(二)中,我以“SayHello”为例讲解了如何定义一个Service。其核心就是为接口或类施加ServiceContractAttribute,为方法施加OperationContractAttribute。在Service的方法中,可以接受多个参数,也可以有返回类型,只要这些数据类型能够被序列化。这样一种方式通常被称为本地对象,远程过程调用(local-object, Remoting-Procedure-Call)方式,它非常利于开发人员快速地进行Service的开发。 在Service Contract编程模型中,还有一种方式是基于Message Contract的。服务的方法最多只能有一个参数,以及一个返回值,且它们的数据类型是通过Message Contract自定义的消息类型。在自定义消息中,可以为消息定义详细的Header和Body,使得对消息的交换更加灵活,也更利于对消息的控制。 一个有趣的话题是当我们定义一个Service时,如果一个private方法被施加了OperationContractAttribute,那么对于客户端而言,这个方法是可以被调用的。这似乎与private对于对象封装的意义有矛盾。但是这样的规定是有其现实意义的,因为对于一个服务而言,服务端和客户端的需求往往会不一致。在服务端,该服务对象即使允许被远程调用,但本地调用却可能会因情况而异。如下面的服务定义:

  1. [ServiceContract]   
  2. public class BookTicket   
  3. {   
  4.    [OperationContract]   
  5.    public bool Check(Ticket ticket)   
  6.    {   
  7.       bool flag;   
  8.       //logic to check whether the ticket is none;   
  9.       return flag;   
  10.    }   
  11.    [OperationContract]   
  12.    private bool Book(Ticket ticket)   
  13.    {   
  14.      //logic to book the ticket   
  15.    }   
  16. }  

在服务类BookTicket中,方法Check和Book都是服务方法,但后者被定义成为private方法。为什么呢?因为对于客户而言,首先会检查是否还有电影票,然而再预定该电影票。也就是说这两项功能都是面向客户的服务,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有可能被查询电影票、预定电影票、出售电影票等业务逻辑所调用。而Book方法,则只针对远程客户,只可能被远程调用。为了保证该方法的安全,将其设置为private,使得本地对象不至于调用它。 因此在WCF中,一个方法是否应该被设置为服务方法,以及应该设置为public还是private,都需要根据具体的业务逻辑来判断。如果涉及到私有的服务方法较多,一种好的方法是利用设计模式的Façade模式,将这些方法组合起来。而这些方法的真实逻辑,可能会散放到各自的本地对象中,对于这些本地对象,也可以给与一定的访问限制,如下面的代码所示:

  1. internal class BusinessObjA   
  2. {   
  3.    internal void FooA(){}   
  4. }   
  5. internal class BusinessObjB   
  6. {   
  7.    internal void FooB(){}   
  8. }   
  9. internal class BusinessObjC   
  10. {   
  11.    internal void FooC(){}   
  12. }   
  13. [ServiceContract]   
  14. internal class Façade   
  15. {   
  16.    private BusinessObjA objA = new BusinessObjA();   
  17.    private BusinessObjB objB = new BusinessObjB();   
  18.    private BusinessObjC objC = new BusinessObjC();   
  19.    [OperationContract]   
  20.    private void SvcA()   
  21.    {   
  22.       objA.FooA();   
  23.    }   
  24.  [OperationContract]   
  25.    private void SvcB()   
  26.    {   
  27.       objB.FooB();   
  28.    }   
  29.    [OperationContract]   
  30.    private void SvcC()   
  31.    {   
  32.       objC.FooC();   
  33.    }   
  34. }  

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

  1. [ServiceContract]   
  2. public interface IOne   
  3. {   
  4.    [OperationContract(IsOneWay=true)]   
  5.    void A();   
  6. }   
  7. [ServiceContract]   
  8. public interface ITwo   
  9. {   
  10.    [OperationContract]   
  11.    void B();   
  12. }   
  13. [ServiceContract]   
  14. public interface IOneTwo : IOne, ITwo   
  15. {   
  16.    [OperationContract]   
  17.    void C();   
  18. }  

在这个例子中,接口IOneTwo继承了接口IOne和ITwo。此时服务IOneTwo暴露的服务方法应该为方法A、B和C。 然而当我们采用Duplex消息交换模式(文章后面会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF要求服务接口IB在继承另一个服务接口IA时,IB的回调接口IBCallBack必须同时继承IACallBack,否则会抛出InvalidContractException异常。正确的定义如下所示:

  1. [ServiceContract(CallbackContract = IACallback)]   
  2. interface IA {}   
  3. interface IACallback {}   
  4.   
  5. [ServiceContract(CallbackContract = IBCallback)]   
  6. interface IB : IA {}   
  7. interface IBCallback : IACallback {}  

五、消息交换模式(Message Exchange Patterns,MEPS)

在WCF中,服务端与客户端之间消息的交换共有三种模式:Request/Reply,One-Way,Duplex。 1、Request/Reply 这是默认的一种消息交换模式,客户端调用服务方法发出请求(Request),服务端收到请求后,进行相应的操作,然后返回一个结果值(Reply)。 如果没有其它特别的设置,一个方法如果标记了OperationContract,则该方法的消息交换模式就是采用的Request/Reply方式,即使它的返回值是void。当然,我们也可以将IsOneWay设置为false,这也是默认的设置。如下的代码所示:

  1. [ServiceContract]   
  2. public interface ICalculator   
  3. {   
  4.    [OperationContract]   
  5.    int Add(int a, int b);   
  6.   
  7.    [OperationContract]   
  8.    int Subtract(int a, int b);   
  9. }  

2、One-Way 如果消息交换模式为One-Way,则表明客户端与服务端之间只有请求,没有响应。即使响应信息被发出,该响应信息也会被忽略。这种方式类似于消息的通知或者广播。当一个服务方法被设置为One-Way时,如果该方法有返回值,会抛出InvalidOperationException异常。 要将服务方法设置为One-Way非常简单,只需要将OperationContractAttribute的属性IsOneWay设置为true就可以了,如下的代码所示:

  1. public class Radio   
  2. {   
  3.    [OperationContract(IsOneWay=true)]   
  4.    private void BroadCast();   
  5. }  

3、Duplex Duplex消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。 要实现消息交换的Duplex,相对比较复杂。它需要定义两个接口,其中服务接口用于客户端向服务端发送消息,而回调接口则是从服务端返回消息给客户端,它是通过回调的方式来完成的。接口定义如下: 服务接口:

  1. [ServiceContract(SessionMode=SessionMode.Allowed, CallbackContract=typeof(ICalculatorDuplexCallback))]   
  2. public interface ICalculatorDuplex   
  3. {   
  4.   
  5.     [OperationContract(IsOneWay=true)]   
  6.     void Clear();   
  7.   
  8.     [OperationContract(IsOneWay=true)]   
  9.     void AddTo(double n);   
  10.   
  11.     [OperationContract(IsOneWay = true)]   
  12.     void SubtractFrom(double n);   
  13. }  

回调接口:

  1. public interface ICalculatorDuplexCallback   
  2. {   
  3.     [OperationContract(IsOneWay=true)]   
  4.     void Equals(double result);   
  5.   
  6.     [OperationContract(IsOneWay=true)]   
  7.     void Equation(string equation);   
  8. }  

注意在接口定义中,每个服务方法的消息转换模式均设置为One-Way。此外,回调接口是被本地调用,因此不需要定义[ServiceContract]。在服务接口中,需要设置ServiceContractAttribute的CallbackContract属性,使其指向回调接口的类型type。 对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还是PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。而PerCall方式下,服务对象是在方法被调用时创建,结束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。本例的实现如下:

  1. [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]   
  2. public class CalculatorService : ICalculatorDuplex   
  3. {   
  4.     double result;   
  5.     string equation;   
  6.     ICalculatorDuplexCallback callback;   
  7.   
  8.     public CalculatorService()   
  9.     {   
  10.         result = 0.0;   
  11.         equation = result.ToString();   
  12.         callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();   
  13.   
  14.     }  
  15.  
  16.     #region ICalculatorDuplex Members   
  17.   
  18.     public void Clear()   
  19.     {   
  20.         equation += "=" + result.ToString();   
  21.         callback.Equation(equation);   
  22.   
  23.     }   
  24.   
  25.     public void AddTo(double n)   
  26.     {   
  27.         result += n;   
  28.         equation += "+" + n.ToString();   
  29.         callback.Equals(result);   
  30.     }   
  31.   
  32.     public void SubtractFrom(double n)   
  33.     {   
  34.         result -= n;   
  35.         equation += "-" + n.ToString();   
  36.         callback.Equals(result);   
  37.     }  
  38.  
  39.     #endregion   
  40. }  

在类CalculatorService中,回调接口对象callback通过OperationContext.Current.GetCallbackChannel<>()获取。然后在服务方法例如AddTo()中,通过调用该回调对象的方法,完成服务端向客户端返回消息的功能。 在使用Duplex时,Contract使用的Binding应该是系统提供的WSDualHttpBinding,如果使用BasicHttpBinding,会出现错误。因此WCF Service Host程序配置文件应该如下所示:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <!-- When deploying the service library project, the content of the config file must be added to the host's    
  4.   app.config file. System.Configuration does not support config files for libraries. -->  
  5.   <system.serviceModel>  
  6.     <services>  
  7.       <service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.Service1Behavior">  
  8.         <host>  
  9.           <baseAddresses>  
  10.             <add baseAddress = "http:localhost:8080/Service1" />  
  11.           </baseAddresses>  
  12.         </host>  
  13.         <!-- Service Endpoints -->  
  14.         <!-- Unless fully qualified, address is relative to base address supplied above -->  
  15.         <endpoint address ="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.ICalculatorDuplex" />  
  16.       </service>  
  17.     </services>  
  18.     <behaviors>  
  19.       <serviceBehaviors>  
  20.         <behavior name="WcfServiceLibrary1.Service1Behavior">  
  21.           <!-- To avoid disclosing metadata information,    
  22.           set the value below to false and remove the metadata endpoint above before deployment -->  
  23.           <serviceMetadata httpGetEnabled="True"/>  
  24.           <!-- To receive exception details in faults for debugging purposes,    
  25.           set the value below to true.  Set to false before deployment    
  26.           to avoid disclosing exception information -->  
  27.           <serviceDebug includeExceptionDetailInFaults="False" />  
  28.         </behavior>  
  29.       </serviceBehaviors>  
  30.     </behaviors>  
  31.   </system.serviceModel>  
  32. </configuration>  

使用命令svcutil.exe http://localhost:8080/Service1?wsdl生成客户端代理类和配置文件,配置文件内容类似:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <configuration>  
  3.     <system.serviceModel>  
  4.         <client>  
  5.             <endpoint address="http:localhost:8080/Service1"    
  6.                       binding="wsDualHttpBinding"  
  7.                       contract="ICalculatorDuplex">  
  8.                 <identity>  
  9.                     <userPrincipalName value="allisok-PC/allisok" />  
  10.                 </identity>  
  11.             </endpoint>  
  12.         </client>  
  13.     </system.serviceModel>  
  14. </configuration>  

当服务端将信息回送到客户端后,对消息的处理是由回调对象来处理的,所以回调对象的实现应该是在客户端完成,如下所示的代码应该是在客户端中:

  1. public class CallbackHandler : ICalculatorDuplexCallback   
  2. {  
  3.     #region ICalculatorDuplexCallback Members   
  4.   
  5.     void ICalculatorDuplexCallback.Equals(double result)   
  6.     {   
  7.         Console.WriteLine("Result({0})", result);   
  8.     }   
  9.   
  10.     void ICalculatorDuplexCallback.Equation(string equation)   
  11.     {   
  12.         Console.WriteLine("Equation({0})", equation);   
  13.     }  
  14.  
  15.     #endregion   
  16. }  

客户端调用服务对象相应的为:

  1. class ClientApp   
  2. {   
  3.     static void Main(string[] args)   
  4.     {   
  5.         InstanceContext instanceContext = new InstanceContext(new CallbackHandler());   
  6.   
  7.         CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);   
  8.   
  9.         // 使用 "client" 变量在服务上调用操作。   
  10.         double value;   
  11.         value = 100;   
  12.         client.AddTo(value);   
  13.   
  14.         value = 50;   
  15.         client.SubtractFrom(value);   
  16.   
  17.         client.Clear();   
  18.            
  19.         // 始终关闭客户端。   
  20.         //client.Close();   
  21.   
  22.         Console.ReadLine();   
  23.   
  24.     }   
  25. }  

注意在Duplex中,会话创建的时机并不是客户端创建Proxy实例的时候,而是当服务对象的方法被第一次调用时,会话方才建立,此时服务对象会在方法调用之前被实例化,直至会话结束,服务对象都是存在的。 参阅 WCF 介绍(一) WCF 介绍(二) WCF 介绍(三) WCF 介绍(四)

Windows Communication Foundation介绍(四)

参阅: WCF 介绍(一) | WCF 介绍(二) | WCF 介绍(三) | WCF 介绍(四)

六、定义DataContract

我在介绍如何定义一个ServiceContract时,举了这样的一个例子,代码如下:

  1. [ServiceContract]   
  2. public class BookTicket   
  3. {   
  4.    [OperationContract]   
  5.    public bool Check(Ticket ticket)   
  6.    {   
  7.       bool flag;   
  8.       //logic to check whether the ticket is none;   
  9.       return flag;   
  10.    }   
  11.    [OperationContract]   
  12.    private bool Book(Ticket ticket)   
  13.    {   
  14.       //logic to book the ticket   
  15.    }   
  16. }  

在Service类BookTicket中,两个服务方法Check和Book的参数均为Ticket类型。这个类型是自定义类型,根据WCF的要求,该类型必须支持序列化的操作,方才可以在服务方法中作为消息被传递。 在.Net中,除了基本类型如int,long,double,以及枚举类型和String类型外,一个自定义的类型如要支持序列化操作,应该标记该类型为[Serializable],或者使该类型实现ISerializable接口。而在WCF中,推荐的一种方式是为这些类型标记DataContractAttribute。方法如下:

  1. [DataContract]   
  2. public class Ticket   
  3. {   
  4. private string m_movieName;   
  5.   
  6.    [DataMember]   
  7.    public int SeatNo;   
  8.    [DataMember]   
  9.    public string MovieName   
  10.    {   
  11.       get {return m_movieName;}   
  12.       set {m_movieName = value;}   
  13.    }   
  14.    [DataMember]   
  15.    private DateTime Time;   
  16. }  

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

  1. [DataContract]   
  2. public class MyGeneric<T>   
  3. {   
  4.     [DataMember]   
  5.     public T theData;   
  6. }  

在类MyGeneric中,泛型参数T必须支持序列化。如实例化该对象:

  1. MyGeneric<int> intObject = new MyGeneric();   
  2. MyGeneric<Custom> customObject = new MyGeneric();  

对象intObject由于传入的泛型参数为int基本类型,因此可以被序列化;而对象customObject是否能被序列化,则要看传入的泛型参数CustomType类型是否支持序列化。 DataContract以Namespace和Name来唯一标识,我们可以在DataContractAttribute的Namespace属性、Name属性中进行设置。如未设置DataContract的Name属性,则默认的名字为定义的类型名。DataMember也可以通过设置Name属性,默认的名字为定义的成员名。如下代码所示:

  1. namespace MyCompany.OrderProc   
  2. {   
  3.     [DataContract]   
  4.     public class PurchaseOrder   
  5.     {   
  6.         // DataMember名字为默认的Amount;   
  7.         [DataMember]   
  8.         public double Amount;   
  9.           
  10.         // Name属性将重写默认的名字Ship_to,此时DataMember名为Address;   
  11.         [DataMember(Name = "Address")]   
  12.         public string Ship_to;   
  13.     }   
  14.     //Namespace为默认值:   
  15.     // http:schemas.datacontract.org/2004/07/MyCompany.OrderProc   
  16.     //此时其名为PurchaseOrder而非MyInvoice   
  17.     [DataContract(Name = "PurchaseOrder")]   
  18.     public class MyInvoice   
  19.     {   
  20.         // Code not shown.   
  21.     }   
  22.   
  23.     // 其名为Payment而非MyPayment   
  24.     // Namespace被设置为http:schemas.example.com/   
  25.     [DataContract(Name = "Payment",   
  26.         Namespace = "http:schemas.example.com/")]   
  27.     public class MyPayment   
  28.     {   
  29.         // Code not shown.   
  30.     }   
  31. }  

// 重写MyCorp.CRM下的所有DataContract的Namespace

  1. // 3.0 的语法?   
  2. [assembly:ContractNamespace(   
  3.       ClrNamespace = "MyCorp.CRM",   
  4.       Namespace= "http:schemas.example.com/crm")]   
  5. namespace MyCorp.CRM   
  6. {   
  7.     // 此时Namespace被设置为http:schemas.example.com/crm.   
  8.     // 名字仍然为默认值Customer   
  9.     [DataContract]   
  10.     public class Customer   
  11.     {   
  12.         // Code not shown.   
  13.     }   
  14. }  

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

  1. [DataContract]   
  2. public class BaseType   
  3. {   
  4.     [DataMember] public string zebra;   
  5. }   
  6.   
  7. [DataContract]   
  8. public class DerivedType : BaseType   
  9. {   
  10.     [DataMember(Order = 0)] public string bird;   
  11.     [DataMember(Order = 1)] public string parrot;   
  12.     [DataMember] public string dog;   
  13.     [DataMember(Order = 3)] public string antelope;   
  14.     [DataMember] public string cat;   
  15.     [DataMember(Order = 1)] public string albatross;   
  16. }  

序列化后的XML内容如下:

  1. <DerivedType>  
  2.     <zebra/>  
  3.     <cat/>  
  4.     <dog/>  
  5.     <bird/>  
  6.     <albatross/>  
  7.     <parrot/>  
  8.     <antelope/>  
  9. </DerivedType>  

因为成员zebra为父类成员,首先其顺序在最前面。cat和dog未指定Order,故在指定了Order的其它成员之前,两者又按照字母顺序排列。parrot和albatross均指定Order值为1,因此也按照字母顺序排列在Order值为0的bird之后。 判断两个DataContract是否相同,应该根据DataContract的Namespace和Name,以及DataMember的Name和Order来综合判断。例如下面代码所示的类Customer和Person其实是同一个DataContract:

  1. [DataContract]   
  2. public class Customer   
  3. {   
  4.     [DataMember]   
  5.     public string fullName;   
  6.   
  7.     [DataMember]   
  8.     public string telephoneNumber;   
  9. }   
  10.   
  11. [DataContract(Name=”Customer”)]   
  12. public class Person   
  13. {   
  14.     [DataMember(Name = "fullName")]   
  15.     private string nameOfPerson;   
  16.   
  17.     private string address;   
  18.   
  19.     [DataMember(Name= "telephoneNumber")]   
  20.     private string phoneNumber;   
  21. }  

再例如下面代码所示的类Coords1、Coords2、Coords3也是相同的DataContract,而类Coords4则因为顺序不同,因而与前面三个类是不同的:

  1. [DataContract(Name= "Coordinates")]   
  2. public class Coords1   
  3. {   
  4.     [DataMember] public int X;   
  5.     [DataMember] public int Y;   
  6. }   
  7.   
  8. [DataContract(Name= "Coordinates")]   
  9. public class Coords2   
  10. {   
  11.      [DataMember] public int Y;   
  12.      [DataMember] public int X;   
  13. }   
  14.   
  15. [DataContract(Name= "Coordinates")]   
  16. public class Coords3   
  17. {   
  18.      [DataMember(Order=2)] public int Y;   
  19.      [DataMember(Order=1)] public int X;   
  20. }   
  21. [DataContract(Name= "Coordinates")]   
  22. public class Coords4   
  23. {   
  24.      [DataMember(Order=1)] public int Y;   
  25.      [DataMember(Order=2)] public int X;   
  26. }  

当DataContract处于继承体系时,还需要注意的是对象的“多态”问题。如果在服务端与客户端之间要传递消息,经常会涉及到类型的动态绑定。根据规定,如果消息的类型是子类类型,那么发送消息一方就不能传递基类类型。相反,如果消息类型是父类类型,那么发送消息一方就可以是父类本身或者其子类。从这一点来看,WCF的规定是与面向对象思想并行不悖的。但是可能存在的问题是,当消息类型定义为父类类型,而发送消息一方传递其子类时,服务端有可能对该子类类型处于“未知”状态,从而不能正常地反序列化。所以,WCF为DataContract提供了KnownTypeAttribute,通过设置它来告知服务端可能存在的动态绑定类类型。 举例来说,如果我们定义了这样三个类:

  1. [DataContract]   
  2. public class Shape { }   
  3.   
  4. [DataContract(Name = "Circle")]   
  5. public class CircleType : Shape { }   
  6.   
  7. [DataContract(Name = "Triangle")]   
  8. public class TriangleType : Shape { }  

然后在类CompanyLogo中定义Shape类型的字段,如下所示:

  1. [DataContract]   
  2. public class CompanyLogo   
  3. {   
  4.     [DataMember]   
  5.     private Shape ShapeOfLogo;   
  6.     [DataMember]   
  7.     private int ColorOfLogo;   
  8. }  

此时的CompanyLogo类由于正确的设置了[DataContract]和[DataMember],而Shape类型也是支持序列化的,因此该类型是可以被序列化的。然而一旦客户端在调用CompanyLogo类型的对象时,为字段ShapeOfLogo设置其值为CircleType或TriangleType类型的对象,就会发生反序列化错误,因为服务端并不知道CircleType或TriangleType类型,从而无法进行正确的匹配。所以上述的CompanyLogo类的定义应修改如下:

  1. [DataContract]   
  2. [KnownType(typeof(CircleType))]   
  3. [KnownType(typeof(TriangleType))]   
  4. public class CompanyLogo   
  5. {   
  6.     [DataMember]   
  7.     private Shape ShapeOfLogo;   
  8.     [DataMember]   
  9.     private int ColorOfLogo;   
  10. }  

类的继承如此,接口的实现也是同样的道理,如下例所示:

  1. public interface ICustomerInfo   
  2. {   
  3.     string ReturnCustomerName();   
  4. }   
  5.   
  6. [DataContract(Name = "Customer")]   
  7. public class CustomerType : ICustomerInfo   
  8. {   
  9.     public string ReturnCustomerName()   
  10.     {   
  11.         return "no name";   
  12.     }   
  13. }   
  14.   
  15. [DataContract]   
  16. [KnownType(typeof(CustomerType))]   
  17. public class PurchaseOrder   
  18. {   
  19.     [DataMember]   
  20.     ICustomerInfo buyer;   
  21.   
  22.     [DataMember]   
  23.     int amount;   
  24. }  

由于PurchaseOrder中定义了ICustomerInfo接口类型的字段,如要该类能被正确的反序列化,就必须为类PurchaseOrder加上[KnownType(typeof(CustomerType))]的标注。 对于集合类型也有相似的规定。例如Hashtable类型,其内存储的均为object对象,但实际设置的值可能是一些自定义类型,此时也许要通过KnownType进行标注。例如在类LibraryCatalog中,定义了Hashtable类型的字段theCatalog。该字段可能会设置为Book类型和Magazine类型,假定Book类型和Magazine类型均被定义为DataContract,则类LibraryCatalog的正确定义应如下所示:

  1. [DataContract]   
  2. [KnownType(typeof(Book))]   
  3. [KnownType(typeof(Magazine))]   
  4. public class LibraryCatalog   
  5. {   
  6.     [DataMember]   
  7.     System.Collections.Hashtable theCatalog;   
  8. }  

如果在一个DataContract中,定义一个object类型的字段。由于object类型是所有类型的父类,所以需要我们利用KnownType标明客户端允许设置的类型。例如类MathOperationData:

  1. [DataContract]   
  2. [KnownType(typeof(int[]))]   
  3. public class MathOperationData   
  4. {   
  5.     private object numberValue;   
  6.     [DataMember]   
  7.     public object Numbers   
  8.     {   
  9.         get { return numberValue; }   
  10.         set { numberValue = value; }   
  11.     }   
  12.     //[DataMember]   
  13.     //public Operation Operation;   
  14. }  

属性Numbers其类型为object,而KnownType设置的类型是int[],因此可以接受的类型就包括:整型,整型数组以及List类型。如下的调用都是正确的:

  1. static void Run()   
  2. {   
  3.     MathOperationData md = new MathOperationData();   
  4.   
  5.     int a = 100;   
  6.     md.Numbers = a;   
  7.   
  8.     int[] b = new int[100];   
  9.     md.Numbers = b;   
  10.       
  11.     List c = new List();   
  12.     md.Numbers = c;      
  13. }  

但如果设置Number属性为ArrayList,即使该ArrayList对象中元素均为int对象,也是错误的:

  1. static void Run()   
  2. {   
  3.     MathOperationData md = new MathOperationData();   
  4.   
  5. ArrayList d = new ArrayList();   
  6.     md.Numbers = d;   
  7. }  

一旦一个DataContract类型标注了KnownTypeAttribute,则该Attribute的作用域可以施加到其子类中,如下所示:

  1. [DataContract]   
  2. [KnownType(typeof(CircleType))]   
  3. [KnownType(typeof(TriangleType))]   
  4. public class MyDrawing   
  5. {   
  6.     [DataMember]   
  7.     private object Shape;   
  8.     [DataMember]   
  9.     private int Color;   
  10. }   
  11.   
  12. [DataContract]   
  13. public class DoubleDrawing : MyDrawing   
  14. {   
  15.     [DataMember]   
  16.     private object additionalShape;   
  17. }  

虽然DoubleDrawing没有标注KnowTypeAttribute,但其字段additionalShape仍然可以被设置为CircleType类型或TriangleType类型,因为其父类已经被设置为KnowTypeAttribute。 注:KnowTypeAttribute可以标注类和结构,但不能标注接口。此外,DataContract同样不能标注接口,仅可以标注类、结构和枚举。要使用DataContractAttribute、DataMemberAttribute和KnownTypeAttribute,需要添加WinFx版本的System.Runtime.Serialization程序集的引用。 参阅 WCF 介绍(一) WCF 介绍(二) WCF 介绍(三) WCF 介绍(四)

http://www.xwy2.com/article.asp?id=20

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值