16.3 WCF核心元素
WCF框架中包含大量的基础概念,在16.2节中创建了一个简单的WCF程序,本节将对前面出现的WCF核心概念进行详细介绍,如WCF地址、绑定和合约等。
16.3.1 地址
WCF的每一个服务都具有一个唯一的地址(Address)。每个地址都包含两个重要元素:服务位置与传输协议,或者是用于服务通信的传输方式。服务位置包括目标主机名、站点(或者网络)、通信端口、管道(或者队列),以及一个可选的特定路径或者URI。
总地来说,WCF支持如下几种传输方式:HTTP,TCP,Peer Network(对等网),IPC(基于命名管道的内部进程通信),MSMQ。
地址通常采用如下格式。
[传输协议]://[主机名或域名][:可选端口]
例如,如下所示的这些都是正确的地址。
http://localhost:8080 http://localhost:5678/MyWcfService net.tcp://localhost:1010/MyWcfService net.pipe://localhost/MyWcfPipe net.msmq://localhost/public/MyWcfService net.msmq://localhost/MyWcfService
如上述示例所示,可以将地址“http://localhost:8080”理解为“采用HTTP访问localhost主机,并在8080端口等待用户的调用”。对于“http://localhost:5678/MyWcfService”地址则可以理解为“采用HTTP访问localhost主机,MyWcfService服务在5678端口处等待用户的调用。”
1.TCP地址
TCP地址采用net.tcp作为协议进行传输,通常它还带有端口号。例如:
net.tcp://localhost:8018/MyWcfService
如果没有指定端口号,则TCP地址的默认端口号为808。例如:
net.tcp://localhost/MyWcfService
另外,两个TCP地址(来自于相同的主机)可以共享一个端口。例如:
net.tcp://localhost:8018/MyWcfService net.tcp://localhost:8018/MyOtherWcfService
2.HTTP地址
HTTP地址是使用频率最高的一种地址,它使用HTTP进行传输,也可以利用HTTPS进行安全传输。HTTP地址通常会被用作对外的基于Internet的服务,并为其指定端口号。例如:
http://localhost:8088 http://localhost:8088/MyWcfService https://localhost/MyWcfService
如果没有指定端口号,则默认为80。与TCP地址相似,两个相同主机的HTTP地址可以共享一个端口,甚至相同的计算机。
3.IPC地址
IPC地址使用net.pipe协议进行传输,这意味着它将使用Windows的命名管道机制。在WCF中,使用命名管道的服务只能接收来自同一台计算机的调用。
因此,在使用时必须明确指定本地计算机名或者直接命名为localhost,然后再为管道名提供一个唯一的标识字符串。例如:
net.pipe://localhost/MyWcfPipe net.pipe://zhht/MyWcfPipe
注意
每台计算机只能打开一个命名管道。因此,两个命名管道地址在同一台计算机上不能共享一个管道名。
4.MSMQ地址
MSMQ地址使用net.msmq协议进行传输,即使用了微软消息队列(Microsoft Message Queue)机制。在使用时必须为MSMQ地址指定队列名。如果是处理私有队列,则必须指定队列类型。例如:
net.msmq://localhost/private/MyWcfService net.msmq://localhost/private/MyOtherWcfService
但对于公有队列而言,队列类型可以省略。例如:
net.msmq://localhost/MyWcfService net.msmq://zhht/private/MyWcfService
5.对等网地址
对等网地址(Peer Network Address)使用net.p2p协议进行传输,它使用了Windows的对等网传输机制。如果没有使用解析器,就必须为对等网地址指定对等网名称、唯一的路径以及端口。
16.3.2 绑定
WCF中的绑定(Binding)指定了服务的通信方式。
使用绑定也是WCF开发区别于Web服务开发的一个重要方面。因为WCF带有许多可供选择的绑定,每种绑定都适合于特定的需求。另外,如果现有的绑定类型不能满足需求,还可以通过扩展CustomBinding类型创建绑定。
简单来说,WCF绑定可以指定如下特性。
(1)传输协议。
(2)安全要求。
(3)编码格式。
(4)事务处理要求。
一个绑定类型包含多个绑定元素,它们描述了上面所有的绑定要求。WCF内置了9种类型的绑定,如表16-2所示列出了这些绑定类型及其说明。
表16-2 WCF绑定类型
提示
所有以Net前缀开始的绑定类型都使用二进制编码在.NET应用程序之间通信,这种编码格式比文本格式要快。
在表16-2中列出的每种绑定类型都有自己的特性。例如,以WS为前缀的绑定类型是独立于平台的,支持Web服务规范。以Net为前缀的使用二进制格式,使.NET应用程序之间的通信具有更高的性能。如表16-3所示针对这些绑定类型按特性进行了分类。
表16-3 绑定类型支持的特性
除了定义绑定之外,WCF服务还必须定义端点。端点依赖于合约、服务的地址和绑定。例如,在下面的示例代码中,实例了一个ServiceHost对象,将地址“http://localhost:8080/MyWcfService”和一个WSHttpBinding实例绑定到服务的一个端点上。
static void StartService() { ServiceHost host; Uri baseAddress = new Uri("http://localhost:8080/MyWcfService"); host = new ServiceHost(typeof(Service1)); WSHttpBinding binding = new WSHttpBinding(); host.AddServiceEndpoint(typeof(Service1), binding, baseAddress); host.Open(); }
除了以编程方式定义绑定之外,还可以在应用程序的配置文件中定义它。WCF的所有配置都位于<system.serviceModel>节点中,<service>节点定义了WCF中所提供的服务,<bindings>节点定义了绑定信息。
例如,下面的配置文件同样实现了上述代码的功能。
<?xml version="1.0" encoding="utf-8" ? > <configuration> <system.serviceModel> <services> <service name="FirstWcfServiceLibrary.Service1"> <host> <baseAddresses> <add baseAddress = "http://localhost:8080/MyWcfService" /> </baseAddresses> </host> <endpoint address ="" binding="wsHttpBinding" contract="FirstWcfServiceLibrary.IService1" bindingConfiguration="config1"/> </service> </services> <bindings> <wsHttpBinding> <binding name="config1"> <reliableSession enabled="true"/> </binding> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
可以看到,一个WCF服务必须要有一个端点,该端点包含地址、绑定和合约信息。WSHttpBinding的默认配置由bindingConfiguration属性指定,该属性引用了下方名为“config1”的绑定配置信息。该配置信息位于<bindings>节点中,并启用了reliableSession。
16.3.3 合约
任何一个分布式应用程序,之所以能够互相传递消息,都是事先制定好数据交换规则的,这个规则正是交换数据的双方(比如服务器端和客户端)能彼此理解对方的依据。WCF作为分布式开发技术的一种,同样具有这样一种特性。而在WCF中制定的规则就被称为合约(Contract),它是WCF的消息标准,是任何一个WCF程序不可或缺的一部分。
在WCF中合约分为4种,分别为:定义服务操作的服务合约(Service Contract),定义自定义数据结构的数据合约(Data Contract),定义错误异常的异常合约(Fault Contract),以及直接控制消息格式的消息合约(Message Contract)。
1.服务合约
一般情况下,用接口(Interface)来定义服务合约。虽然也可以使用类(Class)来定义,但使用接口的好处更明显一些。主要表现在如下方面。
(1)便于合约的继承,不同的类型可以自由实现相同的合约。
(2)同一服务类型可以实现多个合约。
(3)和接口隔离原则相同,随时可以修改服务类型。
(4)便于制定版本升级策略,让新老版本的服务合约同时使用。
服务合约定义了WCF服务可以执行的操作,它包括ServiceContract和OperationContract两种。ServiceContract用于类或者接口上,用于指定此类或者接口能够被远程调用,而OperationContract用于类中的方法上,用于指定该方法可被远程调用。
例如,下面的示例代码使用ServiceContract属性声明接口IMyFirstService可以被远程调用,OperationContract属性声明getTime()方法也可以被远程调用。
[ServiceContract] public interface IMyFirstService { [OperationContract] string getTime(); }
在表16-4中列出了ServiceContract属性的可用选项及其说明。
表16-4 ServiceContract属性的选项及说明
在表16-5中列出了OperationContract属性的可用选项及其说明。
表16-5 OperationContract属性的选项及说明
提示
在服务合约中,还可以使用DeliveryRequirements属性定义服务的传输要求;使用RequireOrderedDelivery属性指定所传递的消息必须以相同的顺序到达;使用QueuedDeliveryRequirements属性指定消息以断开连接的方式传送。
2.数据合约
数据合约也分为两种:DataContract和DataMember。DataContract用于类或者结构上,指定此类或者接口能够被序列化并传输,而DataMember只能用在类或者接口的属性(Property)或者字段(Field)上,指定该属性或者字段能够被序列化传输。
数据合约的序列化不同于普通.NET的序列化机制,在运行时所有的字段(包括私有字段)都会被序列化,而在执行数据合约的序列化时只有被标记了DataMember的属性才会被序列化。
例如,下面创建一个Person类并使用DataContract属性指定为可序列化。另外还创建了一个服务合约并定义了一个Add()方法接收一个Person类型的参数。
[DataContract] public class Person { [DataMember] public int Id; [DataMember] public string Name; [DataMember] public DateTime Birthday; [DataMember] public string Email; } [ServiceContract] public interface IPerson { [OperationContract] bool Add(Person p); }
如表16-6所示列出了DataMember属性可用的选项及其说明。
表16-6 DataMember属性的选项及说明
例如,要对Order对象进行序列化,它的定义如下。在这里使用Namespace选项重新定义了命名空间。对于数据成员使用Name选项指定了一个别名,使用Order选项指定了显示的顺序。
[DataContract(Namespace = "http://www.itzcn.com")] public class Order { [DataMember(Name="OrderId", Order=1)] public Guid ID; [DataMember(Name="OrderDate", Order=2)] public DateTime Date; [DataMember] public string Customer; [DataMember] public string Address; public double TotalPrice; }
执行后,Order对象的序列化XML如下所示。
<Order xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.itzcn.com"> <Address></Address> <Customer></Customer> <OrderId></OrderId> <OrderDate></OrderDate> </Order>
通过定义的数据合约以及与最终生成XML结构的对比,可以总结出WCF默认采用如下的数据合约序列化规则。
(1)XML的根节点为数据合约中类的名称,默认命名空间格式为http://schemas.datacontract.org/2004/07/{类所在命名空间}。
(2)只有使用DataMember属性定义的字段或者属性才能作为数据成员参与序列化(例如本实例中的TotalPrice属性不会再现在序列化后的XML中)。
(3)所有数据成员均以XML元素的形式被序列化。
(4)默认数据成员按照字母顺序排列。
(5)如果通过Order指定了顺序,且值相同,则以字母先后顺序排列。
(6)未指定Order的成员顺序在指定Order顺序之前。
(7)如果DataContract处于继承的类中,那么将优先显示父类中的成员。
3.消息合约
如果需要在WCF服务中对SOAP消息进行控制则必须使用消息合约。在消息合约中,可以指定消息的哪些部分出现在SOAP标题中,哪些部分要放在SOAP的主体中。
例如,下面的示例演示了使用ProcessOrderMessage类定义消息合约的代码。
[MessageContract] public class ProcessOrderMessage { [MessageHeader] public Guid ID; [MessageBodyMember] public Order order; }
如上述代码所示,在这里使用MessageContract属性指定ProcessOrderMessage为一个消息合约,对于SOAP消息中的标题使用MessageHeader属性指定,SOAP主体使用MessageBodyMember属性指定。
为了使用上面定义的消息合约,这里将IProcessOrder接口定义为服务合约,并定义了一个可调用的ProcessOrder()方法。代码如下所示。
[ServiceContract] public interface IProcessOrder { [OperationContract] ProcessOrderMessage ProcessOrder(ProcessOrderMessage message); }
4.异常合约
在WCF中所有合约基本上都是围绕着一个服务调用时的消息交换来进行的。例如,服务的客户端通过向服务的提供者发送请求消息;服务提供者在接收到该请求后激活服务实例,并调用相应的服务操作;最终将返回的结果以回复消息的方式返回给服务的客户端。
但是,如果服务操作不能正确地执行,服务端将会通过一种特殊的消息将错误信息返回给客户端,这种消息被称为异常消息。对于异常消息,同样需要相应的合约来定义其结构,这种合约称为异常合约(Fault Contract)。
WCF通过FaultContract属性来定义异常合约。由于异常合约是基于服务操作级别的,所以该属性将直接应用于服务合约接口或者操作合约的方法上。
下面的示例代码演示了FaultContract定义异常合约的方式。
[ServiceContract] public interface IUser { [OperationContract] [FaultContract(typeof(LoginTimeOut))] bool Login(string username, string userpass); }
在上述代码中,使用FaultContract属性声明调用Login()方法会抛出LoginTimeOut类的异常表示登录超时。
与本节前面介绍的其他合约一样,异常合约的FaultContract属性也有很多可用选项,如表16-7中列出了它们及其说明。
表16-7 FaultContract属性的选项及说明
共有条评论 网友评论