Windows Communication Foundation (WCF)可视为消息传递基础结构。 它可以接收消息、处理消息并将其调度到用户代码以执行进一步作,也可以从用户代码提供的数据构造消息并将其传送到目标。 本主题面向高级开发人员,介绍用于处理消息和包含数据的体系结构。 有关如何发送和接收数据的更简单、面向任务的视图,请参阅 在服务协定中指定数据传输。
注释
本主题讨论在 WCF 对象模型中不可见的 WCF 实现细节。 对于有记载的实现详细信息,请记住两点。 首先,简化说明;由于优化或其他原因,实际实现可能更为复杂。 其次,你绝不应依赖特定的实现细节,即便是有文档记录的那些,因为这些细节可能会在版本之间或甚至在一次维护更新中不通知地发生更改。
基本体系结构
WCF 消息处理功能的核心是 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
在客户端上调用。 客户端上的服务框架将 FromCity
和 ToCity
参数转换为传出 Message 实例,并将其传递给通道堆栈以便发送。
在服务端,当实例从通道堆栈到达时 Message ,服务框架将从消息中提取相关数据以填充 FromCity
和 ToCity
参数,然后调用服务端 FindAirfare
方法。 此方法返回时,服务框架获取返回的整数值和 IsDirectFlight
输出参数,并创建 Message 包含此信息的对象实例。 然后,将 Message
实例传递到通道堆栈,发回客户端。
在客户端上, Message 包含响应消息的实例从通道堆栈中浮出。 服务框架提取返回值和 IsDirectFlight
值,并将其返回到客户端的调用方。
Message 类
该 Message 类旨在作为消息的抽象表示形式,但其设计与 SOAP 消息密切相关。 A Message 包含三个主要信息:消息正文、消息标头和消息属性。
邮件正文
消息正文旨在表示消息的实际数据有效负载。 消息正文始终表示为 XML Infoset。 这并不意味着在 WCF 中创建或接收的所有消息都必须采用 XML 格式。 由通道堆栈决定如何解释消息正文。 它可以以 XML 形式发出它,将其转换为其他格式,甚至完全省略它。 当然,由于 WCF 提供的大部分绑定,消息正文在 SOAP 信封的正文部分中表示为 XML 内容。
请务必认识到, Message
该类不一定包含表示正文的 XML 数据的缓冲区。 从逻辑上讲, Message
包含 XML 信息集,但此 Infoset 可以动态构造,并且可能永远不会在内存中实际存在。
将数据放入消息正文
没有统一的机制将数据放入消息正文。 该 Message 类具有一个抽象方法 OnWriteBodyContents(XmlDictionaryWriter),该方法接受一个 XmlDictionaryWriter。 Message 类的每个子类负责重写此方法并写出其自己的内容。 消息正文在逻辑上包含生成的 OnWriteBodyContent
XML 信息集。 例如,请考虑以下 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 信息集:
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
当然,您通常不会以这种方式创建消息,因为您可以使用该服务框架从操作合同参数创建如前述消息。 此外,该 Message 类具有静态 CreateMessage
方法,可用于创建具有常见内容类型的消息:空消息、包含序列化为 XML DataContractSerializer的对象的消息、包含 SOAP 错误的消息、包含 XML XmlReader表示的消息等。
从消息正文中获取数据
可以通过两种主要方式提取消息正文中存储的数据:
通过调用 WriteBodyContents(XmlDictionaryWriter) 方法并传入一个 XML 编写器,可以一次获取整个消息正文。 完整的消息正文会写出到此编写器中。 一次获取整个消息正文也称为“写入消息” 。 写入主要由通道堆栈在发送消息时完成—通道堆栈的某些部分通常可以访问整个消息正文、对其进行编码和发送。
从消息正文中获取信息的另一种方法是调用 GetReaderAtBodyContents() 和获取 XML 读取器。 然后,可以通过对读取器调用方法按顺序访问消息正文。 获取消息正文逐条也称为 阅读消息。 读取消息主要由服务框架在接收消息时使用。 例如,在DataContractSerializer被使用时,服务框架会在正文上获取一个XML读取器,并将其传递给反序列化引擎,然后开始逐个读取消息元素,从而构建相应的对象图。
消息正文只能检索一次。 这为使用只进流提供了可能。 例如,可以编写一个 OnWriteBodyContents(XmlDictionaryWriter) 重写,该重写从 FileStream 中读取并将结果作为 XML 信息集返回。 你永远不需要“倒退”到文件的开头。
WriteBodyContents
和GetReaderAtBodyContents
方法仅检查消息正文是否从未被检索过,然后分别调用OnWriteBodyContents
或OnGetReaderAtBodyContents
。
WCF 中的消息用法
大多数消息可以归类为 传出 (由服务框架创建的传出消息)或 传入 (从通道堆栈到达并由服务框架解释的消息)。 此外,通道堆栈可以在缓冲模式或流式处理模式下运行。 服务框架还可以公开流式处理或非流式编程模型。 这将产生下表中列出的案例及其简要实施细节。
消息类型 | 消息中的正文数据 | 写入 (OnWriteBodyContents) 实现 | 读取 (OnGetReaderAtBodyContents) 实现 |
---|---|---|---|
传出,从非流处理编程模型创建 | 写入消息所需的数据(例如,序列化消息所需的对象和 DataContractSerializer 实例)* | 用于基于存储的数据写出消息的自定义逻辑(例如,在使用的序列化程序 WriteObject 上调用 DataContractSerializer )* |
调用 OnWriteBodyContents ,缓冲结果,在缓冲区上返回 XML 读取器 |
传出,从流处理编程模型创建 | 具有要写入数据的 Stream * |
使用 IStreamProvider 机制从存储的流中写出数据* | 调用 OnWriteBodyContents ,缓冲结果,在缓冲区上返回 XML 读取器 |
从流通道堆栈传入 | 一个 Stream 对象,表示通过网络传入的数据,其上叠加了一个 XmlReader 对象 |
使用 XmlReader 从存储的 WriteNode 中写出内容 |
返回存储的 XmlReader |
从非流处理通道堆栈传入 | 包含正文数据且上方有 XmlReader 的缓冲区 |
使用 XmlReader 从存储的 WriteNode 中写出内容 |
返回存储的 lang |
* 这些项不是直接在Message
子类中实现的,而是在BodyWriter类的子类中实现的。 有关该 BodyWriter消息的详细信息,请参阅 “使用消息类”。
邮件头
消息可能包含标头。 标头在逻辑上由与名称、命名空间和其他几个属性关联的 XML 信息集组成。 使用 Headers
属性访问 Message消息标头。 每个标头都由一个 MessageHeader 类表示。 通常,当使用配置为处理 SOAP 消息的通道堆栈时,消息标头会被映射到 SOAP 消息标头。
将信息放入消息标头并从中提取信息类似于使用消息正文。 此过程有点简化,因为不支持流式处理。 可以多次访问同一标头的内容,并且可以按任意顺序访问标头,从而强制始终缓冲标头。 没有可用于通过标头获取 XML 读取器的常规用途机制,但 WCF 内部有一个 MessageHeader
子类,表示具有此类功能的可读标头。 当有自定义应用程序标头的消息传入时,通道堆栈会创建这种类型的 MessageHeader
消息。 这使服务框架能够使用反序列化引擎(如 the DataContractSerializer)来解释这些标头。
有关详细信息,请参阅 “使用消息类”。
消息属性
消息可能包含属性。 属性是与字符串名称关联的任何 .NET Framework 对象。 可以通过Message
上的Properties
属性来访问属性。
与消息正文和消息标头(通常分别映射到 SOAP 正文和 SOAP 标头)不同,消息属性通常不会随消息一起发送或接收。 消息属性主要作为一种通信机制,用于在通道堆栈中的各个通道之间以及通道堆栈和服务模型之间传递有关消息的数据。
例如,作为 WCF 一部分包含的 HTTP 传输通道能够生成各种 HTTP 状态代码,例如“404(未找到)”和“500(内部服务器错误),”在向客户端发送答复时。 在发送回复消息之前,它会检查Properties
Message
是否包含一个名为“httpResponse”的属性,该属性包含类型的HttpResponseMessageProperty对象。 如果找到此类属性,它将查看该 StatusCode 属性并使用该状态代码。 如果未找到,则使用默认的“200(确定)”代码。
有关详细信息,请参阅 “使用消息类”。
整体消息
到目前为止,我们讨论了在隔离状态下访问消息的各个部分的方法。 但是,该 Message 类还提供用于处理整个消息的方法。 例如,该方法 WriteMessage
将整个消息写出给 XML 编写器。
为此,必须在整个 Message
实例和 XML 信息集之间定义映射。 事实上,此类映射存在:WCF 使用 SOAP 标准来定义此映射。 Message
将实例写出为 XML 信息集时,生成的 Infoset 是包含消息的有效 SOAP 信封。 因此, WriteMessage
通常会执行以下步骤:
编写 SOAP 信封元素的开始标签。
写入 SOAP 标头元素的起始标签,写出所有的标头,然后关闭标头元素。
编写 SOAP 消息体元素起始标签。
调用
WriteBodyContents
或等效的方法以写出正文。关闭正文和信封元素。
上述步骤与 SOAP 标准密切相关。 这很复杂,因为存在多个版本的 SOAP,例如,在不知道使用的 SOAP 版本的情况下,无法正确写出 SOAP 信封元素。 此外,在某些情况下,可能需要完全关闭此复杂的 SOAP 特定映射。
出于这些目的,在Message
上提供了一个Version
属性。 它可以设置为在写出消息时使用的 SOAP 版本,也可以设置为 None
阻止任何特定于 SOAP 的映射。 如果Version
属性设置为None
,那么处理整个消息的方法会像消息仅包含其正文一样,WriteMessage
只需调用WriteBodyContents
,而不是执行上面列出的多个步骤。 预期在传入的消息中,Version
将被自动检测并正确设置。
通道堆栈
渠道
如前所述,通道堆栈负责将传出 Message 实例转换为某些作(例如通过网络发送数据包),或将某些作(例如接收网络数据包)转换为传入 Message
实例。
通道堆栈由一个或多个按序列排序的通道组成。 传出 Message
实例将传递到堆栈中的第一个通道(也称为 最顶层的通道),该通道将其传递到堆栈中的下一个通道,依此类推。 消息在最后一个通道中终止,称为 传输通道。 传入消息源自传输通道,并通过不同的通道逐级向上传递。 从顶层通道中,消息通常被传递到服务框架中。 虽然这是应用程序消息的常见模式,但某些通道可能的工作方式略有不同,例如,它们可能会发送自己的基础结构消息,而无需从上面的通道传递消息。
消息在堆栈中传递时,通道可能会对消息执行各种操作。 最常见的作是向传出消息添加标头,并在传入消息上读取标头。 例如,通道可以计算消息的数字签名,并将其添加为标头。 通道还可以检查传入消息上的此数字签名标头,并阻止没有有效签名的消息进入通道堆栈。 通道也经常设置或检查消息属性。 消息正文通常不会修改,尽管允许这样做,例如 WCF 安全通道可以加密消息正文。
传输通道和消息编码器
堆栈中的最底端通道负责实际将传出的 Message(由其他通道修改后)转换为某种操作。 在接收端,这是将某种操作转换为由其他通道进行处理的 Message
的通道。
如前所述,这些动作可能有所不同:通过各种协议发送或接收网络数据包、在数据库中读取或写入消息,或在消息队列中排队或取消排队消息,以提供几个示例。 所有这些动作都有一个共同点:它们需要在 WCFMessage
实例与可以发送、接收、读取、写入、排队或取消排队的实际字节组之间进行转换。 转换为 Message
一组字节的过程称为 编码,从一组字节创建 Message
反向过程称为 解码。
大多数传输通道使用称为 消息编码器的 组件来完成编码和解码工作。 消息编码器是类的 MessageEncoder 子类。 MessageEncoder
包括各种 ReadMessage
和 WriteMessage
方法重载,可在 Message
和字节组之间转换。
在发送端,缓冲传输通道将从其上方通道接收到的Message
对象传递给WriteMessage
。 它获取一个字节数组,然后用于执行其作(例如将这些字节打包为有效的 TCP 数据包并将其发送到正确的目标)。 流传输通道首先创建一个 Stream
(例如通过传出 TCP 连接),然后传递需要的 Stream
和 Message
以便发送相应的 WriteMessage
重载,从而写出消息。
在接收端,缓冲传输通道将传入字节(例如,从传入的 TCP 数据包)提取到数组中,并调用 ReadMessage
以获取一个 Message
对象,该对象可以进一步向上传递通道堆栈。 流传输通道创建一个Stream
对象(例如,通过传入的 TCP 连接创建的网络流),并将其传递给ReadMessage
以获取Message
对象。
传输通道和消息编码器之间的分离不是必需的;可以编写不使用消息编码器的传输通道。 但是,这种分离的优点是易于组合。 只要传输通道仅使用基础 MessageEncoder,它就可以与任何 WCF 或第三方消息编码器一起使用。 同样,同一编码器通常可用于任何传输通道。
消息编码器操作
要描述编码器的典型操作,考虑以下四种情况是非常有用的。
操作 | 注释 |
---|---|
编码,缓冲 | 在缓冲模式下,编码器通常会创建可变大小的缓冲区,然后创建一个 XML 编写器。 然后,它调用 WriteMessage(XmlWriter) 对被编码的消息进行操作,先写出标头,然后使用 WriteBodyContents(XmlDictionaryWriter) 编码正文,如本主题中有关 Message 的上一节所述。 然后返回缓冲区的内容(表示为字节数组),供传输通道使用。 |
编码,流处理 | 在流式传输模式下,操作类似于上述操作,但更简单。 不需要缓冲区。 通常在流上创建 XML 编写器并在 WriteMessage(XmlWriter) 上调用 Message 以将消息写出到此编写器。 |
解码,缓冲 | 在缓冲模式下解码时,通常会创建包含缓冲数据的特殊 Message 子类。 将读取消息的标头,并创建位于消息正文上的 XML 读取器。 这将是用 GetReaderAtBodyContents()返回的读取器。 |
解码,流处理 | 在流模式下解码时,通常会创建特殊的 Message 子类。 流的前进速度刚好足以读取所有标头并将标头定位在消息正文上。 然后,通过流创建 XML 读取器。 这将是用 GetReaderAtBodyContents()返回的读取器。 |
编码器也可以执行其他功能。 例如,编码器可以池化 XML 读取器和写入器。 每次需要一个 XML 读取器或编写器时,创建一个新的 XML 读取器或编写器会非常昂贵。 因此,编码器通常维护读取器池和可配置大小的编写器池。 在前面描述编码器操作的说明中,每当使用短语“创建 XML 读取器/编写器”时,它通常表示“从池中获取一个,或者在不可用时创建一个。”编码器(以及在解码时创建的 Message
子类)包含逻辑,用于在不再需要读取器和写入器时(例如当 Message
关闭时)将它们归还到池中。
WCF 提供了三个消息编码器,尽管可以创建其他自定义类型。 提供的类型为文本、二进制和消息传输优化机制(MTOM)。 在 “选择消息编码器”中详细介绍了这些内容。
IStreamProvider 接口
在将包含经过流处理的正文的传出消息写入 XML 编写器时, Message 会在其 OnWriteBodyContents(XmlDictionaryWriter) 实现中使用类似于下面的一系列调用:
写入流之前的所有必要信息(例如 XML 开始标记)。
写入流。
写入流之后的任何信息(例如 XML 结束标记)。
这对于类似于文本 XML 编码的编码可以正常工作。 但是,某些编码不会将 XML 信息集信息(例如开始和结束 XML 元素的标记)与元素中包含的数据放在一起。 例如,在 MTOM 编码中,消息拆分为多个部分。 一个部分包含 XML 信息集,它可能包含对实际元素内容的其他部分的引用。 与流式传输的内容相比,XML 信息集通常很小,因此缓冲 Infoset、写出信息集,然后以流式传输方式写入内容是有意义的。 这意味着在写入结束元素标记时,应当尚未写出流。
为此目的, IStreamProvider 接口被使用。 该接口有一个 GetStream() 返回要写入的流的方法。 在 OnWriteBodyContents(XmlDictionaryWriter) 中编写流式消息正文的正确方法如下所示:
写入流之前的所有必要信息(例如 XML 开始标记)。
对采用
WriteValue
、具有 XmlDictionaryWriter 实现(该实现可返回要写入的流)的 IStreamProvider调用IStreamProvider
重载。写入流之后的任何信息(例如 XML 结束标记)。
使用此方法,XML 编写器可以选择何时调用 GetStream() 和写出流数据。 例如,文本和二进制 XML 编写器将立即调用它,并在开始标记和结束标记之间写出流式传输的内容。 当 MTOM 编写器准备好编写消息的相应部分时,MTOM 编写器可能会决定稍后调用 GetStream() 。
在服务框架中表示数据
如本主题的“基本体系结构”部分所述,服务框架是 WCF 的一部分,除此之外,服务框架还负责在消息数据和实际 Message
实例的用户友好编程模型之间进行转换。 通常,消息交换在服务框架中表示为用属性标记的 OperationContractAttribute .NET Framework 方法。 该方法可以接受某些参数,并且可以返回返回值或输出参数(或两者)。 在服务端,输入参数表示传入消息,返回值和输出参数表示传出消息。 在客户端,情况正好相反。 使用参数和返回值描述消息的编程模型在 “在服务协定中指定数据传输”中进行了详细介绍。 但是,本部分将提供简要概述。
编程模型
WCF 服务框架支持五种不同的编程模型来描述消息:
1. 空消息
这是最简单的情况。 若要描述空传入消息,请勿使用任何输入参数。
[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer
若要描述空传出消息,请使用 void 返回值,并且不使用任何 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
示例中,描述了双向消息交换模式。 从操作返回了一条消息,但它为空。 可以从操作中返回错误。 在“设置灯泡”示例中,消息交换模式是单向的,因此没有要描述的传出消息。 在这种情况下,服务无法将任何状态传回客户端。
2. 直接使用消息类
可以直接在作协定中使用 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="*" 与将 Message 用作参数的组合被称为“通用协定”,因为它能够接收所有可能的消息。) 若要能够发送所有可能的消息,请使用 Message 作为返回值并设置为 ReplyAction
“*”。 这将阻止服务框架添加自己的 Action 标头,使你能够使用 Message
返回的对象控制此标头。
3. 消息协定
WCF 提供声明性编程模型来描述消息,称为 消息协定。 此模型在 “使用消息协定”中进行了详细介绍。 实质上,整个消息由单个 .NET Framework 类型表示,该类型使用诸如消息协定类的属性 MessageBodyMemberAttribute 并 MessageHeaderAttribute 描述消息协定类的哪些部分应映射到消息的哪个部分。
消息协定提供了对生成的 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
标记为要序列化的项(带有 MessageBodyMemberAttribute或 MessageHeaderAttribute其他相关属性)必须可序列化才能参与消息协定。 有关详细信息,请参阅本主题后面的“序列化”部分。
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)
服务框架自动决定将所有三个信息(customerID
和item
quantity
)放入消息正文中,并将其包装在名为SubmitOrderRequest
的包装元素中。
建议使用操作契约参数的简单列表来描述要发送或接收的信息,除非存在特殊原因需要转移到更复杂的消息契约或基于 Message
的编程模型。
5.流
在操作合约中使用 Stream
或其任一子类,或者将其作为消息合约中唯一的消息体部分,可以被视为与上述描述不同的编程模型。 以这种方式使用 Stream
是保证合同以流式传输方式使用的唯一方法,只需编写自己的与流式处理兼容的 Message
子类即可。 有关详细信息,请参阅 大型数据和流式处理。
当使用 Stream
或其子类之一以这种方式时,不会调用序列化程序。 对于传出消息,会创建一个特殊流 Message
子类并写出流,如关于 IStreamProvider 接口的一节中所述。 对于传入的消息,服务框架在该消息上创建一个Stream
子类,并将其提供给操作。
编程模型限制
上述编程模型不能任意组合。 例如,如果某个作接受消息协定类型,则消息协定必须是其唯一的输入参数。 此外,该操作必须返回空消息(void 的返回类型)或其他消息契约。 每个特定编程模型的主题介绍了这些编程模型限制: 使用消息协定、 使用消息类和 大型数据和流式处理。
消息格式化程序
通过将称为 消息格式化程序的 组件插入服务框架来支持上述编程模型。 消息格式化程序是实现 IClientMessageFormatter 和/或 IDispatchMessageFormatter 接口(以便分别在客户端和服务 WCF 客户端中使用)的类型。
消息格式化程序通常由行为插入。 例如, DataContractSerializerOperationBehavior 可插入数据协定消息格式化程序。 在服务端通过在ApplyDispatchBehavior(OperationDescription, DispatchOperation)方法中将Formatter设置为正确的格式化程序,或者在客户端通过在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 转换为返回值/输出参数 |
序列化
每当使用消息协定或参数来描述消息内容时,都必须使用序列化在 .NET Framework 类型和 XML Infoset 表示形式之间进行转换。 例如,在 WCF 中的其他位置使用序列化, Message 可以使用泛型 GetBody 方法将反序列化消息的整个正文读取到对象中。
WCF 支持“开箱即用”的两种序列化技术,用于序列化和反序列化参数和消息部分:DataContractSerializer和 。XmlSerializer
此外,还可以编写自定义序列化程序。 但是,WCF 的其他部分(如泛型GetBody
方法或 SOAP 错误序列化)可能仅限于使用XmlObjectSerializer子类(DataContractSerializer而不是NetDataContractSerializerXmlSerializer子类),甚至可能硬编码为仅使用子DataContractSerializer类。
XmlSerializer
是 ASP.NET Web 服务中使用的序列化引擎。 DataContractSerializer
这是了解新数据协定编程模型的新序列化引擎。 DataContractSerializer
是默认选项,可以通过使用DataContractFormatAttribute属性在每次操作时选择使用XmlSerializer
。
DataContractSerializerOperationBehavior 和 XmlSerializerOperationBehavior 是分别负责为 DataContractSerializer
和 XmlSerializer
插入消息格式化程序的操作行为。 该 DataContractSerializerOperationBehavior 行为实际上可与派生自 XmlObjectSerializer的任何序列化程序一起运行,包括 NetDataContractSerializer (在“使用 Stand-Alone 序列化”中详细介绍)。 此行为调用 CreateSerializer
虚拟方法重载之一以获取序列化程序。 若要插入其他序列化程序,请创建一个新的 DataContractSerializerOperationBehavior 子类并重写两个 CreateSerializer
重载。