现在的位置: 首页 > 综合 > 正文

WCF技术剖析之十七:消息(Message)详解(上篇)

2011年07月01日 ⁄ 综合 ⁄ 共 7286字 ⁄ 字号 评论关闭

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]消息交换是WCF进行通信的唯一手段,通过方法调用(Method Call)形式体现的服务访问需要转化成具体的消息,并通过相应的编码(Encoding)才能通过传输通道发送到服务端;服务操作执行的结果也只能以消息的形式才能被正常地返回到客户端。所以,消息在整个WCF体系结构中处于一个核心的地位,WCF可以看成是一个消息处理的管道。

尽管消息在整个WCF体系中具有如此重要的意义,可是一般的WCF编程人员,却意识不到消息的存在。原因很简单,WCF设计的目标就是实现消息通信的所有细节,为最终的编程人员提供一个完全面向对象的编程模型。所以对于一般的编程人员来说,他们面对的是接口,却不知道服务契约对于服务的描述;面对的是数据类型,却不知道数据契约对序列化的作用;面对的是方法调用和返回值的获取,却不了解底层消息交换的过程。

鼓励大家深入了解WCF关于消息处理的流程具有两个目的:第一,只有在对整个消息处理流程具有清晰认识的基础上才能写出高质量的WCF程序。第二,WCF是一个极具可扩展性的通信框架,可以灵活地创建一些自定义WCF扩展(WCF Extension)以实现你所需要的功能。如同WCF的插件一样,这些自定义的WCF扩展以即插即用的方式参与到WCF整个消息处理流程之中。了解WCF整个消息处理流程是灵活进行WCF扩展的前提。

在WCF中,定义了一个System.ServiceModel.Channels.Message类,用以表示这些具有不同表现形态的消息。在本篇文章中,我们会着重来讨论这个Message类型。首先来介绍消息的版本。

一、消息版本(Message Version)

由于消息基于不同的格式或者结构,不同的格式决定了对消息不同的处理方式,所以对一个消息进行正确处理的前提是确定消息的格式或结构。在WCF中消息的格式与结构由消息的版本决定,在Message中定义了一个类型为MessageVersion的Version属性来表示消息的版本。

 1: public abstract class Message : IDisposable

 2: { 

 3: //其他成员

 4: public abstract MessageVersion Version { get; }

 5: }

MessageVersion类型定义在System.ServiceModel.Channels命名空间下。由于SOAP规范的版本和WS-Addressing规范的版本是决定消息格式与结构的两个主要因素,所以,MessageVersion由SOAP规范和WS-Addressing规范共同决定。WCF通过System.ServiceModel.EnvelopeVersion和System.ServiceModel.AddressingVersion两个类分别定义SOAP规范的版本和WS-Addressing的版本。

MessageVersion中定义两个静态的方法CreateVersion用以创建相应的MessageVersion对象,两个属性Envelope和Addressing分别表示通过EnvelopeVersion和AddressingVersion体现的SOAP规范版本和WS-Addressing规范版本。

 1: public sealed class MessageVersion

 2: { 

 3: //其他成员

 4: public static MessageVersion CreateVersion(EnvelopeVersion envelopeVersion);

 5: public static MessageVersion CreateVersion(EnvelopeVersion envelopeVersion, AddressingVersion addressingVersion); 

 6:  

 7: public AddressingVersion Addressing { get; }

 8: public EnvelopeVersion Envelope { get; }

 9: }

到目前为止SOAP和WS-Addressing各有两个版本:SOAP 1.1 和SOAP1.2, WS-Addressing 2004和WS-Addressing 1.0。它们分别通过定义在EnvelopeVersion和AddressingVersion中相应的静态只读属性表示。Soap11和Soap12代表SOAP 1.1和SOAP1.2,而WSAddressingAugust2004和WSAddressing10则表示WS-Addressing 2004和WS-Addressing 1.0。EnvelopeVersion.None表示消息并非一个SOAP消息,比如非XML结构的消息(比如基于JSON格式)以及POX(Plain Old XML)消息。AddressingVersion.None则表示消息不遵循WS-Addressing规范,比如通过手工方式解决寻址问题。

 1: public sealed class EnvelopeVersion

 2: { 

 3: //其他成员

 4: public static EnvelopeVersion None { get; }

 5: public static EnvelopeVersion Soap11 { get; }

 6: public static EnvelopeVersion Soap12 { get; }

 7: }

 1: public sealed class AddressingVersion

 2: { 

 3: //其他成员 

 4: public static AddressingVersion None { get; } 

 5: public static AddressingVersion WSAddressing10 { get; }

 6: public static AddressingVersion WSAddressingAugust2004 { get; }

 7: }

注: MessageVersion的静态方法CreateVersion(EnvelopeVersion envelopeVersion)默认采用的AddressingVersion为WSAddressing10。

由于EnvelopeVersion和AddressingVersion共同决定了MessageVesion。所以EnvelopeVersion和AddressingVersion的两两组合就得到相应的MessageVersion。这些通过两者组合得到的MessageVersion通过静态只读属性定义在MessageVersion类中。Soap11WSAddressing10、Soap11WSAddressingAugust2004、Soap12WSAddressing10和Soap12WSAddressingAugust2004的含义都是一目了然的,而None、Soap11和Soap12表示的EnvelopeVersion和Addressing组合分别是:

  • None:EnvelopeVersion.None + AddressingVersion.None;
  • Soap11:EnvelopeVersion.Soap11+ AddressingVersion.None;
  • Soap12:EnvelopeVersion.Soap12 + AddressingVersion.None
 1: public sealed class MessageVersion

 2: { 

 3: //其他成员 

 4: public static MessageVersion Default { get; }

 5:  

 6: public static MessageVersion None { get; }

 7: public static MessageVersion Soap11 { get; }

 8: public static MessageVersion Soap11WSAddressing10 { get; }

 9: public static MessageVersion Soap11WSAddressingAugust2004 { get; }

 10: public static MessageVersion Soap12 { get; }

 11: public static MessageVersion Soap12WSAddressing10 { get; }

 12: public static MessageVersion Soap12WSAddressingAugust2004 { get; }

 13: }

WS-Addressing是建立在SOAP之上的,所以EnvelopeVersion.None和AddressingVersion.WSAddressingAugust2004与AddressingVersion.WSAddressing10的组合是无效的。此外在MessageVersion中还定义了一个静态只读属性Default,表示默认的MessageVersion,目前该值为MessageVersion.Soap12WSAddressing10。

二、如何创建消息

由于Message是一个抽象类型,不能直接实例化。Message类中定义了一系列静态CreateMessage方法,使我们能够方便快捷地以不同的方式进行消息的创建。对于如此众多的CreateMessage方法,按照具体的消息创建方式的不同,大体上可以分为5类:

  • 创建空消息;
  • 将对象序列化成消息的主体(Body);
  • 通过XMLWriter将内容“写”到消息中;
  • 通过XMLReader将内容“读”到消息中;
  • 创建Fault消息。

1、创建空消息

下面是所有CreateMessage静态方法中最简单的一个,包含两个输入参数:消息的版本和Action。通过该方法可以创建一个只包含Action报头的SOAP消息。

 1: public abstract class Message : IDisposable

 2: {

 3: //其他成员

 4: public static Message CreateMessage(MessageVersion version, string action); 

 5: }

为演示消息的创建以及创建后的消息的结构,我写了下面一个辅助方法WriteMessage。该方法将一个Message对象写入一个文件中,并通过开启进程的方式将文件打开。

 1: static void WriteMessage(Message message, string fileName)

 2: {

 3: using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))

 4: {

 5: message.WriteMessage(writer);

 6: }

 7: Process.Start(fileName);

 8: }

通过下面的代码,调用Message的CreateMessage方法,并设置消息版本为MessageVersion.Soap12WSAddressing10,Action设置为http://www.artech.com/myaction。最终将会生成如后面XML片断所示的SOAP消息。

 1: string fileName = @"E:\message.xml";

 2: Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "http://www.artech.com/myaction");

 3: WriteMessage(message, fileName); 

 1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

 2: <s:Header>

 3: <a:Action s:mustUnderstand="1">http://www.artech.com/myaction</a:Action>

 4: </s:Header>

 5: <s:Body />

 6: </s:Envelope> 

由于消息报头(Header)仅仅限于SOAP消息,所以如果将消息的版本改成MessageVersion.None,制定的Action不会被包含在消息中。实际上创建的Message对象不包含任何内容,最终生成的XML文件也不会包含任何文本信息。

 1: string fileName = @"E:\message.xml";

 2: Message message = Message.CreateMessage(MessageVersion.None, "http://www.artech.com/myaction");

 3: WriteMessage(message, fileName); 

2、将对象序列化成消息的主体

现在我们来关注Message的第2个重载的CreateMessage静态方法。如下面代码所示,该方法在上面一个重载方法的基础上加了一个object类型的body参数,表示消息的主体(Body)。在执行该方法的时候,相应的序列化器会被调用,将对象序列化成XML并将其置于消息的主体部分。默认的序列化器就是我们在前面介绍的DataContractSerializer。

 1: public abstract class Message : IDisposable

 2: {

 3: //其他成员

 4: public static Message CreateMessage(MessageVersion version, string action, object body);

 5: }

为了演示对象的序列化,我定义了下面一个数据契约Order,并定义了4个数据成员:OrderNo、OrderDate、Customer和ShipAddress。

 1: [DataContract(Namespace = "http://www.artech.com")]

 2: public class Order

 3: {

 4: [DataMember(Name = "OrderNo", Order = 1)]

 5: public Guid ID

 6: { get; set; }

 7:  

 8: [DataMember(Name = "OrderDate", Order = 2)]

 9: public DateTime Date

 10: { get; set; }

 11:  

 12: [DataMember(Order = 3)]

 13: public string Customer

 14: { get; set; }

 15:  

 16: [DataMember(Order = 4)]

 17: public string ShipAddress

 18: { get; set; }

 19: }

通过下面的代码,创建Order对象,并将其传入CreateMessage方法,作为body参数。最终将会生成如后面所示的SOAP消息。

 1: string fileName = @"E:\message.xml";

 2: Order order = new Order

 3: {

 4: ID = Guid.NewGuid(),

 5: Date = DateTime.Today,

 6: Customer = "Foo",

 7: ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province"

 8: };

 9: Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "http://www.artech.com/myaction", order);

 10: WriteMessage(message, fileName);

 1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

 2: <s:Header>

 3: <a:Action s:mustUnderstand="1">http://www.artech.com/myaction</a:Action>

 4: </s:Header>

 5: <s:Body>

 6: <Order xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.artech.com">

 7: <OrderNo>104a0213-1a0b-4d0b-b084-e912a991f908</OrderNo>

 8: <OrderDate>2008-12-17T00:00:00+08:00</OrderDate>

 9: <Customer>Foo</Customer>

 10: <ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</ShipAddress>

 11: </Order>

 12: </s:Body>

 13: </s:Envelope>

从上面生成的XML,我们可以看出SOAP的主体部分就是Order对象通过DataContractSerializer序列化生成的XML。如果我们的消息不是一个SOAP消息呢?为了演示非SOAP消息的创建,我们将消息的版本替换成MessageVersion.None。从最终产生的XML结构来看,消息的整个部分就是Order对象序列化后的XML。

 1: //其他代码

 2: Message message = Message.CreateMessage(MessageVersion.None, "http://www.artech.com/myaction", order);

 3: WriteMessage(message, fileName);<

抱歉!评论已关闭.