閱讀338 返回首頁    go 汽車大全


WCF技術剖析之十九:深度剖析消息編碼(Encoding)實現(下篇)

通過上篇的介紹,我們知道了WCF所有與編碼與解碼相關的功能都實現在相應的System.Xml.XmlDictionaryWriterSystem.Xml.XmlDictionaryReader中。但是在真正的WCF處理框架中,卻並不直接使用XmlDictioanryWriter和XmlDictionaryReader對象,而通過相應的消息編碼器(System.ServiceModel.Channels.MessageEncoder)對其進行進一步封裝,專門用於消息的編碼和解碼。

一、消息編碼器(MessageEncoder)

消息編碼器通過類型MessageEncoder表示,MessageEncoder是定義在System.ServiceModel.Channels命名空間下的一個抽象類。從下麵的定義中可以看出,MessageEncoder主要包含兩種類型的操作:讀消息和寫消息,分別通過ReaderMessage和WriteMessage方法實現。此外,兩個額外的方法,GetProperty<T>用於獲取MessageEncoder相關的一些屬性,IsContentTypeSupported用於判斷MessageEncoder是否支持某種類型的MIME類型。

   1: public abstract class MessageEncoder
   2: {
   3:     //其他成員
   4:     public virtual T GetProperty<T>() 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, "https://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對分離的內容進行引用。

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">https://www.artech.com/myaction</a:Action></s:Header><s:Body><Bitmap xmlns="https://schemas.datacontract.org/2004/07/System.Drawing" xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:x="https://www.w3.org/2001/XMLSchema"><Data i:type="x:base64Binary" xmlns="">/9j/4AAQSkZJRgABAQAAAQABAAD/(...省略...)ZIz7V3gVcR/KPu+lUNWVfs3Qf6309jTkk47DW5/9k=</Data></Bitmap></s:Body></s:Envelope>
Content-Type: multipart/related;type="application/xop+xml";boundary="06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1";start="<http://tempuri.org/0/633655837835941838>";start-info="application/soap+xml"
 
--06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1
Content-ID: <http://tempuri.org/0/633655837835941838>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="application/soap+xml"
 
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="https://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">https://www.artech.com/myaction</a:Action></s:Header><s:Body><Bitmap xmlns="https://schemas.datacontract.org/2004/07/System.Drawing" xmlns:i="https://www.w3.org/2001/XMLSchema-instance" xmlns:x="https://www.w3.org/2001/XMLSchema"><Data i:type="x:base64Binary" xmlns=""><xop:Include href="cid:http%3A%2F%2Ftempuri.org%2F1%2F633655837836161838" xmlns:xop="https://www.w3.org/2004/08/xop/include"/></Data></Bitmap></s:Body></s:Envelope>
--06a0ac15-70c6-47e9-8837-ebc04a9ac1c2+id=1
Content-ID: <http://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<T>(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獲取下來用於消息的解碼和編碼。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 11:35:03

  上一篇:go  WCF技術剖析之十九:深度剖析消息編碼(Encoding)實現(上篇)
  下一篇:go  WCF技術剖析之二十: 服務在WCF體係中是如何被描述的?