转载于MSDN:http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/wcfarch.mspx?mfr=true
摘要:概览 Windows Communication Foundation (WCF) 体系结构及其主要概念。代码示例演示 WCF 约定、终结点和行为。
简介 | |
WCF 基础 | |
代码示例 | |
小结 |
本文档提供 Windows Communication Foundation (WCF) 体系结构的概览。本文旨在阐释 WCF 中的主要概念以及它们如何协调工作。还有几个代码示例对这些概念进行深入阐释,但代码不是本文档的重点。
本文档下面的部分分为以下两个主要内容:
• | WCF 基础:涵盖 WCF 中的主要概念、术语和体系结构组件。 |
• | 代码示例:提供几个较短的代码示例,旨在阐释并具体化"WCF 基础"中涵盖的概念。 |
WCF 服务是一个公开终结点 集合的程序。每个终结点都是一个与外界通讯的入口。
客户端是一个与一个或多个终结点交换消息的程序。客户端还可以公开一个终结点,从而以双工消息交换的模式从服务接收消息。
以下部分更详细地描述了这些基础。
终结点
一个服务终结点具有一个地址、一个绑定 和一个约定。
终结点的地址是终结点驻留的网络地址。EndpointAddress 类表示一个 WCF 终结点地址。
终结点的绑定指定终结点与外界通讯的方式,包括传输协议(例如,TCP、HTTP)、编码(例如,文本、二进制文件)以及安全要求(例如,SSL、SOAP 消息安全)等。Binding 类表示 WCF 绑定。
终结点的约定指定终结点通讯的内容,它本质上是操作中有组织的消息集合,这些操作具有基本的消息交换模式 (MEPs),如单工、双工,以及请求/应答。ContractDescription 类表示 WCF 约定。
ServiceEndpoint 类表示终结点,它具有一个 EndpointAddress、一个绑定以及一个 ContractDescription,分别对应于终结点的地址、绑定和约定(参见图 1)。
图 1. 每个服务的终结点包含一个 EndpointAddress、一个绑定以及一个由 ContractDescription 表示的约定。
EndpointAddress
EndpointAddress 实际上是一个 URI、一个标识和一个可选标头的集合,如图 2 所示。
终结点的安全标识通常就是它的 URI;然而在高级方案中,可以使用 Identity 地址属性独立于 URI 来显式设置标识。
可选标头用于提供除终结点 URI 外的其他寻址信息。例如,地址标头对于区分共享相同地址 URI 的多个终结点很有用。
图 2. EndpointAddress 包含一个 URI,AddressProperties 包含一个标识和一个 AddressHeaders 集合。
绑定
绑定具有名称、命名空间,以及一个可编写的绑定元素的集合(图 3)。绑定的名称和命名空间在服务的元数据中唯一标识该绑定。每个绑定元素都描述终结点与外界通讯的方式 的一个方面。
图 3. 绑定类及其成员
例如,图 4 显示一个包含三个绑定元素的绑定元素集合。每个绑定元素的存在都描述与终结点通讯的方式 的一部分。TcpTransportBindingElement 表示终结点将使用 TCP 作为传输协议与外界通讯。ReliableSessionBindingElement 表示终结点使用可靠的消息处理来提供消息传递保证。SecurityBindingElement 表示终结点使用 SOAP 消息安全性。每个绑定元素通常具有这样的属性:它们进一步描述与终结点通讯的方式 的细节。例如,ReliableSessionBindingElement 有一个 Assurances 属性,它指定所需的邮件传递保证,如无,至少一次,至多一次,或正好一次。
图 4. 一个具有三个绑定元素的绑定示例
绑定中绑定元素的顺序和类型十分重要:绑定元素的集合用于构建根据其中绑定元素的顺序排序的通讯堆栈。最后一个添加到集合中的绑定元素对应于通讯堆栈的底部元素,而第一个绑定元素对应于顶部元素。传入消息自下向上流经堆栈,而传出消息自上向下流经堆栈。因此,集合中绑定元素的顺序直接影响通讯堆栈组件处理消息的顺序。请注意,WCF 提供一组预定义的绑定,在大多数方案中可以使用这些绑定而不用定义自定义绑定。
约定
WCF 约定是一个操作的集合,这些操作指定终结点与外界通讯的内容。每个操作都是一个简单的消息交换,例如单向或请求/应答消息交换。
ContractDescription 类用于描述 WCF 约定及其操作。在 ContractDescription 中,每个 Contract 操作都有一个对应的 OperationDescription,它们描述操作的各个方面,如操作是单向还是请求/应答。每个 OperationDescription 还使用一个 MessageDescriptions 集合描述组成操作的各个消息。
利用 WCF 编程模型,ContractDescription 通常从定义约定的接口或类进行创建。该类型使用 ServiceContractAttribute 进行注释,其对应于终结点操作的方法使用OperationContractAttribute 进行注释。您也可以手动构建一个 ContractDescription,无需从使用属性注释的 CLR 类型开始。
一个双工 约定定义两组逻辑操作:一组操作是,服务向客户端公开以进行调用;另一组操作是,客户端向服务公开以进行调用。用于定义双工约定的程序模型会将每组操作分为一个单独的类型(每种类型必须是一个类或一个接口),并使用 ServiceContractAttribute 注释表示服务操作的约定,引用定义客户端(或回调)操作的约定。此外,ContractDescription 还包含对每种类型的引用,从而可以将其组成一个双工约定。
与绑定类似,每个约定都有一个在服务元数据中唯一标识该约定的名称和命名空间。
每个约定还有一个 ContractBehaviors 集合,它是修改或扩展约定行为的模块。下一部分对这些行为进行更详细的说明。
图 5. ContractDescription 类描述 WCF 约定
行为
行为是修改或扩展服务或客户端功能的类型。例如,ServiceMetadataBehavior 实现的元数据行为控制服务是否发布元数据。同样,安全行为控制模拟和授权,而事务行为控制登记和自动完成事务。
行为还参与构建信道的过程,它可以根据用户指定的设置和/或服务或信道的其他方面修改该信道。
服务行为是实现 IServiceBehavior 并将其应用于服务的类型。同样,信道行为是实现 IChannelBehavior 并将其应用于客户端信道的类型。
服务和信道描述
ServiceDescription 类是一个内存内结构,它描述一个 WCF 服务,包括服务公开的终结点、应用于服务的行为,以及实现该服务的类型(一个类)(参见图 6)。ServiceDescription 用于创建元数据、代码/配置和信道。
您可以手动构建该 ServiceDescription 对象。您也可以从使用特定 WCF 属性注释的类型创建它,这是较为常见的情况。该类型的代码可以手动编写,也可以使用 WCF 工具(名为 svcutil.exe)从 WSDL 文档生成。
尽管 ServiceDescription 对象可以显式创建并填充,但它们通常作为运行服务的一部分在后台创建。
图 6. ServiceDescription 对象模型
在客户端同样如此,ChannelDescription 描述一个到特定终结点的 WCF 客户端信道(图 7)。ChannelDescription 类有一个 IchannelBehaviors 的集合,IchannelBehaviors 是应用于信道的行为。它还有一个描述终结点(信道与该终结点进行通讯)的 ServiceEndpoint。
请注意,与 ServiceDescription 不同,ChannelDescription 只包含一个代表目标终结点(信道与该终结点进行通讯)的 ServiceEndpoint。
图 7. ChannelDescription 对象模型
WCF 运行时
WCF 运行时是一组负责发送和接收消息的对象。例如,格式化消息、应用安全性、使用各种传输协议传输和接收消息,以及向适当的操作分发接收到的消息,所有这些操作都在 WCF 运行时中发生。以下部分阐释 WCF 运行时的主要概念。
消息
WCF 消息是客户端和终结点之间的数据交换单元。消息本质上是 SOAP 消息 InfoSet 的一个内存内表示形式。请注意,消息不与文本 XML 绑定在一起。另外,根据所使用的编码机制,消息可以使用 WCF 二进制格式、文本 XML 或任何其他自定义格式序列化。
信道
信道是将消息发送至终结点和从终结点接收消息的核心抽象。从广义上讲,有两类信道:传输信道使用某种形式的传输协议(如 TCP、UDP 或 MSMQ)处理发送或接收不透明的八位字节流。另一方面,协议信道通过处理并有可能修改消息实现基于 SOAP 的协议。例如,安全信道添加并处理 SOAP 消息头,并且可能通过对其进行加密修改消息体。信道是可编辑的,因此一层信道可以位于另一层信道之上,而另一层信道又可以位于第三层信道之上。
EndpointListener
EndpointListener 是与 ServiceEndpoint 等效的运行时。ServiceEndpoint 的 EndpointAddress、约定和绑定(代表位置、内容 和方式)分别对应于 EndpointListener 的侦听地址、消息筛选和分发,以及信道堆栈。EndpointListener 包含负责发送和接收消息的信道堆栈。
ServiceHost 和 ChannelFactory
WCF 服务运行时通常通过调用 ServiceHost.Open 在后台创建。ServiceHost(如图 6 所示)驱动从服务类型创建 ServiceDescription,并使用配置和/或代码中定义的终结点填充 ServiceDescription 的 ServiceEndpoint 集合。然后,ServiceHost 使用 ServiceDescription 为 ServiceDescription 中的每个 ServiceEndpoint 以 EndpointListener 对象的形式创建一个信道堆栈。
图 8. ServiceHost 对象模型
同样,在客户端,客户端运行时由 ChannelFactory 创建,后者等效于客户端的 ServiceHost。
ChannelFactory 驱动根据约定的类型、绑定以及 EndpointAddress 创建一个 ChannelDescription。然后,它使用该 ChannelDescription 客户端的信道堆栈。
与服务运行时不同,客户端运行时不包括含 EndpointListener,因为客户端始终启动到服务的连接,这样就无需"侦听"传入的连接。
本部分提供的代码示例说明如何构建服务和客户端。这些示例旨在使上述概念更加具体化,而不是教您 WCF 编程。
定义并实现约定
如前所述,定义约定最简单的方法是创建一个接口或类,并使用 ServiceContractAttribute 对其加以注释,使系统能够通过它轻松地创建一个 ContractDescription。
使用接口或类定义约定时,作为约定成员的每个接口或类方法都必须使用 OperationContractAttribute 进行注释。例如:
using System.ServiceModel; //a WCF contract defined using an interface [ServiceContract] public interface IMath { [OperationContract] int Add(int x, int y); }
在本例中,实现约定仅仅是创建一个实现 IMath 的类。该类成为 WCF 服务类。例如:
//the service class implements the interface public class MathService : IMath { public int Add(int x, int y) { return x + y; } }
定义终结点并启动服务
终结点可在代码或配置中进行定义。在下面的示例中,DefineEndpointImperatively 方法显示在代码中定义终结点并启动服务的最简单方法。
DefineEndpointInConfig 方法显示在配置中定义的等效终结点(配置示例在以下代码之后)。
public class WCFServiceApp { public void DefineEndpointImperatively() { //create a service host for MathService ServiceHost sh = new ServiceHost(typeof(MathService)); //use the AddEndpoint helper method to //create the ServiceEndpoint and add it //to the ServiceDescription sh.AddServiceEndpoint( typeof(IMath), //contract type new WSHttpBinding(), //one of the built-in bindings "http://localhost/MathService/Ep1"); //the endpoint's address //create and open the service runtime sh.Open(); } public void DefineEndpointInConfig() { //create a service host for MathService ServiceHost sh = new ServiceHost (typeof(MathService)); //create and open the service runtime sh.Open(); } } <!-- configuration file used by above code --> <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> <services> <!-- service element references the service type --> <service type="MathService"> <!-- endpoint element defines the ABC's of the endpoint --> <endpoint address="http://localhost/MathService/Ep1" binding="wsHttpBinding" contract="IMath"/> </service> </services> </system.serviceModel> </configuration>
向终结点发送消息
以下代码显示向 IMath 终结点发送消息的两种方法。SendMessageToEndpoint 隐藏信道的创建,这在后台发生,而 SendMessageToEndpointUsingChannel 示例显式进行信道的创建。
SendMessageToEndpoint 中的第一个示例使用一个名为 svcutil.exe 的工具以及服务的元数据来生成一个约定(本示例中的 IMath)、一个实现该约定的代理类(本示例中的 MathProxy),以及相关联的配置(此处未显示)。再次强调,IMath 定义的约定指定了内容(即可以执行的操作),而生成的配置包含一个绑定(方式)和一个地址(位置)。
使用该代理类只需对其进行实例化并调用 Add 方法。在后台,该代理类将创建一个信道,并使用该信道与终结点通讯。
下面的 SendMessageToEndpointsUsingChannel 中的第二个示例显示如何使用 ChannelFactory 与终结点直接通讯。在该示例中,使用 ChannelFactory.CreateChannel 直接创建信道,而不是用代理类或配置。而且,ChannelFactory 构造函数采用这两部分信息作为参数,而不是使用配置来定义终结点的地址和绑定。定义一个终结点所需的第三部分信息(即约定)作为类型 T 传入。
using System.ServiceModel; //this contract is generated by svcutil.exe //from the service's metadata public interface IMath { [OperationContract] public int Add(int x, int y) { return x + y; } } //this class is generated by svcutil.exe //from the service's metadata //generated config is not shown here public class MathProxy : IMath { ... } public class WCFClientApp { public void SendMessageToEndpoint() { //this uses a proxy class that was //created by svcutil.exe from the service's metadata MathProxy proxy = new MathProxy(); int result = proxy.Add(35, 7); } public void SendMessageToEndpointUsingChannel() { //this uses ChannelFactory to create the channel //you must specify the address, the binding and //the contract type (IMath) ChannelFactory factory=new ChannelFactory( new WSHttpBinding(), new EndpointAddress("http://localhost/MathService/Ep1")); IMath channel=factory.CreateChannel(); int result=channel.Add(35,7); factory.Close(); } }
定义自定义行为
定义自定义行为只需实现 IServiceBehavior(或客户端行为的 IChannelBehavior)。以下代码显示一个实现 IServiceBehavior 的示例行为。在 IServiceBehavior.ApplyBehavior 中,代码检查 ServiceDescription 并写出每个 ServiceEndpoint 的地址、绑定和约定,以及 ServiceDescription 中每个行为的名称。
这个特殊的行为也是一个属性(从 System.Attribute 继承),使其可以以声明方式应用,如下所示。然而,行为不必成为属性。
[AttributeUsageAttribute( AttributeTargets.Class, AllowMultiple=false, Inherited=false)] public class InspectorBehavior : System.Attribute, System.ServiceModel.IServiceBehavior { public void ApplyBehavior( ServiceDescription description, Collection behaviors) { Console.WriteLine("-------- Endpoints ---------"); foreach (ServiceEndpoint endpoint in description.Endpoints) { Console.WriteLine("--> Endpoint"); Console.WriteLine("Endpoint Address: {0}", endpoint.Address); Console.WriteLine("Endpoint Binding: {0}", endpoint.Binding.GetType().Name); Console.WriteLine("Endpoint Contract: {0}", endpoint.Contract.ContractType.Name); Console.WriteLine(); } Console.WriteLine("-------- Service Behaviors --------"); foreach (IServiceBehavior behavior in description.Behaviors) { Console.WriteLine("--> Behavior"); Console.WriteLine("Behavior: {0}", behavior.GetType().Name); Console.WriteLine(); } } }
应用自定义行为
通过向 ServiceDescription(或客户端上的 ChannelDescription)添加行为的实例,所有行为都可以以命令方式应用。例如,要以命令方式应用 InspectorBehavior,您可以编写以下代码:
ServiceHost sh = new ServiceHost(typeof(MathService)); sh.AddServiceEndpoint( typeof(IMath), new WSHttpBinding(), "http://localhost/MathService/Ep1"); //Add the behavior imperatively InspectorBehavior behavior = new InspectorBehavior(); sh.Description.Behaviors.Add(behavior); sh.Open();
此外,从 System.Attribute 继承的行为还能够以声明方式应用于服务。例如,因为 InspectorBehavior 继承自 System.Attribute,所以它可以以声明方式得以应用,如下所示:
[InspectorBehavior] public class MathService : IMath { public int Add(int x, int y) { return x + y; } }
WCF 服务公开了一个终结点集合,其中每个终结点都是与外界通讯的入口。每个终结点都有一个地址、一个绑定和一个约定 (ABC)。地址是终结点驻留的位置,绑定是终结点通讯的方式,约定是终结点通讯的内容。
在服务端,ServiceDescription 保存 ServiceEndpoint 的集合,其中每个 ServiceEndpoint 都描述服务公开的一个终结点。根据该描述,ServiceHost 创建一个运行时,它针对 ServiceDescription 中的每个 ServiceEndpoint 包含了一个 EndpointListener。终结点的地址、绑定和约定(代表位置、内容 和方式)分别对应于 EndpointListener 的侦听地址、消息筛选和分发,以及信道堆栈。
同样,在客户端,ChannelDescription 保存与客户端通讯的一个 ServiceEndpoint。根据该 ChannelDescription,ChannelFactory 创建可以与服务的终结点通讯的信道堆栈。