Windows Communication Foundation (WCF) 是 Microsoft 为构建面向服务的应用程序而提供的统一编程模型。借助这一模型,开发人员可以构建既能跨平台与现有投资集成又能与现有投资交互的安全、可靠的事务处理解决方案。下面将由浅入深的学习WCF。
1 术语
可以先跳过本节,回过头来阅读温习各术语的含义。
术语 |
定义 |
address(地址) |
地址用于指定接收消息的位置。它以统一资源标识符 (URI) 的形式指定。URI 架构部分指定用于到达地址的传输机制,如 HTTP 和 TCP。URI 的层次结构部分包含一个唯一的位置,其格式取决于传输机制。 |
application endpoint(应用程序终结点) |
一个终结点,由应用程序公开并对应于该应用程序实现的服务协定。 |
behavior(行为) |
行为是控制服务、终结点、特定操作或客户端的各种运行时特性的要素。行为按照范围进行分组:常见行为在全局范围内影响所有终结点,服务行为仅影响与服务相关的方面,终结点行为仅影响与终结点相关的属性,操作级行为影响特定操作。 |
binding(绑定) |
定义用于与 WCF 服务进行通信的通信协议。它由一组称为绑定元素的要素构造而成,这些元素一个个地叠起来形成通信基础结构。请参见“终结点”。 |
channel(通道) |
通道是绑定元素的具体实现。绑定表示配置,而通道是与该配置相关联的实现。因此,每个绑定元素都有一个相关联的通道。通道一个个地叠起来形成绑定的具体实现:通道堆栈。 |
claims-based security(基于声明的安全性) |
允许基于声明对受保护资源进行授权访问。 |
client application(客户端应用程序) |
客户端应用程序是与一个或多个终结点交换消息的程序。客户端应用程序可通过创建 WCF 客户端的实例和调用该 WCF 客户端的方法来启动。需要注意的是,单个应用程序既可以充当客户端,也可以充当服务。 |
configuration(配置) |
配置的优点在于,它使非开发人员(如网络管理员)可以在代码编写完成后直接对客户端和服务参数进行设置,而不必重新进行编译。使用配置不仅可以设置值(如终结点地址),还可以通过添加终结点、绑定和行为来实施进一步的控制。可以通过配置、代码编写或将两者结合在一起对应用程序进行控制。 |
contract(协定) |
协定是对其所属的特定协定类型的支持规范。例如,服务协定是一组操作的规范。在 WCF 中,协定具有一个层次结构,此结构在 System.ServiceModel.Description 命名空间的说明对象中进行了镜像。服务协定是 WCF 中最大的协定范围。服务协定中的每个服务操作都具有一个操作协定,用于指定该操作可以交换的消息(包括错误消息)以及交换的方向。操作中的每条消息都具有一个消息协定(即 SOAP 消息信封的结构规范),而每个消息协定又都具有一个数据协定,用于指定消息中包含的数据结构。 |
data contract(数据协定) |
服务使用的数据类型必须在元数据中进行描述,以使其他各方可以与该服务进行交互操作。数据类型的说明称为数据协定,而这些类型可以在消息的任何部分使用(例如,作为参数或返回类型)。如果服务仅使用简单类型,则无需显式使用数据协定。 |
declarative application(声明性应用程序) |
具有充分的描述以便无需运行命令性指令即可在运行时创建的应用程序。 |
endpoint(终结点) |
包含一个地址、一个绑定和一个用于与 WCF 服务进行通信的协定。 |
endpoint address(终结点地址) |
您可以为服务中的每个终结点均创建一个具有唯一性的终结点地址,还可以在一定的条件下跨多个终结点共享一个地址。 |
fault contract(错误协定) |
可以将错误协定与服务操作进行关联,以指示可能返回到调用方的错误。一个操作可以具有零个或更多个与其相关联的错误。这些错误是在编程模型中作为异常建模的 SOAP 错误。这些异常将转换为 SOAP 错误,然后可以发送到客户端。 |
hosting(宿主) |
服务必须承载于某个进程中。宿主是控制服务的生存期的应用程序。服务可以是自承载的,也可以由现有的宿主进程进行管理。 |
hosting process(宿主进程) |
宿主进程是专为承载服务而设计的应用程序。这些宿主进程包括 Internet 信息服务 (IIS)、Windows 激活服务 (WAS) 和 Windows 服务。在这些宿主方案中,由宿主控制服务的生存期。例如,使用 IIS 可以设置包含服务程序集和配置文件的虚拟目录。在收到消息时,IIS 将启动服务并控制服务的生存期。 |
initiating operation(启动操作) |
作为新会话的第一个操作而调用的操作。只有在已调用至少一个启动操作之后才可以调用非启动操作。 |
instancing model(实例化模型) |
每个服务都具有一个实例化模型。有三种实例化模型:“单个”,在这种模型中,由单个 CLR 对象为所有客户端提供服务;“每个调用”,在这种模型中,将创建一个新的 CLR 对象来处理每个客户端调用;“每个会话”,在这种模型中,将创建一组 CLR 对象,并且为每个独立的会话使用一个对象。实例化模型的选择取决于应用程序要求和服务的预期使用模式。 |
message(消息) |
消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头。 |
message contract(消息协定) |
消息协定描述消息的格式。例如,它会声明消息元素应包含在消息头中还是包含在消息正文中,应该对消息的何种元素应用何种级别的安全性,等等。 |
message security mode(消息安全模式) |
消息安全模式指定通过实现一种或多种安全规范来保证安全。每个消息都包含必要的安全机制,用于在消息传输过程中保证安全,并使接收方能够检测到篡改和对消息进行解密。从这种意义上说,安全信息包装在每个消息中,从而提供了跨多个跃点的端到端安全。由于安全信息成为消息的一部分,因此还可以在消息中包含多种凭据(这些凭据称为“声明”)。这种方法还具有这样一个优点,即消息可以通过任意传输协议(包括在其起点和目标之间的多个传输协议)安全地传送。这种方法的缺点在于所使用的加密机制较为复杂,使性能受到影响。 |
metadata(元数据) |
服务的元数据描述服务的各种特征,外部实体需要了解这些特征以便与该服务进行通信。ServiceModel 元数据实用工具 (Svcutil.exe) 可以使用元数据生成 WCF 客户端以及客户端应用程序用来与服务进行交互的伴随配置。 服务所公开的元数据包括 XML 架构文档(用于定义服务的数据协定)和 WSDL 文档(用于描述服务的方法)。 启用元数据后,WCF 可通过检查服务及其终结点来自动生成服务的元数据。若要发布服务的元数据,必须显式启用元数据行为。 |
operation contract(操作协定) |
操作协定定义参数并返回操作的类型。在创建定义服务协定的接口时,可以通过将 T:System.ServiceModel.OperationContractAttribute 属性应用于协定中包含的每个方法定义来表示一个操作协定。可以将操作建模为采用单个消息作为参数并返回单个消息,或者建模为采用一组类型作为参数并返回一个类型。在后一种情况下,将由系统来确定需要为该操作交换的消息的格式。 |
projection(投影) |
网络上的数据的表示形式。例如,SOAP 投影会作为 SOAP 信封发送消息;Web 投影会以 JSON 格式发送消息。 |
security(安全性) |
WCF 中的安全性包括保密性(为防止窃听而进行的消息加密)、完整性(用于检测消息篡改行为的方法)、身份验证(用于验证服务器和客户端的方法)以及授权(资源访问控制)。通过利用现有安全机制(如 TLS over HTTP,也称为 HTTPS)或通过实现各种 WS-* 安全规范中的一个或多个规范,可以提供这些功能。 |
self-hosted service(自承载服务) |
自承载服务是在开发人员创建的进程应用程序中运行的服务。开发人员控制服务的生存期、设置服务的属性、打开服务(这会将服务设置为侦听模式)以及关闭服务。 |
service(服务) |
公开一个或多个终结点的程序或进程,其中每个终结点都会公开一个或多个操作。 |
service contract(服务协定) |
服务协定将多个相关的操作联系在一起,组成单个功能单元。协定可以定义服务级设置,如服务的命名空间、对应的回调协定以及其他此类设置。在大多数情况下,协定的定义方法是用所选的编程语言创建一个接口,然后将 T:System.ServiceModel.ServiceContractAttribute 属性应用于该接口。通过实现该接口,可生成实际的服务代码。 |
service operation(服务操作) |
服务操作是在服务的代码中定义的过程,用于实现某种操作的功能。此操作作为 WCF 客户端上的方法向客户端公开。该方法可能返回一个值,并可能采用数量可选的参数,或是不采用任何参数且不返回任何响应。例如,一个实现简单的“Hello”的操作可以用作客户端存在通知,并可以开始一系列操作。 |
system-provided bindings(系统提供的绑定) |
WCF 包含许多系统提供的绑定。这些绑定是针对特定方案进行优化的绑定元素的集合。例如,T:System.ServiceModel.WSHttpBinding 是为了与实现各种 WS* 规范的服务进行互操作而专门设计的。通过仅提供那些可以正确应用于特定方案的选项,这些绑定可以节省时间。如果其中的某个绑定不能满足您的要求,也可以创建您自己的自定义绑定。 |
terminating operation(终止操作) |
作为现有会话的最后一个消息而调用的操作。默认情况下,在关闭与服务相关联的会话之后,WCF 会回收服务对象及其上下文。 |
transport security mode(传输安全模式) |
可以通过以下三种模式之一来保证安全:传输模式、消息安全模式和使用消息凭据的传输模式。传输安全模式指定由传输层机制(如 HTTPS)提供保密性、完整性和身份验证。在使用像 HTTPS 这样的传输协议时,此模式的优点在于性能出色,而且由于它在 Internet 上非常流行,因此很容易理解。其缺点在于,这种安全分别应用于通信路径中的每个跃点,这使得通信容易遭受“中间人”攻击。 |
transport with message credential security mode(使用消息凭据的传输安全模式) |
此模式使用传输层来提供消息的保密性、身份验证和完整性,并且每个消息都可以包含消息接收方所要求的多个凭据(声明)。 |
type converter(类型转换器) |
CLR 类型可以与一个或多个 System.ComponentModel.TypeConverter 派生类型关联,这些派生类型使 CLR 类型的实例与其他类型的实例之间能够互相转换。类型转换器使用 System.ComponentModel.TypeConverterAttribute 特性与 CLR 类型相关联。可以在 CLR 类型或属性上直接指定 TypeConverterAttribute。在属性上指定的类型转换器始终优先于在属性的 CLR 类型上指定的类型转换器。 |
WCF client(WCF 客户端) |
WCF 客户端是一个客户端应用程序构造,可将服务操作作为方法公开(使用所选的 .NET Framework 编程语言,如 Visual Basic 或 Visual C#)。任何应用程序都可以承载 WCF 客户端,包括承载服务的应用程序。因此,可以创建一个包含其他服务的 WCF 客户端的服务。 使用 ServiceModel 元数据实用工具 (Svcutil.exe) 并将其指向正在运行的元数据发布服务,可以自动生成 WCF 客户端。 |
workflow services(工作流服务) |
工作流服务是作为工作流实现的 WCF 服务。工作流包含发送和/或接收 WCF 消息的消息传递活动。 |
WS-* |
一组不断增加的、在 WCF 中实现的 Web 服务 (WS) 规范(如 WS-Security、WS-ReliableMessaging 等)的简写。 |
XAML |
可扩展应用程序标记语言 |
XAML schema(XAML 架构) |
在 XAML 中用于定义自定义类型的标记架构。 |
2 元数据
元数据,metadata,用于描述要素、数据集或数据集系列的内容、覆盖范围、质量、管理方式、数据的所有者、数据的提供方式等有关的信息,是一种二进制信息,元数据最本质、最抽象的定义为:data about data (关于数据的数据)。
元数据编码语言(Metadata Encoding Languages)指对元数据元素和结构进行定义和描述的具体语法和语义规则,常称为定义描述语言(DDL)。一些标准化的DDL,例如SGML和XML。
3 WCF概述
借助 WCF,可以将数据作为异步消息从一个服务终结点发送至另一个服务终结点。消息可以简单到作为 XML 发送的单个字符或单个单词,复杂消息可以是二进制数据流。
WCF 是一个灵活的平台。由于这一极强的灵活性,WCF 还在 Microsoft 的一些其他产品中得以利用。
与 WCF 配对的第一项技术是 Windows Workflow Foundation (WF)。
Microsoft BizTalk Server R2 还利用 WCF 作为通信技术。
Microsoft Silverlight 是一个用于创建可互操作的、丰富 Web 应用程序的平台,允许开发人员创建媒体密集的网站(如流视频)。从版本 2 开始,Silverlight 加入了 WCF 作为通信技术,以将 Silverlight 应用程序连接到 WCF 终结点。
WCF 是一个运行时和一组 API,用于创建在服务与客户端之间发送消息的系统。WCF 建立在基于消息的通信这一概念基础之上,消息在终结点之间发送。终结点是发送或接收消息(或执行这两种操作)的场所,它们定义消息交换所需要的所有信息。“终结点”以基于标准的方式描述消息应发送到的位置、消息应如何发送以及消息应具有的形式。服务可以将这些信息作为元数据加以公开,而客户端可以处理这些元数据以生成适当的 WCF 客户端和通信堆栈。
通信堆栈的一个必要元素是传输协议。可以使用常用传输协议(如 HTTP 和 TCP)通过 Intranet 和 Internet 发送消息。也可以使用其他支持与消息队列应用程序和对等网络网格上的节点进行通信的传输协议。使用 WCF 的内置扩展点可以添加更多传输机制。通信堆栈中的另一个必要元素是指定如何将任意给定消息格式化的编码。WCF 提供了下列编码:
1、文本编码,一种可互操作的编码。
2、消息传输优化机制 (MTOM) 编码,该编码是一种可互操作的方法,用于高效地将非结构化二进制数据发送到服务或从服务接收这些数据。
3、用于实现高效传输的二进制编码。
WCF 支持多种消息模式,包括请求-回复、单向和双工通信。不同传输协议支持不同的消息模式,因而会影响它们所支持的交互类型。
4 WCF 体系结构
4.1 协定
协定定义消息系统的各个方面。
数据协定描述组成某一服务可创建或使用的每则消息的每个参数。消息参数由 XML 架构定义语言 (XSD) 文档定义,这使得任何理解 XML 的系统均可处理该文档。
消息协定使用 SOAP 协议定义特定消息部分,当互操作性要求对消息的某些部分进行更精细的控制时,消息协定可实现这种控制。
服务协定指定服务的实际方法签名,并以支持的编程语言之一(例如 Visual Basic 或 Visual C#)作为接口进行分发。
策略和绑定规定与某一服务进行通信所需的条件。例如,绑定必须(至少)指定所使用的传输(例如 HTTP 或 TCP)和编码。策略包括安全要求和其他条件,必须满足这些要求和条件才能与服务进行通信。
4.2 服务运行时
服务运行时层包含仅在服务实际运行期间发生的行为,即该服务的运行时行为。
遏制控制处理的消息数,如果对服务的需求增长到预设限制,该消息数则会发生变化。
错误行为指定服务出现内部错误时应采取的操作,例如控制传递给客户端的信息(信息过多会向恶意用户提供攻击的机会)。
元数据行为控制是否以及如何向外部提供元数据。
实例行为指定可运行的服务实例的数目(例如,singleton 指定只能用单一实例来处理所有消息)。通过事务行为,可以在失败时回滚已进行事务处理的操作。调度行为用于控制 WCF 基础结构处理消息的方式。
通过扩展性功能可以自定义运行时进程。例如,消息检查功能用于检查消息的各个部分,使用参数筛选功能可以根据作用于消息头的筛选器来执行预设操作。
4.3 消息传递
消息传递层由通道组成。通道是以某种方式对消息进行处理(例如通过对消息进行身份验证)的组件。一组通道也称为“通道堆栈”。
通道对消息和消息头进行操作。这与服务运行时层不同,服务运行时层主要涉及对消息正文内容的处理。
有两种类型的通道:传输通道和协议通道。
传输通道读取和写入来自网络(或外部的某些其他通信点)的消息。某些传输通道使用编码器来将消息(表示为 XML Infoset)转换为网络所使用的字节流的表示形式,或将字节流表示形式转换为消息。传输通道的示例包括 HTTP、命名管道、TCP 和 MSMQ。编码的示例包括 XML 和优化的二进制文件。
协议通道经常通过读取或写入消息的其他头的方式来实现消息处理协议。此类协议的示例包括 WS-Security 和 WS-Reliability。消息传递层说明数据的可能格式和交换模式。WS-Security 是对在消息层启用安全性的 WS-Security 规范的实现。通过 WS-Reliable Messaging 通道可以保证消息的传递。编码器提供了大量的编码,可使用这些编码来满足消息的需要。HTTP 通道指定应使用超文本传输协议来传递消息。同理,TCP 通道指定 TCP 协议。事务流通道控制已经过事务处理的消息模式。通过命名管道通道可以进行进程间通信。使用 MSMQ 通道可以与 MSMQ 应用程序进行互操作。
4.4 承载和激活
服务的最终形式为程序。与其他程序类似,服务必须在可执行文件中运行。这称为“自承载”服务。
某些服务(如 IIS 或 Windows 激活服务 (WAS))“被承载”,即在外部代理管理的可执行文件中运行。通过 WAS,可以在运行 WAS 的计算机上部署 WCF 应用程序时自动激活该应用程序。还可通过可执行文件(.exe 文件)的形式来手动运行服务。服务也可作为 Windows 服务自动运行。COM+ 组件也可作为 WCF 服务承载。
5 WCF入门
先初步了解创建 WCF 服务和客户端应用程序所需的步骤,如何使用协定定义服务,如何实现服务,以及如何在代码中配置服务、承载服务和运行服务;如何创建客户端代理,如何配置客户端应用程序,以及如何创建和使用可以访问服务功能的客户端。服务会发布可以访问的元数据,这些数据定义了客户端应用程序与服务操作进行通信所需的构造。
5.1 创建 WCF 服务
服务是一种构造,它公开一个或多个终结点,其中每个终结点都公开一项或多项服务操作。服务的终结点指定下列信息:服务所在的位置;一个绑定,其中包含客户端必须与服务进行通信的信息;一个协定,用于定义服务向其客户端提供的功能。
5.1.1 定义 WCF 服务协定
创建基本 WCF 服务时,第一项任务是定义协定。协定指定服务支持的操作。可以将操作视为一个 Web 服务方法。接口中的每个方法都对应于特定的服务操作。每个接口都必须将 ServiceContractAttribute 应用于自身,而每个操作都必须将 OperationContractAttribute 应用于自身。如果接口中的一个方法具有 ServiceContractAttribute 而没有 OperationContractAttribute,则不公开该方法。
主要步骤:
1、 添加System.ServiceModel.dll 的引用。
2、 导入System.ServiceModel 命名空间,using System.ServiceModel。
3、 定义新接口(终结点的协议),如:
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
}
4、 为接口中协定公开的每个操作声明一个方法,并对希望作为公共 WCF 协定的一部分公开的每个方法应用 OperationContractAttribute 特性。如:
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
5.1.2 实现 WCF 服务协定
创建使用接口定义的协定,下一步骤是实现接口。
1、 定义一个实现接口的类(终结点的地址),也称之为“在接口中声明方法签名”。
public class CalculatorService : ICalculator
2、 在类中实现在接口中定义的每个方法,也称之为“应用于每个方法签名”。
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
// Code added to write output to the console window.
Console.WriteLine("Return: {0}", result);
return result;
}
5.1.3 承载和运行基本的 WCF 服务
此过程包含以下步骤:
1、 为服务创建基址。为服务的基址创建 Uri 实例。此 URI 指定 HTTP 方案、本地计算机、端口号 8000,以及服务协定中为服务命名空间指定的服务路径,如:
Uri baseAddress = new Uri("http://localhost:8000/ServiceModelSamples/Service");
2、 承载服务。
(1)、导入 System.ServiceModel.Description 命名空间。
using System.ServiceModel.Description;
(2)、创建一个新的 ServiceHost 实例以承载服务。必须指定实现服务协定和基址的类型。
ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress);
(3)、添加一个捕获 CommunicationException 的 try-catch 语句,并在接下来的三个步骤中将该代码添加到 try 块中。catch 子句应该显示错误信息,然后调用 selfHost.Abort()。
try
{
// ...
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
(4)、添加公开服务的终结点。必须指定终结点公开的协议、绑定和终结点的地址。对于此示例,将 ICalculator 指定为协定,将 WSHttpBinding 指定为绑定,并将 CalculatorService 指定为地址。在这里请注意,终结点地址是相对地址。
selfHost.AddServiceEndpoint(typeof(ICalculator),
new WSHttpBinding(), "CalculatorService");// CalculatorService终结点的地址
终结点的完整地址是基址和终结点地址的组合。
在此例中,完整地址是 http://localhost:8000/ServiceModelSamples/Service/CalculatorService。
(5)、启用元数据交换。为此,添加服务元数据行为。首先创建一个 ServiceMetadataBehavior 实例,将 HttpGetEnabled 属性设置为 true,然后为服务添加新行为。如:
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();//实例化服务元数据行为
smb.HttpGetEnabled = true;//设置HttpGetEnabled 属性
selfHost.Description.Behaviors.Add(smb);// 为服务添加新行为
(5)、打开 ServiceHost 并等待传入消息。
5.2 创建 WCF 客户端及代理
1、为项目添加对 System.ServiceModel.dll 的引用。
2、导入System.ServiceModel 命名空间。
3、生成服务的代码文件和配置文件。例如:
(1)、在 Visual Studio 中,按 F5 启动在前面的主题中创建的服务。
(2)、在“开始”菜单上,单击“所有程序”,然后单击“Visual Studio 2010”。单击“Visual Studio 工具”,然后单击“Visual Studio 2010 命令提示”。
(3)、导航到要放置客户端代码的目录。
(4)、运用命令行工具ServiceModel元数据实用工具 (Svcutil.exe)生成服务的代码文件和配置文件。/out 开关会将客户端代理文件的名称更改为 GeneratedProxy.cs。/config 开关会将客户端配置文件的名称从默认的 Output.config 更改为 App.config。
(5)、将客户端代理代码文件包括在项目中。
5.3 创建 WCF 客户端实例
(1)创建 客户端代理实例。
CalculatorClient client = new CalculatorClient();
(2)从生成的代理调用服务操作。
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
(3)关闭客户端代理实例。
client.Close();
测试:按F5启动service项目,在解决方案中右击client项目,选择“调试”\“启动新实例”。
5.4 代码示例
6 基本 WCF 编程
前面学习了WCF的入门,现在学习一些创建WCF应用程序的基础知识。
设计和实现服务,就是如何定义和实现 WCF 协定。服务协定指定终结点与外界通信的内容,就是定义一个或多个的接口。更具体地说,它是有关一组特定消息的声明,这些消息被组织成基本消息交换模式 (MEP),如请求/答复、单向和双工。如果说服务协定是一组在逻辑上相关的消息交换,那么服务操作就是单个消息交换。
6.1 设计服务协定
服务即一组操作。若要创建服务协定,必须对操作建模并指定其分组。在 WCF应用程序中,通过创建一个方法并使用 OperationContractAttribute 属性对其进行标记来定义操作。然后,若要创建服务协定,需要将操作组合到一起,具体方法是在使用 ServiceContractAttribute 属性标记的接口中声明这些操作,或在使用同一属性进行标记的类中定义它们。
简单的说,定义协定,就是定义接口,再定义类去实现接口中的操作。
不过,也可以使用类来定义服务协定,并同时实现该协定。可以通过直接向类和类上的方法分别应用 ServiceContractAttribute 和 OperationContractAttribute 来创建服务,这种方法的优点是快速且简便。缺点是托管类不支持多个继承,因此,一次只能实现一个服务协定。
每个操作都有一个返回值和一个参数,即使它们为 void。参数或返回值中使用的每个类型都必须是可序列化的,换言之,该类型的对象必须能够转换为字节流,并能够从字节流转换为对象。
面向服务的应用程序(例如(WCF) 应用程序)设计为与 Microsoft 平台和非 Microsoft 平台上的最大可能数量的客户端应用程序进行互操作。为了获得最大可能的互操作性,建议您使用 DataContractAttribute 和 DataMemberAttribute 属性对您的类型进行标记,以创建数据协定。数据协定是服务协定的一部分,用于描述您的服务操作交换的数据。
WCF 编程模型中指定三种消息交换模式:请求/答复、单向和双工消息模式。这些交换模式是针对操作(OperationContract)来定义的。
6.1.1 请求/答复协定
OperationContractAttribute 类和 IsOneWay 属性为False(默认值为False)时,表示为请求/答复模式。
在WCF入门中做的练习,是按照“新建ServiceHost实承载服务—>定义终节点—>添加服务元数据行为”步骤进行的,做这部分的练习时,遇到一个问题。在创建好一个简单的service后,用svculit工具生成客户端用的配置文件和程序代理文件时,不成功。原来,在本次练习中,我漏掉了
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
sh.Description.Behaviors.Add(smb);
这部分代码,补上就能顺利使用svculit工具了。生成了客户端配置文件及代理程序文件后,上述代码其实可以注释掉了,服务照常能访问。
//Uri uri = new Uri("http://localhost:8010/ServiceModelSample/service");
ServiceHost sh = new ServiceHost(typeof(CalculatorService));
//sh.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "CalculatorService");
//***下面这段代码创建元数据行为实例,用于svcutil工具生成客户端代理及配置文件用
//ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
//smb.HttpGetEnabled = true;
//sh.Description.Behaviors.Add(smb);
//***
sh.Open();
上述贴出了本次练习中提起服务的代码,其中将终节点部分注释掉了,包括元添加数据行为,也能使用svculit工具及服务访问,因为在服务器端使用了app.config,下一节将对服务器端的配置文件结构进行解析。
6.1.2 WCF服务app.config
<system.serviceModel>
<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">//必须定义一个behavior
<serviceMetadata httpGetEnabled="True"/>//元数据属性
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="WCF_Contract_Sample_Service.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
//指定服务名称,与实现WCF服务协定所定义的类名一致,通常要带上命名空间名。
//与serviceBehaviors中定义的behavior同名
<host >
<baseAddresses >
<add baseAddress ="http://localhost:8010/ServiceModelSample/service"/>//必须指定服务uri
</baseAddresses>
</host>
<endpoint
address="" binding="wsHttpBinding" contract="WCF_Contract_Sample_Service.ICalculator">
//必须定义一个endpoint,且contract为定义的服务协定,通常要带上命名空间名。
</endpoint>
</service>
</services>
</system.serviceModel>
有上述的配置,就可以用简短的两行代码提起服务:
ServiceHost sh = new ServiceHost(typeof(CalculatorService));
sh.Open();
6.1.3 通过app.config来承载运行服务的示例
6.1.4 单向协定
在6.1.1中定义的是双向协定, IsOneWay缺省值就是false。
[OperationContract(IsOneWay=false )]
double Add(double x, double y);
如果将上述代码IsOneWay=true,则会报“使用 IsOneWay=true 标记的操作不得声明输出参数、引用参数或返回值。”的错误。
可以这样来理解单向协定,就是往服务器送数据,没有返回值。通常要为返回 void 的操作指定单向消息交换,将 IsOneWay 属性设置为 true。
6.1.5 通过svc和web.config来承载运行服务
我通过示例来学习本节内容。此示例来自微软发布的WF_WCF_Samples\WCF\Basic\Contract\Service\Session\CS\ Session.sln来学习,WF_WCF_Samples示例可以在微软的网站上下载。可通过链接WCF_Session_Sample.zip下载本次练习。
在此之前我学习了通过代码在控制台上承载服务来完成练习,也学习了通过服务端使用app.config配置文件在控制台上承载服务来完成练习。但控制台程序多数都运用于练习,有用控制台程序来开发产品级服务的,本示例起开始用类库来承载服务。之前学习的WF_WCF_Samples中的示例,服务的address都是指向IIS中配置的虚拟目录ServiceModelSamples,此虚拟目录可以手工创建,所在站点需要使用Asp.net 4.0应用程序池。本次示例起,因为使用了类库来承载服务,需要将虚拟目录ServiceModelSamples“转换为应用程序”,否则服务无法访问,提示“由于编译过程中出现异常,无法激活服务”。
在一下service类库的属性,在“生成事件”标签,“后期生成事件命令行”处,有几行代码:
mkdir %SystemDrive%\inetpub\wwwroot\servicemodelsamples
mkdir %SystemDrive%\inetpub\wwwroot\servicemodelsamples\bin,表示如果没有此目录将创建。
copy "$(TargetDir)service.dll" %SystemDrive%\inetpub\wwwroot\servicemodelsamples\bin
表示会将生成的dll拷贝到站点。
copy "$(ProjectDir)service.svc" %SystemDrive%\inetpub\wwwroot\servicemodelsamples
表示会将svc文件拷贝到站点目录下。
copy "$(ProjectDir)web.config" %SystemDrive%\inetpub\wwwroot\servicemodelsamples
表示会将web.config拷贝到站点目录下。
这样,不论我们将示例部署在任务目录,都可以正确访问到服务地址。建议将本地的80端口对应的站点,使用asp.net 4.0的应用程序池,配置ServiceModelSamples应用程序目录。
6.1.5.1 Svc文件
可以通过右击类库,添加\新建项,选择文本文件,将名称命为service.svc的方式为服务创建 .svc 文件。.svc 文件的最常见语法如以下语句所示:
<% @ServiceHost Service="MyNamespace.MyServiceImplementationTypeName" %>
//命名空间名+实现协定的类名
6.1.5.2 Web.config文件
<system.serviceModel>
<protocolMapping>
<add scheme="http" binding="wsHttpBinding" />
</protocolMapping>
<bindings>
<!-- configure a binding that support a session -->
<wsHttpBinding>
<binding>
<reliableSession enabled="true" />
</binding>
</wsHttpBinding>
</bindings>
<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
本示例中的配置项,比这前的示例中的app.config中的项要简单些,仅定义了protocolMapping、bindings、serviceBehaviors,并没有host、endpoint部分。
6.1.5.3 ServiceContract的SessionMode属性
获取或设置是否允许、不允许或要求会话。使用 SessionMode 属性来要求在终结点之间支持会话的绑定。 会话就是将在两个或多个终结点之间交换的消息集相互关联的方式。 如果您的服务支持信道会话,则可以使用 InstanceContextMode 属性指定您的服务协定实现实例与信道会话实例之间的关系。
例如,如果将 SessionMode 属性设置为 SessionMode.Required 并将 InstanceContextMode 属性设置为 PerSession,则客户端将使用相同的连接重复调用同一个服务对象。换个方式来理解,就是会以新的实例创建服务对象。
成员名称 |
说明 | |
Allowed |
指定当传入绑定支持会话时,协定也支持会话。 | |
Required |
指定协定需要会话绑定。 如果绑定并未配置为支持会话,则将引发异常。 | |
NotAllowed |
指定协定永不支持启动会话的绑定。 |
如果您的服务支持会话,则可以使用 ServiceBehaviorAttribute.InstanceContextMode 属性指定您的服务协定实现实例与通道会话之间的关系。
6.1.5.4 ServiceBehavior的InstanceContextMode 属性
获取或设置指示新服务对象何时创建的值。默认值 PerSession 会构造服务应用程序,以在客户端和服务应用程序之间建立新的通信会话时,创建新的服务对象。 相同会话中的后续调用由同一个对象处理。
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorSession
成员名称 |
说明 | |
PerSession |
为每个会话创建一个新的 InstanceContext 对象。 | |
PerCall |
新的 InstanceContext 对象在每次调用前创建,在调用后回收。 如果信道未创建会话,则该值的行为就如同 PerCall 一样。 | |
Single |
只有一个 InstanceContext 对象用于所有传入呼叫,并且在调用后不回收。 如果服务对象不存在,则创建一个。 |
ServiceBehavior,服务行为,是使用于实现服务协定的类定义上。
6.1.5.5 Session示例解析
服务中的代码:
[ServiceContract(Namespace="http://Microsoft.Samples.Session", SessionMode=SessionMode.Required)]
public interface ICalculatorSession//定义服务协定(接口)
{
[OperationContract(IsOneWay=true)]//单向
void Clear();
......
[OperationContract]//有返回值
double Result();
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorSession//实现接口
{
double result = 0.0D;
public void Clear()//实现操作
{
result = 0.0D;
}
public void AddTo(double n) //实现操作
{
result += n;
}
......
public double Result()//实现操作,返回结果
{
return result;
}
}
客户端代码:
static void Main()
{
CalculatorSessionClient client = new CalculatorSessionClient();//创建代理类访问实例
client.Clear();//清0
client.AddTo(100.0D);//加100
client.SubtractFrom(50.0D);//减50
client.MultiplyBy(17.65D);//乘
client.DivideBy(2.0D);//除
double result = client.Result();//返回计算结果
Console.WriteLine("(((0 + 100) - 50) * 17.65) / 2 = {0}", result);
}
我们可以通过在IE中访问http://localhost/ServiceModelSamples/service.svc,来查看服务。
按F5运行client,结果为: (((0+100)-50)*17.65)/2=441.25。
在不关闭刚运行的client的情况下,我们再执行WCF\Basic\Contract\Service\Session\CS\client\bin\client.exe,显示的结果同上。
将client.Clear();这行注释掉,得到同样的结果,因为使用了PerSession,建立新的通信会话时,创建新的服务对象。InstanceContextMode=InstanceContextMode.PerSession。
如果将client.Clear();这行注释掉,将PerSession改为Single,
按F5运行client,结果为:(((0+100)-50)*17.65)/2=441.25。
结束运行,再按F5运行,结果为: (((0+100)-50)*17.65)/2=4335.28125
验证了Single方式下,只有一个上下文实例在响应客户端请求,且调用后不会回收。
6.1.6 双工协定
双工协定使得客户端和服务器可以独立地相互通信,这样双方都可以启动对另一方的呼叫。双工协定由客户端和服务器之间的两个单向协定组成,并且不需要方法调用是相关的。当服务必须向客户端查询更多信息或在客户端上显式引发事件时,可使用这种协定。
创建双工协定,其实就是在服务器端即创建调用接口,又创建回调接口,在客户端实现回调接口。通常过程为:
1、服务器端创建协定,使用SessionMode和CallbackContract 属性,获取或设置当协定为双工协定时的回调协定类型。例如:CallbackContract=typeof(ICalculatorDuplexCallback)。
SessionMode 枚举:指定可用于指示支持协定需要或支持的可靠会话的值。
双工协定,需要支持会话状态的绑定,即要在ServiceContract中加属性“SessionMode = SessionMode.Required”。
2、服务器端创建回调接口,如:public interface ICalculatorDuplexCallback{}
3、服务器端定义类,实现接口中的各操作。
实现接口类中需要用到OperationContext.GetCallbackChannel<T> 方法:获取调用当前操作的客户端实例的通道。
例如:ICalculatorDuplexCallback callback = null;
callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
双工协定需要使用到的binding的类型为“wsDualHttpBinding”:
<protocolMapping>
<add scheme="http" binding="wsDualHttpBinding" />
</protocolMapping>
4、 客户端使用 ServiceModel 元数据实用工具 (Svcutil.exe) 为客户端生成协定(接口)。
使用 Svcutil.exe 下载元数据文档时,参数url是指定到提供元数据的服务终结点或到联机承载的元数据文档的 URL。本节使用的示例此参数应指定为:
http://localhost/servicemodelsamples/service.svc。
5、 在客户端类中实现回调接口。如:public class CallbackHandler : ICalculatorDuplexCallback{}
6、 创建 InstanceContext 类的一个实例。
7、 使用需要 InstanceContext 对象的构造函数创建 WCF 客户端的一个实例。
8、 根据需要调用 WCF 客户端的方法。
6.1.6.1 双工协定示例
WCF_Duplex_Sample.zip是一个很好的使用 WCF 客户端访问服务的示例。
6.1.7 在服务协定中指定数据传输
可以将 WCF 视为一种消息传递基础结构。服务操作可以接收消息、处理消息以及发送消息。消息是使用操作协定描述的。例如,分析以下协定:
[ServiceContract]
public interface IAirfareQuoteService
{
[OperationContract]
float GetAirfare(string fromCity, string toCity);//返回两城市间的机票价
}
这里,GetAirfare 操作接受了一个包含有关 fromCity 和 toCity 的信息的消息,然后返回一个包含一个数字(机票价)的消息。
描述消息的最简单方式是使用参数列表和返回值。在上面的示例中,fromCity 和 toCity 字符串参数用于描述请求消息,浮点型返回值用于描述答复消息。如果返回值本身不足以描述答复消息,则可以使用 out 参数。例如,下面的操作在其请求消息中包含 fromCity 和 toCity,在其答复消息中包含一个数字和一种货币。
[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);
描述传递的消息,还有很多种方式,详细请参见:
http://msdn.microsoft.com/zh-cn/library/ms732038.aspx。
6.1.8 使用 XmlSerializer 类
WCF可以使用两种不同的序列化技术将应用程序中的数据转换为在客户端和服务之间进行传输的 XML,此过程称为序列化。DataContractSerializer 为默认序列化程序。
WCF 还支持 XmlSerializer 类。XmlSerializer 类不是 WCF 的专用类。ASP.NET Web 服务同样使用该类作为序列化引擎。XmlSerializer 类支持的类型少于 DataContractSerializer 类支持的类型,但它允许对生成的 XML 进行更多的控制,并且支持更多的 XML 架构定义语言 (XSD) 标准。
6.1.9 对数据进行流处理
若要对数据进行流处理,服务的 OperationContract 必须满足两个要求:
a.保留要进行流处理的数据的参数必须是方法中的唯一参数。例如,如果要对输入消息进行流处理,则该操作必须正好具有一个输入参数。同样,如果要对输出消息进行流处理,则该操作必须正好具有一个输出参数或一个返回值。
b.参数和返回值的类型中至少有一个必须是 Stream, Message 或 IXmlSerializable。
例如:
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface IStreamingSample
{
[OperationContract]
Stream GetStream(string data);
[OperationContract]
bool UploadStream(Stream stream);
[OperationContract]
Stream EchoStream(Stream stream);
[OperationContract]
Stream GetReversedStream();
}
6.1.10 创建类或结构的基本数据协定
通过将 DataContractAttribute 属性应用于类来声明该类型具有数据协定。通过将 DataMemberAttribute 属性 (Attribute) 应用于每个成员来定义要序列化的成员(属性 (Property)、字段或事件)。 这些成员称为数据成员。例如:
using System;
using System.Runtime.Serialization;
[DataContract]
public class Person
{
// This member is serialized.
[DataMember]
internal string FullName;
// This is serialized even though it is private.
[DataMember]
private int Age;
// This is not serialized because the DataMemberAttribute
// has not been applied.
private string MailingAddress;
// This is not serialized, but the property is.
private string telephoneNumberValue;
[DataMember]
public string TelephoneNumber
{
get { return telephoneNumberValue; }
set { telephoneNumberValue = value; }
}
}
6.1.11 在协定和服务中指定和处理错误
在基于 SOAP 的应用程序(如 WCF 应用程序)中,服务方法使用 SOAP 错误消息来传递处理错误信息。此外,由于 SOAP 错误在客户端以 XML 格式表示,这是一种任何 SOAP 平台上的客户端都可以使用的具有极好的互操作性的类型系统,可增加 WCF 应用程序的适用范围。
发送到客户端的任何托管异常信息都必须在服务上从异常转换为 SOAP 错误,并在 WCF 客户端中从 SOAP 错误转换为错误异常。
暂不详细学习这部分,需要时参见:http://msdn.microsoft.com/zh-cn/library/ms733721.aspx。
7 Word版下载
WCF就先学习这些基础知识吧,接下来,我需要安排转入MVC的学习了。