閱讀930 返回首頁    go 微軟 go Office


WCF技術剖析之十七:消息(Message)詳解(中篇)

上篇中大體上圍繞著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 = "https://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, "https://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, "https://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, "https://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}", 

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

最後更新:2017-10-30 12:04:09

  上一篇:go  WCF技術剖析之十七:消息(Message)詳解(上篇)
  下一篇:go  我同桌搶到X了 你搶到了麼?