深度剖析消息编码(Encoding)实现

一、消息编码器(MessageEncoder)

消息编码器通过类型MessageEncoder表示,MessageEncoder是定义在System.ServiceModel.Channels命名空间下的一个抽象类。从下面的定义中可以看出,MessageEncoder主要包含两种类型的操作:读消息和写消息,分别通过ReaderMessage和WriteMessage方法实现。此外,两个额外的方法,GetProperty用于获取MessageEncoder相关的一些属性,IsContentTypeSupported用于判断MessageEncoder是否支持某种类型的MIME类型。

   1: public abstract class MessageEncoder
   2: {
   3:     //其他成员
   4:     public virtual T GetProperty() where T : class;
   5:     public virtual bool IsContentTypeSupported(string contentType);
   6:  
   7:     public Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager);
   8:     public Message ReadMessage(Stream stream, int maxSizeOfHeaders);
   9:     public abstract Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType);
  10:     public abstract Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType);
  11:  
  12:     public abstract void WriteMessage(Message message, Stream stream);
  13:     public ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager);
  14:     public abstract ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset);
  15:  
  16:     public abstract string ContentType { get; }
  17:     public abstract string MediaType { get; }
  18:     public abstract MessageVersion MessageVersion { get; }
  19: }

与上面介绍的3种类型的XmlDictionaryWriter/XmlDictionaryReader相对应,WCF同样定义了MessageEncoder:TextMessageEncoder、BinaryMessageEncoder和MtomMessageEncoder三种MessageEncoder,它们分别封装了XmlUTF8TextWriter/XmlUTF8TextReader、XmlBinaryWriter/XmlBinaryReader和XmlMtomWriter/XmlMtomReader。WCF定义了3个相应的工厂类:TextMessageEncoderFactory、BinaryMessageEncoderFactory和MtomMessageEncoderFactory用于创建相应的MessageEncoder。它们共同继承一个抽象类:System.ServiceModel.Channels.MessageEncoderFactory。通过只读属性Encoder得到相应的MessageEncoder。

   1: public abstract class MessageEncoderFactory
   2: {
   3:     //其他成员
   4:     public abstract MessageEncoder Encoder { get; }
   5: }

二、 实例演示通过MessageCoder对消息进行编码

接下来,我们来演示一个实例:如何通过MessageCoder对一个具体的Message对象进行编码。本例主要演示TextMessageCoder和MtomMessageEncoder编码方式的对比。此外,为了演示MTOM对二进制数据的编码优化,我们创建一个基于二进制内容的Message对象,并将一个位图作为消息的主体。

我们先创建如下一个静态辅助方法WriteMessage,该方法通过MessageEncoderFactory得到的MessageEncoder对象将Message对象写入一个文件中。

   1: static void WriteMessage(MessageEncoderFactory encoderFactory, Message message, string fileName)
   2: {
   3:     using (FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Write))
   4:     {
   5:         encoderFactory.Encoder.WriteMessage(message, stream);
   6:     }
   7: }

如果调用上面的方法,首先需要创建MessageEncoderFactory对象。由于TextMessageEncoderFactory和MtomMessageEncoderFactory是一个内部类型,不能直接实例化,所以只能通过反射的机制创建两个MessageEncoder。下面是TextMessageEncoder和MtomMessageEncoderFactory构造函数的定义。

   1: internal class TextMessageEncoderFactory : MessageEncoderFactory
   2: {
   3:     //其他成员
   4:     public TextMessageEncoderFactory(MessageVersion version, Encoding writeEncoding, int maxReadPoolSize, int maxWritePoolSize, XmlDictionaryReaderQuotas quotas);
   5: }
   6: internal class MtomMessageEncoderFactory : MessageEncoderFactory
   7: {
   8:     //其他成员
   9:     public MtomMessageEncoderFactory(MessageVersion version, Encoding writeEncoding, int maxReadPoolSize, int maxWritePoolSize, int maxBufferSize, XmlDictionaryReaderQuotas quotas);
  10: }

在下面的代码中,先通过Message的静态方法CreateMessage创建Message对象,需要注意的第3个参数是一个表示位图的Bitmap对象。然后通过反射创建TextMessageEncoderFactory和MtomMessageEncoderFactory对象,并调用上面定义的辅助方法WriteMessage。

   1: Message message = Message.CreateMessage(MessageVersion.Default, "http://www.artech.com/myaction", new Bitmap(@"C:\Users\Jinnan\Pictures\photo.jpg"));
   2: MessageBuffer buffer = message.CreateBufferedCopy(int.MaxValue);
   3:  
   4: //通过反射创建TextMessageEncoderFactory
   5: string encoderFactoryType = "System.ServiceModel.Channels.TextMessageEncoderFactory,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
   6: MessageEncoderFactory encoderFactory = (MessageEncoderFactory)Activator.CreateInstance(Type.GetType(encoderFactoryType), MessageVersion.Default, Encoding.UTF8, int.MaxValue, int.MaxValue, new XmlDictionaryReaderQuotas());
   7:  
   8: WriteMessage(encoderFactory, buffer.CreateMessage(), @"E:\message.text.xml"); 
   9:  
  10: //通过反射创建MtomMessageEncoderFactory
  11: encoderFactoryType = "System.ServiceModel.Channels.MtomMessageEncoderFactory,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
  12: encoderFactory = (MessageEncoderFactory)Activator.CreateInstance(Type.GetType(encoderFactoryType), MessageVersion.Default, Encoding.UTF8, int.MaxValue, int.MaxValue, int.MaxValue, new XmlDictionaryReaderQuotas());
  13:  
  14: WriteMessage(encoderFactory, buffer.CreateMessage(), @"E:\message.mtom.xml");

下面给出的两段文字分别是通过TextMessageEncoder和MtomMessageEncoder对相同的Message对象进行编码后的结果。从中我们可以清晰地看出,TextMessageEncoder将位图进行Base64编码,编码后的内容以内联(Inline)的方式包含在SOAP主体中。而MtomMessageEncoder会生成一个MIME Multipart/Related XOP Package,SOAP封套作为其主体。编码后的字节和SOAP封套是分离的,SOAP的主体部分并不包含位图的内容,仅仅是通过Context-ID对分离的内容进行引用。

//www.w3.org/2003/05/soap-envelope" 
xmlns:a="http://www.w3.org/2005/08/addressing">http://www.artech.com/myaction/9j/4AAQSkZJRgABAQAAAQABAAD/(...省略...)ZIz7V3gVcR/KPu+lUNWVfs3Qf6309jTkk47DW5/9k=
Content-Type: multipart/related;type="application/xop+xml";boundary="06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1";start="//tempuri.org/0/633655837835941838>";start-info="application/soap+xml"
 
--06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1
Content-ID: //tempuri.org/0/633655837835941838>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="application/soap+xml"
 
//www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">http://www.artech.com/myaction
--06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1
Content-ID: //tempuri.org/1/633655837836161838>
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream
 
[省略不可读的编码内容]
--06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1--

三、WCF体系下的编码机制实现

最后我们来介绍WCF体系下是如何对消息进行编码的。在客户端,以方法调用形式体现的服务访问通过ClientMessageFormatter生成请求消息。该请求消息最终通过绑定对象从服务模型层转到信道层。我们说绑定是绑定元素的有序组合,对于所有类型的绑定来说,有两个绑定类型是必不可少的:MessageEncodingBindingElement和TransportBindingElement。而消息的编码由这两个绑定元素共同完成。

上面我们介绍了3种编码方式:Text、Binary和MTOM;对应3种不同的XmlDictionaryWriter/XmlDictionaryReader:XmlUTF8TextWriter/ XmlUTF8TextReader、XmlBinaryWriter/XmlBinaryReader和XmlMtomWriter/XmlMtomReader;3种XmlDictionaryWriter/XmlDictionaryReader又对应着3种MessageEncoder:TextMessageEncoder、BinaryMessagEncoder和MtomMessageEncoder;这3种不同的MessageEncoder又具有它们各自的MessageEncoderFactory:TextMessageEncoderFactory、BinaryMessagEncoderFactory和MtomMessageEncoderFactory。最终这3种MessageEncoderFactory被3种相应的MessageEncodingBindingElement用于进行具体的编码。MessageEncodingBindingElement通过CreateMessageEncoderFactory得到相应的MessageEncoderFactory。

public abstract class MessageEncodingBindingElement : BindingElement
{
    //其他成员
    public abstract MessageEncoderFactory CreateMessageEncoderFactory();
    public override T GetProperty(BindingContext context) where T: class;
    public abstract MessageVersion MessageVersion { get; set; }   
}

对应着3种不同的MessageEncoderFactory,WCF定义了3种不同的MessageEncodingBindingElement,它们分别是:TextMessageEncodingBindingElement、BinaryMessageEncodingBindingElement和MtomMessageEncodingBindingElement。

在介绍绑定的时候,我们说BindingElement创建相应的ChannelFactory/ChannelListener,而ChannelFactory/ChannelListener最终创建相应的Channel进行消息的处理。这种说法是不准确的,并不是所有的BindingElement都会创建Channel,实际上没有用于专门编码的Channel,具体的编码工作是TransportChannel完成的。图1揭示了WCF进行消息编码的本质。

clip_image002

图1 WCF体系下消息编码的实现

当通过绑定对象创建信道栈的时候,MessageEncodingBindingElement的BuildChannelFactory/BuildChannelListener方法首先被调用,MessageEncodingBindingElement会创建相应的MessageEncoderFactory对象,将其置于当前的BindingContext中。然后TransportBindingElement的BuildChannelFactory/BuildChannelListener方法被调用,并创建TransportChannelFactory/TransportChannelListener对象,TransportChannelListener和TransportChannelFactory创建TransportChannel用于请求监听和消息发送,与此同时TransportChannel会将MessageEncoderFactory从BindingContext获取下来用于消息的解码和编码。

请使用浏览器的分享功能分享到微信等