[原创]WCF技术剖析之十七:消息(Message)详解(中篇)

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]在上篇中大体上围绕着Message的两个话题进行讲述:消息版本(Message Version)和采用五种不同的方式创建Message。本篇文章将会详细介绍Message的另外两个主题:和消息的基本操作,比如读、写、拷贝、关闭等,以及消息状态机(Message State Machine)。

知道了消息是如何创建的,我们接着讨论消息的一些基本的操作。除了上面介绍的消息创建之外,一个消息涉及到的操作大体分为以下4类:

  • 读消息:读取整个消息的内容或者有选择地读取报头或者主体部分内容;
  • 写消息:将整个消息的内容或者主体部分内容写入文件或者流;
  • 拷贝消息:通过消息拷贝生成另一个具有相同内容的新消息;
  • 关闭消息:关闭消息,回收一些非托管资源。

上述的这些消息的基本操作都和消息的状态密切相关,消息操作和消息状态之间的关系体现在以下两个方面:

  • 消息的状态决定了可以采取的操作;
  • 消息操作伴随着消息状态的改变。

一、消息状态机(Message State Machine)

图1展示的是基于消息的状态机,从图中我们可以得出下面的一些关于Message对象状态转换的规则:

  • 消息的读、写和拷贝操作只能作用于状态为Created的消息上;
  • 消息的读、写和拷贝将消息状态从Created转换成Read、Written和Copied。
  • 所有状态的消息都可以直接关闭,关闭后消息的状态转换为Closed。

clip_image002

图1 Message对象状态机

 

在WCF中,消息的状态通过System.ServiceModel.Channels.MessageState枚举表示,MessageState定义了5种消息状态,与上图所示的5种状态一一对应。MessageState的定义如下:

   1: public enum MessageState
   2: {
   3:     Created,
   4:     Read,
   5:     Written,
   6:     Copied,
   7:     Closed
   8: }

二、消息的读取

读取消息主体部分的内容是最为常见的操作。如果主体部分的内容对应一个可以序列化的对象,可以通过GetBody<T>方法读取消息主体并反序列化生成相应的对象。而通过GetReaderAtBodyContents得到一个XmlDictionaryReader对象,通过这个对象可以进一步提取消息主体部分的内容。

   1: public abstract class Message : IDisposable
   2: {
   3:     //其他成员
   4:     public T GetBody<T>();
   5:     public T GetBody<T>(XmlObjectSerializer serializer);
   6:     public XmlDictionaryReader GetReaderAtBodyContents();    
   7: }

我们演示一下GetBody<T>方法的例子。假设消息主体部分对应的类型为下面所示的Customer类,这是一个数据契约。

   1: [DataContract(Namespace = "http://www.artech.com")]
   2: public class Customer
   3: {
   4:     [DataMember]
   5:     public string Name
   6:     { get; set; }
   7:  
   8:     [DataMember]
   9:     public string Compnay
  10:     { get; set; }
  11:  
  12:     [DataMember]
  13:     public string Address
  14:     { get; set; }
  15:  
  16:     public override bool Equals(object obj)
  17:     {
  18:         Customer customer = obj as Customer;
  19:         if (customer == null)
  20:         {
  21:             return false;
  22:         }
  23:         return this.Name == customer.Name && this.Compnay == customer.Compnay && this.Address == customer.Address;
  24:     }
  25: }

在下面的程序中,通过Customer对象创建Message对象,调用GetBody<Customer>方法读取主体部分的内容并反序列化成Customer对象。可以想象开始创建的Customer对象和通过GetBody<Customer>方法得到的Customer对象的是得相等的,输出的结果证明了这一点。

   1: Customer customer = new Customer
   2: {
   3:     Name     = "Foo",
   4:     Compnay     = "NCS",
   5:     Address     = "#328, Airport Rd, Industrial Park, Suzhu Jiangsu Province"
   6: };
   7: Message message = Message.CreateMessage(MessageVersion.Default, "http://www.artech.com/myaction", customer);
   8: Customer cusomterToRead = message.GetBody<Customer>();
   9: Console.WriteLine("customer.Equals(cusomterToRead) = {0}", customer.Equals(cusomterToRead));

输出结果:

   1: customer.Equals(cusomterToRead) = True

按照我们上面介绍的消息状态机所描述的,只有状态为Created的消息才能执行读取操作,否则会抛出异常。无论是执行了GetBody<T>方法还是GetReaderAtBodyContents方法,Message对象的状态都将转换为Read。在上面代码的基础上,添加了两行额外的代码输出消息的状态,并再一次调用Message对象的GetBody<T>方法。程序运行输出消息的状态(message.State = Read),正执行到第2个GetBody<T>方法时,抛出如图2所示的InvalidOperationException异常。

   1: //省略代码
   2: Message message = Message.CreateMessage(MessageVersion.Default, "http://www.artech.com/myaction", customer);
   3: Customer cusomterToRead = message.GetBody<Customer>();
   4: Console.WriteLine("message.State = {0}", message.State);
   5: cusomterToRead = message.GetBody<Customer>();
   6: Console.WriteLine("customer.Equals(cusomterToRead) = {0}", customer.Equals(cusomterToRead));

输出结果:

   1: message.State = Read

clip_image004

图2 重复读取消息导致的异常

三、消息的写入

在Message类中,定义了一系列WriterXxx方法用于消息的写操作。通过这些方法,我们可以将整个消息或者是消息的主体部分内容写入XmlWriter或者XmlDictioanryWriter中,最终写入文件或者流。

   1: public abstract class Message : IDisposable
   2: {    
   3:     //其他成员
   4:     public void WriteBody(XmlDictionaryWriter writer);
   5:     public void WriteBody(XmlWriter writer);
   6:     public void WriteBodyContents(XmlDictionaryWriter writer);
   7:     public void WriteMessage(XmlDictionaryWriter writer);
   8:     public void WriteMessage(XmlWriter writer);
   9:     public void WriteStartBody(XmlDictionaryWriter writer);
  10:     public void WriteStartBody(XmlWriter writer);
  11:     public void WriteStartEnvelope(XmlDictionaryWriter writer);
  12: }

我们在前面作演示时创建的辅助方法WriteMessage(如下面的代码所示),就是通过调用WriteMessage方法将消息的内容写入一个指定的XML文件中的。同消息的读取一样,写操作只能作用于状态为Created的消息。成功执行了消息写入操作后,状态转换为Written。

   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: }

四、消息的拷贝

通过前面的介绍和演示,相信读者对消息的状态转换已有一个清晰的认识:消息的读写都会改变消息的状态,而读写操作只能作用于状态为Created的消息。由此就出现了这样一个问题:在真正的WCF应用中,我们往往需要将消息进行日志记录。如果按照正常的方式进行消息的读取和写入,会导致状态的改变,如果消息传递到WCF的处理管道,作用于该消息对象的读、写操作都将失败。在这种情况下,我们需要使用到消息的拷贝功能。Message类中定义了一个CreateBufferedCopy方法,专门用于消息的拷贝。

   1: public abstract class Message : IDisposable
   2: {
   3:     //其他成员
   4:     public MessageBuffer CreateBufferedCopy(int maxBufferSize);
   5: }

CreateBufferedCopy方法的返回结果并不是我们想象的Message对象,而是一个System.ServiceModel.Channels.MessageBuffer对象,MessageBuffer表示消息在内存中缓存。当CreateBufferedCopy成功执行后,消息的状态转换成Copied,很显然后续的操作不能再使用该消息。但是却可以通过MessageBuffer对象创建一个新的Message对象,该对象具有与原来一样的内容,但是状态却是Created。在MessageBuffer中,定义了如下一个CreateMessage方法,用于新消息的创建。

   1: public abstract class MessageBuffer : IXPathNavigable, IDisposable
   2: {
   3:     //其他成员
   4:     public abstract Message CreateMessage();
   5: }

比如,我们通过下面的方式解决前面所演示的重复读取的问题,将不会在有InvalidOperatioException异常抛出。

   1: Message message = Message.CreateMessage(MessageVersion.Default, "http://www.artech.com/myaction", customer);
   2: MessageBuffer messageBuffer = message.CreateBufferedCopy(int.MaxValue);
   3: message = messageBuffer.CreateMessage(); 
   4: Customer cusomterToRead = message.GetBody<Customer>();
   5: message = messageBuffer.CreateMessage();
   6: cusomterToRead = message.GetBody<Customer>();
   7: Console.WriteLine("customer.Equals(cusomterToRead) = {0}", 

注:部分内容节选自《WCF技术剖析(卷1)》第六章: 消息、消息契约与消息编码

WCF技术剖析系列:

WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构
WCF技术剖析之二:再谈IIS与ASP.NET管道
WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿
WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘
WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务
WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效
WCF技术剖析之七:如何实现WCF与EnterLib PIAB、Unity之间的集成
WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制
WCF技术剖析之九:服务代理不能得到及时关闭会有什么后果?
WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

WCF技术剖析之十一:异步操作在WCF中的应用(上篇)
WCF技术剖析之十一:异步操作在WCF中的应用(下篇)
WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)
WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
WCF技术剖析之十四:泛型数据契约和集合数据契约(上篇)
WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)
WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
WCF技术剖析之十六:数据契约的等效性和版本控制


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值