程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之十七:消息(Message)詳解(中篇)

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

編輯:關於.NET

在上篇中大體上圍繞著Message的兩個話題進行講述:消息版本(Message Version)和采用五種不同的方式創建Message。本篇文章將會詳細介紹Message的另外兩個主題:和消息的基本操作,比如讀、寫、拷貝、關閉等,以及消息狀態機(Message State Machine)。

知道了消息是如何創建的,我們接著討論消息的一些基本的操作。除了上面介紹的消息創建之外,一個消息涉及到的操作大體分為以下4類:

讀消息:讀取整個消息的內容或者有選擇地讀取報頭或者主體部分內容;

寫消息:將整個消息的內容或者主體部分內容寫入文件或者流;

拷貝消息:通過消息拷貝生成另一個具有相同內容的新消息;

關閉消息:關閉消息,回收一些非托管資源。

上述的這些消息的基本操作都和消息的狀態密切相關,消息操作和消息狀態之間的關系體現在以下兩個方面:

消息的狀態決定了可以采取的操作;

消息操作伴隨著消息狀態的改變。

一、消息狀態機(Message State Machine)

圖1展示的是基於消息的狀態機,從圖中我們可以得出下面的一些關於Message對象狀態轉換的規則:

消息的讀、寫和拷貝操作只能作用於狀態為Created的消息上;

消息的讀、寫和拷貝將消息狀態從Created轉換成Read、Written和Copied。

所有狀態的消息都可以直接關閉,關閉後消息的狀態轉換為Closed。

圖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

圖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}",
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved