数据传输体系结构概述

可以将 Windows Communication Foundation (WCF) 看作一种消息传递基础结构。 它可以接收消息,处理消息,根据用户代码调度消息以便进一步操作,或者从用户代码给定的数据构造消息并将消息发送到目标。 本主题旨在向高级开发人员说明用于处理消息和所包含数据的体系结构。 有关如何发送和接收数据的面向任务的更简单介绍,请参阅 Specifying Data Transfer in Service Contracts

注意

本主题讨论通过检查 WCF 对象模型所看不到的 WCF 实现详细信息。 对于有记载的实现详细信息,请记住两点。 首先,说明非常简单;由于优化或其他原因,实际实现可能更复杂。 其次,切勿依赖于特定的实现详细信息(即使是已记载的详细信息),因为这些信息在版本之间,甚至在服务发行过程中有可能在不另行通知的情况下发生更改。

基本体系结构

WCF 消息处理功能的核心是 Message 类,使用 Message 类中对此进行了详细介绍。 WCF 的运行时组件可分为两个主要部分:通道堆栈和服务框架,其间使用 Message 类作为连接点。

通道堆栈负责在有效的 Message 实例和对应于发送或接收消息数据的某种操作之间进行转换。 在发送端,通道堆栈采用有效的 Message 实例,经过一些处理后,执行逻辑上对应于发送消息的某种操作。 该操作可能是发送 TCP 或 HTTP 数据包、让消息在消息队列中排队、将消息写入数据库、将消息保存到文件共享或其他任何操作,具体取决于实现。 最常见的操作是通过网络协议发送消息。 在接收端发生相反的情况:检测操作(可能是 TCP 或 HTTP 数据包接收操作或任何其他操作),处理后,通道堆栈将此操作转换为有效的 Message 实例。

可以通过直接使用 Message 类和通道堆栈来使用 WCF。 但是,这样做很麻烦且很费时。 另外,Message 对象不提供元数据支持,因此如果以这种方式使用 WCF,则无法生成强类型 WCF 客户端。

因此,WCF 包含一个提供易于使用的编程模型的服务框架,你可以使用该模型构造和接收 Message 对象。 服务框架通过服务协定的概念将服务映射到 .NET Framework 类型,并将消息发送给用户操作,这些操作只是标记有 OperationContractAttribute 属性的 .NET Framework 方法(有关更多详细信息,请参阅设计服务协定)。 这些方法可能具有参数和返回值。 在服务端,服务框架将传入的 Message 实例转换为参数,并将返回值转换为传出的 Message 实例。 在客户端,执行相反的操作。 例如,考虑下面的 FindAirfare 操作。

[ServiceContract]
public interface IAirfareFinderService
{
    [OperationContract]
    int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
<ServiceContract()> _
Public Interface IAirfareFinderService

    <OperationContract()> _
    Function FindAirfare(ByVal FromCity As String, _
    ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer

End Interface

假设在客户端上调用了 FindAirfare 。 客户端上的服务框架会将 FromCityToCity 参数转换为传出的 Message 实例并将该实例传递到通道堆栈以便发送。

在服务端,来自通道堆栈的 Message 实例到达时,服务框架会从消息中提取相关数据来填充 FromCityToCity 参数,然后调用服务端 FindAirfare 方法。 该方法返回时,服务框架会采用返回的整数值和 IsDirectFlight 输出参数,并创建一个包含此信息的 Message 对象实例。 然后,服务框架会将 Message 实例传递到通道堆栈以便发回到客户端。

在客户端,包含响应消息的 Message 实例会从通道堆栈中显现出来。 服务框架会提取返回值和 IsDirectFlight 值并将这些值返回到客户端的调用方。

Message 类

Message 类用作消息的抽象表示形式,但其设计在很大程度上依赖于 SOAP 消息。 Message 包含三个主要信息部分:消息正文、消息头和消息属性。

消息正文

消息正文用于表示消息的实际数据负载。 消息正文始终表示为 XML Infoset。 这并不意味着在 WCF 中创建或接收的所有消息都必须采用 XML 格式。 这要由通道堆栈来确定如何解释消息正文。 通道堆栈可能会将消息正文作为 XML 发出、将转换为某种其他格式,甚至可能会完全忽略该消息正文。 当然,对于 WCF 提供的大多数绑定,消息正文在 SOAP 信封的正文部分中都表示为 XML 内容。

必须认识到的是, Message 类并不一定对表示正文的 XML 数据包含缓冲区。 从逻辑上说, Message 包含 XML Infoset,但可以动态构造此 Infoset,因此它可能永远也不会实际存在于内存中。

将数据放入消息正文

将数据放入消息正文并没有统一的机制。 Message 类有一个抽象方法 OnWriteBodyContents(XmlDictionaryWriter),该方法采用 XmlDictionaryWriterMessage 类的每个子类负责重写此方法并写出其自己的内容。 消息正文逻辑上包含 OnWriteBodyContent 生成的 XML Infoset。 例如,考虑下面的 Message 子类。

public class AirfareRequestMessage : Message
{
    public string fromCity = "Tokyo";
    public string toCity = "London";
    //code omitted…
    protected override void OnWriteBodyContents(XmlDictionaryWriter w)
    {
        w.WriteStartElement("airfareRequest");
        w.WriteElementString("from", fromCity);
        w.WriteElementString("to", toCity);
        w.WriteEndElement();
    }

    public override MessageVersion Version
    {
        get { throw new NotImplementedException("The method is not implemented.") ; }
    }

    public override MessageProperties Properties
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }
    public override MessageHeaders Headers
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool IsEmpty
    {
        get
        {
            return base.IsEmpty;
        }
    }

    public override bool IsFault
    {
        get
        {
            return base.IsFault;
        }
    }
}
Public Class AirfareRequestMessage
    Inherits Message

    Public fromCity As String = "Tokyo"
    Public toCity As String = "London"
    ' Code omitted…
    Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
        w.WriteStartElement("airfareRequest")
        w.WriteElementString("from", fromCity)
        w.WriteElementString("to", toCity)
        w.WriteEndElement()
    End Sub

    Public Overrides ReadOnly Property Version() As MessageVersion
        Get
            Throw New NotImplementedException("The method is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Properties() As MessageProperties
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Headers() As MessageHeaders
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property


    Public Overrides ReadOnly Property IsEmpty() As Boolean
        Get
            Return MyBase.IsEmpty
        End Get
    End Property

    Public Overrides ReadOnly Property IsFault() As Boolean
        Get
            Return MyBase.IsFault
        End Get
    End Property
End Class

实际上,一个 AirfareRequestMessage 实例仅包含两个字符串(“fromCity”和“toCity”)。 但在逻辑上,消息中包含的是下面的 XML Infoset:

<airfareRequest>  
    <from>Tokyo</from>  
    <to>London</to>  
</airfareRequest>  

当然,您通常不会用这种方式创建消息,因为您可以使用服务框架从操作协定参数创建诸如前面的消息。 另外, Message 类具有静态 CreateMessage 方法,您可以使用此方法创建具有常见内容类型的消息:空消息、包含用 DataContractSerializer序列化为 XML 的对象的消息、包含 SOAP 错误的消息、包含由 XmlReader表示的 XML 的消息等。

从消息正文获取数据

可以采用两种主要方式提取消息正文中存储的数据:

  • 通过调用 WriteBodyContents(XmlDictionaryWriter) 方法并传入一个 XML 编写器,可以一次获取整个消息正文。 完整的消息正文会写出到此编写器中。 一次获取整个消息正文也称为“写入消息” 。 编写主要由通道堆栈在发送消息时完成:通道堆栈的某些部分通常会访问整个消息正文、进行编码,然后发送整个消息正文。

  • 从消息正文获取信息的另一种方式是调用 GetReaderAtBodyContents() 并获取一个 XML 读取器。 之后可以通过在读取器上调用方法,根据需要按顺序访问消息正文。 逐段获取消息正文也称为“读取消息” 。 读取消息主要由服务框架在接收消息时使用。 例如,使用 DataContractSerializer 时,服务框架将使用 XML 读取器获取消息正文并将其传递给反序列化引擎,之后,反序列化引擎将开始逐元素地读取消息并构造相应对象图。

消息正文只能检索一次。 这为使用只进流提供了可能。 例如,您可以写入一个从 OnWriteBodyContents(XmlDictionaryWriter) 读取数据的 FileStream 重写,并以 XML Infoset 形式返回结果。 永远不需要“倒回”到文件的开头部分。

WriteBodyContentsGetReaderAtBodyContents 方法只检查以前是否从未检索过该消息正文,然后分别调用 OnWriteBodyContentsOnGetReaderAtBodyContents

WCF 中的消息用法

大多数消息都可以分类为传出消息 (由服务框架创建并由通道堆栈发送的消息)或传入消息 (来自通道堆栈并由服务框架解释的消息)。 而且,通道堆栈可以以缓冲模式或流模式操作。 服务框架还可以公开流处理或非流处理的编程模型。 下表列出了可能出现的各种情况,并提供了有关其实现的简单说明。

消息类型 消息中的正文数据 写入 (OnWriteBodyContents) 实现 读取 (OnGetReaderAtBodyContents) 实现
传出,从非流处理编程模型创建 写入消息所需的数据(例如,序列化消息所需的对象和 DataContractSerializer 实例)* 用于基于存储的数据写出消息的自定义逻辑(例如,在使用的序列化程序 WriteObject 上调用 DataContractSerializer )* 调用 OnWriteBodyContents,缓冲结果,通过缓冲区返回 XML 读取器
传出,从流处理编程模型创建 具有要写入数据的 Stream * 使用 IStreamProvider 机制从存储的流中写出数据* 调用 OnWriteBodyContents,缓冲结果,通过缓冲区返回 XML 读取器
从流通道堆栈传入 一个 Stream 对象,表示通过网络传入的、具有 XmlReader 的数据 使用 XmlReader 从存储的 WriteNode中写出内容 返回存储的 XmlReader
从非流处理通道堆栈传入 一个缓冲区,其中包含具有 XmlReader 的正文数据 使用 XmlReader 从存储的 WriteNode中写出内容 返回存储的 lang

* 这些项不直接在 Message 子类中实现,而是在 BodyWriter 类的子类中实现。 有关 BodyWriter的详细信息,请参阅 Using the Message Class

消息头

消息可以包含标头。 标头在逻辑上由与名称、命名空间和几个其他属性相关联的 XML Infoset 组成。 在 Headers 上使用 Message属性可以访问消息头。 每个标头由一个 MessageHeader 类表示。 消息头通常在使用配置的通道堆栈处理 SOAP 消息时映射到 SOAP 消息头。

将信息放入消息头和从消息头中提取信息类似于使用消息正文。 由于不支持流处理,因此过程有些简化。 可以多次访问同一个标头的内容,并且可以按任意顺序访问标头,强制标头始终进行缓冲。 虽然没有可用于通过标头获取 XML 读取器的通用机制,但是 WCF 内部具有一个 MessageHeader 子类,可表示具有此功能的可读标头。 这种类型的 MessageHeader 是在传入具有自定义应用程序标头的消息时由通道堆栈创建的。 它使服务框架能够使用反序列化引擎(如 DataContractSerializer)来解释这些标头。

有关详细信息,请参阅使用 Message 类

消息属性

消息可以包含属性。 属性是任何与字符串名称关联的 .NET Framework 对象。 通过 Properties 上的 Message属性可以访问这些属性。

与消息正文和消息头不同(通常分别映射到 SOAP 正文和 SOAP 标头),消息属性通常不与消息一起发送或接收。 消息属性主要作为一种通信机制,用于在通道堆栈中的各个通道之间以及通道堆栈和服务模块之间传递有关消息的数据。

例如,作为 WCF 一部分的 HTTP 传输通道在向客户端发送答复时,能够生成多种 HTTP 状态代码,如“404(未找到)”和“500(内部服务器错误)”。 在发送答复消息之前,该通道将检查 MessageProperties 是否包含名为“httpResponse”的属性,该属性包含类型为 HttpResponseMessageProperty 的对象。 如果找到此属性,HTTP 传输通道将查看 StatusCode 属性并使用该状态代码。 如果未找到此属性,则使用默认的“200(确定)”代码。

有关详细信息,请参阅使用 Message 类

整体消息

到目前为止,我们已经讨论了用于单独访问消息各个部分的方法。 不过, Message 类还提供了以整体形式处理整个消息的方法。 例如, WriteMessage 方法可将整个消息写出到 XML 编写器。

为此,必须在整个 Message 实例和 XML Infoset 之间定义一个映射。 实际上存在这样的映射:WCF 使用 SOAP 标准定义此映射。 在将 Message 实例作为 XML Infoset 写出时,生成的 Infoset 是包含该消息的有效 SOAP 信封。 因此, WriteMessage 通常会执行以下步骤:

  1. 写入 SOAP 信封元素开始标记。

  2. 写入 SOAP 标头元素开始标记,写出所有标头并关闭标头元素。

  3. 写入 SOAP 正文元素开始标记。

  4. 调用 WriteBodyContents 或等效方法写出正文。

  5. 关闭正文和信封元素。

前面的步骤与 SOAP 标准密切相关。 实际上,由于存在多个版本的 SOAP,因此情况会更复杂,例如,不知道使用的 SOAP 版本将不可能正确写出 SOAP 信封元素。 而且,有时可能需要完全关闭这种特定于 SOAP 的复杂映射。

为此, Version 上提供了 Message属性。 该属性可以设置为写出消息时要使用的 SOAP 版本,也可以设置为 None 以阻止任何特定于 SOAP 的映射。 如果将 Version 属性设置为 None,则处理整个消息的方法会将消息视为仅由正文构成,例如, WriteMessage 将只调用 WriteBodyContents 而不执行上面列出的多个步骤。 对于传入消息,需要自动检测并正确设置 Version

通道堆栈

声道

如前所述,通道堆栈负责将传出的 Message 实例转换为某种操作(如通过网络发送数据包),或者将某种操作(如接收网络数据包)转换为传入的 Message 实例。

通道堆栈由一个或多个按顺序排序的通道组成。 传出的 Message 实例传递到堆栈中的第一个通道(也称为“最顶端通道” ),该通道将该实例向下传递到堆栈中的下一个通道,依此类推。 消息在最后一个通道中终止,此通道称为“传输通道” 。 传入消息源自传输通道,在堆栈中从一个通道向上传递到另一个通道。 消息通常从最顶端通道传入服务框架。 这是应用程序消息的通常模式,但某些通道的工作方式可能略有不同,例如这些通道可能会发送自己的基础结构消息,而不从其上面的通道传入消息。

消息在堆栈中传递时,通道可能会对消息执行各种操作。 最常见的操作是给传出消息添加标头和读取传入消息的标头。 例如,通道可能会计算消息的数字签名并将此签名添加为标头。 通道也可能检查传入消息的数字签名标头,并阻止没有有效签名的消息在通道堆栈中向上传递。 通道通常还设置或检查消息属性。 虽然允许修改消息正文,但通常不这样做,例如,WCF 安全通道可以加密消息正文。

传输通道和消息编码器

堆栈中的最底端通道负责实际将传出的 Message(由其他通道修改后)转换为某种操作。 在接收端,这是将某种操作转换为由其他通道进行处理的 Message 的通道。

如前所述,这些操作可能有所不同,例如:通过各种协议发送或接收网络数据包、在数据库中读取或写入消息、在“消息队列”队列中对消息进行排队或取消排队,等等。 所有这些操作都有一个共同点:它们都需要在 WCFMessage 实例和可以发送、接收、写入、读取、排队或取消排队的字节组之间进行转换。 将 Message 转换为字节组的过程称为“编码” ,从字节组创建 Message 的逆过程称为“解码” 。

大多数传输通道都使用称为“消息编码器” 的组件来完成编码和解码工作。 消息编码器是 MessageEncoder 类的子类。 MessageEncoder 包括各种 ReadMessageWriteMessage 方法重载,可在 Message 和字节组之间转换。

在发送端,缓冲传输通道将从上面通道中接收的 Message 对象传递到 WriteMessage。 该通道返回字节数组,然后使用此数组执行其操作(如将这些字节包装为有效的 TCP 数据包并将这些数据包发送到正确目标)。 流传输通道首先创建一个 Stream (例如通过传出 TCP 连接),然后传递需要的 StreamMessage 以便发送相应的 WriteMessage 重载,从而写出消息。

在接收端,缓冲传输通道将传入字节(例如传入的 TCP 数据包中的字节)提取到数组中并调用 ReadMessage 以获取可在通道堆栈中继续向上传递的 Message 对象。 流传输通道创建 Stream 对象(例如通过传入 TCP 连接传输的网络流)并将该对象传递到 ReadMessage 以返回 Message 对象。

传输通道和消息编码器之间并不强制分离;可以写入不使用消息编码器的传输通道。 但这种分离具有易于撰写的优势。 只要传输通道仅使用基本 MessageEncoder,它就能使用任何 WCF 或第三方消息编码器。 同样,这个编码器通常可用于任何传输通道。

消息编码器操作

为了说明编码器的典型操作,有必要考虑以下四种情况。

Operation 评论
编码,缓冲 在缓冲模式中,编码器通常创建可变大小的缓冲区,然后在其上创建 XML 编写器。 然后,编码器在编码的消息上调用 WriteMessage(XmlWriter) ,使用 WriteBodyContents(XmlDictionaryWriter)依次写出标头和正文,如本主题前面有关 Message 的一节所述。 然后为要使用的传输通道返回缓冲区的内容(表示为字节数组)。
编码,流处理 在流处理模式中,操作与上面类似,但更简单。 不需要使用缓冲区。 通常在流上创建 XML 编写器并在 WriteMessage(XmlWriter) 上调用 Message 以将消息写出到此编写器。
解码,缓冲 在缓冲模式中解码时,通常创建包含缓冲数据的特殊的 Message 子类。 读取消息头,在消息正文上创建 XML 读取器。 这将是用 GetReaderAtBodyContents()返回的读取器。
解码,流处理 在流处理模式中解码时,通常创建特殊的 Message 子类。 流的前进速度刚好足以读取所有标头并将标头定位在消息正文上。 然后在流上创建 XML 读取器。 这将是用 GetReaderAtBodyContents()返回的读取器。

编码器也可以执行其他功能。 例如,编码器可以共享 XML 读取器和编写器。 每次需要新 XML 读取器或编写器时都进行创建很浪费资源。 因此,编码器通常保持一个可配置大小的读取器池和编写器池。 在前面所述的编码器操作说明中,使用的短语“创建 XML 读取器/编写器”通常是指“从池中取出一个读取器/编写器或在没有可用读取器/编写器时创建一个读取器/编写器”。编码器(及其在解码时创建的 Message 子类)中包含用于在不需要读取器和编写器时(例如关闭 Message 时)将其返回到池的逻辑。

WCF 提供了三种消息编码器,但也可以创建其他自定义类型。 提供的类型为文本、二进制和消息传输优化机制 (MTOM)。 这些编码器将在 Choosing a Message Encoder中详细说明。

IStreamProvider 接口

在将包含经过流处理的正文的传出消息写入 XML 编写器时, Message 会在其 OnWriteBodyContents(XmlDictionaryWriter) 实现中使用类似于下面的一系列调用:

  • 写入流之前的所有必要信息(例如 XML 开始标记)。

  • 写入流。

  • 写入流之后的任何信息(例如 XML 结束标记)。

这对于类似于文本 XML 编码的编码可以正常工作。 但是,有些编码不将 XML Infoset 信息(例如,开始和结束 XML 元素的标记)和元素中包含的数据放在一起。 例如,在 MTOM 编码中,消息拆分为多个部分。 一部分包含 XML Infoset,其中可能包含对实际元素内容的其他部分的引用。 由于 XML Infoset 通常比流处理的内容小,因此有必要缓冲 Infoset,将其写出,然后以流处理方式写入内容。 这意味着在写入结束元素标记时,应当尚未写出流。

为此,应使用 IStreamProvider 接口。 该接口具有一个可返回要写入的流的 GetStream() 方法。 写出 OnWriteBodyContents(XmlDictionaryWriter) 中经过流处理的消息正文的正确方式如下:

  1. 写入流之前的所有必要信息(例如 XML 开始标记)。

  2. 对采用 WriteValue 、具有 XmlDictionaryWriter 实现(该实现可返回要写入的流)的 IStreamProvider调用 IStreamProvider 重载。

  3. 写入流之后的任何信息(例如 XML 结束标记)。

使用此方法时,XML 编写器可以选择何时调用 GetStream() 和写出经过流处理的数据。 例如,文本和二进制 XML 编写器将立即调用此方法并写出开始标记和结束标记之间的经过流处理的内容。 MTOM 编写器准备写入消息的相应部分时,它可以决定以后调用 GetStream()

在服务框架中表示数据

如本主题“基础体系结构”一节中所述,服务框架是 WCF 的一部分,负责在消息数据的用户友好编程模型和实际的 Message 实例之间进行转换。 通常,消息交换在服务框架中表示为用 OperationContractAttribute 属性标记的 .NET Framework 方法。 此方法可以接受参数并可以返回一个返回值和/或 out 参数。 在服务端,输入参数表示传入消息,返回值和 out 参数表示传出消息。 在客户端,情况正好相反。 使用参数和返回值来说明消息的编程模型将在 Specifying Data Transfer in Service Contracts中详细说明。 不过,本节将提供简要概述。

编程模型

WCF 服务框架支持五种用于说明消息的编程模型:

1.空消息

这是最简单的情况。 若要说明空的传入消息,请不要使用任何输入参数。

[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer

若要说明空的传出消息,请使用一个空的返回值且不使用任何 out 参数:

[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)

请注意,这不同于单向操作协定:

[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)

SetDesiredTemperature 示例中,说明了双向消息交换模式。 从操作中返回了消息,但该消息为空。 可以从操作中返回错误。 在“Set Lightbulb”示例中,消息交换模式为单向,因此没有要说明的传出消息。 在此例中,服务无法将任何状态传回客户端。

2.直接使用 Message 类

在操作协定中可以直接使用 Message 类(或它的一个子类)。 在这种情况下,服务框架只将 Message 从操作传递到通道堆栈(或从通道堆栈传递到操作),不进行其他处理。

直接使用 Message 有两种主要使用场合。 如果任何其他编程模型都不能灵活地说明消息,则可以对高级方案使用此模型。 例如,您可能想使用磁盘上的文件来说明消息,用文件的属性作为消息头,用文件的内容作为消息正文。 您可以创建类似于下面的内容。

public class FileMessage : Message
// Code not shown.
Public Class FileMessage
    Inherits Message
    ' Code not shown.

操作协定中 Message 的第二种常见用途是,服务并不关心特定的消息内容,而是像对待黑盒子一样对待消息。 例如,您可能具有将消息转发给多个其他收件人的服务。 可以按以下方式编写协定。

[OperationContract]
public FileMessage GetFile()
{
    //code omitted…
    FileMessage fm = new FileMessage("myFile.xml");
    return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
    'code omitted…
    Dim fm As New FileMessage("myFile.xml")
    Return fm
End Function

Action="*" 一行可以有效地关闭消息调度并确保发送到 IForwardingService 协定的所有消息都会发送到 ForwardMessage 操作。 (通常,调度程序会检查消息的“Action”标头以确定消息用于什么操作。Action="*" 表示“Action 标头的所有可能值”。)Action="*" 和使用 Message 作为参数的组合称为“通用协定”,因为它能够接收所有可能的消息。 为了能够发送所有可能的消息,请使用 Message 作为返回值并将 ReplyAction 设置为“*”。 这将阻止服务框架添加其自己的 Action 标头,使您能够使用返回的 Message 对象控制此标头。

3.消息协定

WCF 为说明消息提供了一种声明性编程模型,称为“消息协定”。 此模型将在 Using Message Contracts中详细说明。 实质上,整个消息由单个 .NET Framework 类型表示,该类型使用 MessageBodyMemberAttributeMessageHeaderAttribute 等属性来说明消息协定类的哪些部分应当映射到消息的哪个部分。

消息协定对生成的 Message 实例提供广泛的控制(虽然明显没有直接使用 Message 类所提供的控制那样广泛)。 例如,消息正文通常由多段信息组成,每段消息由其各自的 XML 元素表示。 这些元素可以直接出现在正文中(空 模式),也可以包装 在包含 XML 元素中。 使用消息协定编程模型时可以进行空与包装决策并控制包装名称和命名空间的名称。

下面的消息协定代码示例演示了这些功能。

[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
    [MessageHeader]
    public string customerID;
    [MessageBodyMember]
    public string item;
    [MessageBodyMember]
    public int quantity;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
    <MessageHeader()> Public customerID As String
    <MessageBodyMember()> Public item As String
    <MessageBodyMember()> Public quantity As Integer
End Class

标记为要进行序列化(使用 MessageBodyMemberAttributeMessageHeaderAttribute或其他相关特性)的项必须为可序列化的,以参与消息协定。 有关详细信息,请参阅本主题后面的“序列化”部分。

4.参数

通常,想要说明对多段数据执行的操作的开发人员并不需要消息协定所提供的那样高的控制度。 例如,在创建新服务时,开发人员通常不需要进行空与包装决策和确定包装元素名称。 进行这些决策通常需要了解有关 Web 服务和 SOAP 的深入知识。

WCF 服务框架可以自动挑选最佳和最具互操作性的 SOAP 表示形式来发送或接收多个相关信息段,而不强制用户进行这些选择。 只要将这些信息段作为参数或操作协定的返回值进行说明就能实现此目的。 例如,请考虑下面的操作协定。

[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
    ByVal customerID As String, _
    ByVal item As String, _
    ByVal quantity As Integer)

服务框架将自动决定将所有三段信息(customerIDitemquantity)放入消息正文并将它们包装在名为 SubmitOrderRequest的包装元素中。

推荐的方法是将要发送或接收的信息作为操作协定参数的简单列表进行说明,除非存在特殊原因需要移动到更复杂的消息协定或基于 Message的编程模型。

5.流

在操作协定中使用 Stream 或它的一个子类或者将其作为消息协定中唯一的消息正文部分,这种方式可视为与上述模型不同的一种单独的编程模型。 以这种方式使用 Stream 是除了编写您自己的流兼容 Message 子类以外,保证协定能够以流处理方式使用的唯一途径。 有关详细信息,请参阅大型数据和流式处理

以这种方式使用 Stream 或它的一个子类时,不会调用序列化程序。 对于传出消息,会创建一个特殊流 Message 子类并写出流,如关于 IStreamProvider 接口的一节中所述。 对于传入消息,服务框架会在传入消息上创建一个 Stream 子类并将该子类提供给操作。

编程模型限制

上述编程模型不能任意组合。 例如,如果某个操作接受某种消息协定类型,则消息协定必须是其唯一的输入参数。 而且,操作必须返回空消息(void 返回类型)或另一个消息协定。 这些编程模型限制将在每个特定编程模型的主题: Using Message ContractsUsing the Message ClassLarge Data and Streaming中进行说明。

消息格式化程序

通过将称为“消息格式化程序” 的组件插入到服务框架中可以为上述编程模型提供支持。 消息格式化程序是实现 IClientMessageFormatter 和/或 IDispatchMessageFormatter 接口(以便分别在客户端和服务 WCF 客户端中使用)的类型。

消息格式化程序通常由行为插入。 例如, DataContractSerializerOperationBehavior 可插入数据协定消息格式化程序。 通过在服务端在 Formatter 方法中将 ApplyDispatchBehavior(OperationDescription, DispatchOperation) 设置为正确的格式化程序,或者在客户端在 Formatter 方法中将 ApplyClientBehavior(OperationDescription, ClientOperation) 设置为正确的格式化程序,可以完成此操作。

下表列出了消息格式化程序可能实现的方法。

接口 方法 操作
IDispatchMessageFormatter DeserializeRequest(Message, Object[]) 将传入的 Message 转换为操作参数
IDispatchMessageFormatter SerializeReply(MessageVersion, Object[], Object) 从操作返回值/out 参数创建传出的 Message
IClientMessageFormatter SerializeRequest(MessageVersion, Object[]) 从操作参数创建传出的 Message
IClientMessageFormatter DeserializeReply(Message, Object[]) 将传入的 Message 转换为返回值/out 参数

序列化

每当使用消息协定或参数来说明消息内容时,必须使用序列化在 .NET Framework 类型和 XML Infoset 表示形式之间进行转换。 WCF 中的其他位置也使用序列化,例如,Message 具有一个泛型 GetBody 方法,可以用该方法来读取反序列化为对象的整个消息正文。

WCF 支持两种“现成”的序列化技术,用于对参数和消息部分进行序列化和反序列化:DataContractSerializerXmlSerializer。 另外,您也可以编写自定义序列化程序。 但是,WCF 的其他部分(如泛型 GetBody 方法或 SOAP 错误序列化)可能限制为仅使用 XmlObjectSerializer 子类(DataContractSerializerNetDataContractSerializer,而不是 XmlSerializer),甚至已经硬编码为仅使用 DataContractSerializer

XmlSerializer 是 ASP.NET Web 服务中使用的序列化引擎。 DataContractSerializer 是理解新数据协定编程模型的新序列化引擎。 DataContractSerializer 默认选择,可以通过使用 XmlSerializer 特性对每个操作选择使用 DataContractFormatAttribute

DataContractSerializerOperationBehaviorXmlSerializerOperationBehavior 是分别负责为 DataContractSerializerXmlSerializer插入消息格式化程序的操作行为。 DataContractSerializerOperationBehavior 行为实际上可操作从 XmlObjectSerializer派生的任何序列化程序,包括 NetDataContractSerializer (在“使用独立序列化”中进行详细说明)。 此行为调用 CreateSerializer 虚拟方法重载之一以获取序列化程序。 若要插入其他序列化程序,请创建一个新的 DataContractSerializerOperationBehavior 子类并重写两个 CreateSerializer 重载。

另请参阅