MessageHeaders類型
因為SOAP消息可能包含很多消息頭塊,所以在一個Message類型裡,我們需要 一種表示一組消息頭塊對象的方法。MessageHeaders就是這個作用,並且它定義 了一個MessageHeaders 類型的只讀屬性Headers。Headers屬性是我們在Message 裡增加、修改、查詢和移除MessageHeader的主要方式。在某種意義上,本節主 要是講解MessageHeaders類型,以及可以應用到Message類型的Headers屬性上的 所有信息。與Message相反,在實例化一個Message之後,我們可以隨便修改 Headers屬性的內容。MessageHeaders是一個具體類,而不是抽象類,它不包含 工廠方法。這一點值得注意,因為本章討論過的類型都是抽象的並且定義了工廠 方法。
像前面提到的一樣, MessageHeaders,在一定層次上,是一組 MessageHeader對象。MessageHeader類型的對象模型,奇怪的是少了一個可以返 回MessageHeader對象集合的成員。作為替代,MessageHeaders實現了 IEnumerable<MessageHeaderInfo> and IEnumerable接口。這意味著我們 可以簡單地迭代MessageHeaders類型來查看所有的消息頭塊(在MessageHeaders 對象賦值以後)。
注意
為了完整,我必須提下MessageHeaderInfo類型,它是MessageHeader的基類 。MessageHeaderInfo定義了幾個表示SOAP消息頭塊的屬性,比如:Actor、 MustUnderstand等等。太白地說,我們看不出這個類型存在的理由,因為 MessageHeader是抽象的。
創建一個MessageHeaders對象
MessageHeaders類型定義了三個公開的構造函數。這裡要著重指出的是絕大 多數開發人員都不會直接使用這些構造函數,因為Message類型(子類型)底層 機制會為你調用其中的一個構造函數。如果你要選擇繼承Message類型的話,或 許需要調用其中一個構造函數去設置Message的消息頭部分。
其中一個構造函數接受MessageHeaders類型的參數。構造函數會對 MessageHeaders執行深拷貝,並把它存儲在MessageHeaders實例裡。
另外一個構造函數接受一個MessageVersion類型的參數,如你所料,這是設 置MessageHeaders 實例的SOAP version和WS-Addressing version。最後一個構 造函數接受一個MessageVersion類型和一個Int32類型的參數。這個構造函數設 置SOAP和WS-Addressing的版本,同樣包括內部消息頭塊list裡元素的個數。記 住實際元素的個數可以超過Int32設置的個數。如果我們知道將要增加到 MessageHeaders對象裡的消息頭的個數,使用這個重載方法,它會提升性能,因 為在對象的整個生命周期裡早期的時候,已經設置好了合適的存儲空間。
添加一個MessageHeader
一旦MessageHeaders對象實例化完畢,我們需要給它增加一個或者多個 MessageHeader對象。MessageHeaders類型定義了接受一個MessageHeader 對象 作為參數的Add方法。然後把插入MessageHeader 對象插入到消息頭塊列表的最 末端。
如果我們需要把MessageHeader對象插入到特定的位置,我們可以使用Insert 方法。它接受一個Int32 和MessageHeader類型的參數。Int32 類型的參數表示 要插入的位置,MessageHeader參數是要插入的對象。非常有意思的是 MessageHeaders把MessageHeader 對象存放在一個數組結構中。如果我們傳遞的 索引大於數組的大小,方法會拋出一個ArgumentOutOfRangeException。
獲取MessageHeader的值
當一個程序接收、解碼和反序列化一個stream到Message對象的時候,我們經 常需要獲取一個或者多個消息頭塊的值。因為MessageHeader類型提供了多種方 式,我們必須求助於MessageHeaders類型。
一種方式,我們在MessageHeaders對象裡獲取特定的MessageHeader,就是使 用索引。為了找到特定消息頭塊的索引,我們可以調用兩個FindHeader方法。它 們都接受表示nam和namespace的String參數。其中一個方法接受一個表示能夠與 消息頭塊交互的actor的String參數。它們的返回值都是Int32。如果沒有匹配的 消息頭塊,FindHeader回返回-1。如果找到多個消息頭塊,會返回第一個匹配的 消息頭塊的索引。
備注
我的觀點,這不是一個良好的設計, 它違反了Microsoft文檔裡已經規定的 最佳實踐和關於framework設計的內部標准。它應該命名為為TryFindHeader或者 如果沒有找到匹配的消息頭就應該拋出一個異常。拋開我的看法,當調用 FindHeader方法的時候,我們必須檢查返回的值是否為-1。
在找到消息頭塊的索引以後(只要不是-1),我們隨後就可以檢查消息頭塊 的值。為此,我們調用其中一個GetHeader<T> 方法。重載的方法接受各 種參數,包括一個消息頭塊的索引和一個自定義的序列化器。其中三個重載方法 接受的String參數可以映射到FindHeader方法接受的參數上。內部來看,這些重 載方法調用適當的FindHeader方法,並且檢查返回的值是否是-1.與FindHeader 相反,如果沒有找到匹配的消息頭塊,GetHeader<T>方法會拋出個異常。
復制一個MessageHeaders對象
MessageHeaders類型提供了幾種機制從MessageHeaders 對象去復制一個或者 所有的消息頭塊。為了看看這個功能有何用處,思考一下,當需要根據一個接受 到的Message來創建一個新的Message的時候,需要什麼數據。如果接受的 Message包含一個PurchaseOrderInfo消息頭塊,我們或許需要包含一個復制的消 息頭塊在回復的Message 裡。雖然可以使用相同的值重新創建一個新的消息頭塊 ,但是復制一個存在的消息頭塊到一個新的Message裡會簡單很多。
這兩個CopyHeaderFrom方法提供了復制一個消息頭塊的值到MessageHeaders 實例裡的能力。它們都接受Int32參數作為消息頭塊的索引。並且會把消息頭塊 也放到內部數組的末端,沒有提供指定位置的是方法。其中一個 CopyHeaderFrom方法接受一個Message對象作為參數,而另外一個接受一個 MessageHeaders對象作為參數。內部來看,前者通過Message類型裡的Headers屬 性調用後者。
兩個CopyHeadersFrom方法提供了復制整個MessageHeaders 對象內容的能力 、同樣有一個方法接受一個Message作為參數,另外一個方法接受一個 MessageHeaders參數。源數據消息頭塊被追加到目的消息頭塊的尾部。換句話說 ,這個操作不是一個替換,而是對現有消息頭塊的串聯。這可能有一些意外的結 果,如下所示:
// create a Message創建一個消息
Message message = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction",
"Hello WCF");
// add two new headers to the Message
message.Headers.To = new Uri ("http://wintellect.com/Original");
message.Headers.Add(MessageHeader.CreateHeader("test", "test", "test"));
// create a new Message創建一個新的消息
Message message2 = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"urn:SomeAction2",
"Hello WCF2");
// add two new headers to the Message
message2.Headers.To = new Uri("http://wintellect.com/Test");
message2.Headers.Add(MessageHeader.CreateHeader("test", "test", "test"));
// copy the headers from the first Message into the second one復制消息頭塊
message2.Headers.CopyHeadersFrom(message);
// show the contents
Console.WriteLine(message2.ToString());
When this code executes, the following output is generated:
執行代碼,產生如下結果:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">urn:SomeAction2</a:Action>
<a:To s:mustUnderstand="1">http://wintellect.com/Test</a:To>
<test xmlns="test">test</test>
<a:Action s:mustUnderstand="1">urn:SomeAction</a:Action>
<a:To s:mustUnderstand="1">http://wintellect.com/Original</a:To>
<test xmlns="test">test</test>
</s:Header>
<s:Body>
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
Hello WCF2
</string>
</s:Body>
</s:Envelope>
哎呦!這個Message顯然不對。事實上,CopyHeaderFrom方法遇到相同的問題 (重復的消息頭塊)。換句話說,復制消息頭是件頭疼的事情,開發人員需要檢 查目標Message裡是否有重復的消息頭塊、
序列化一個MessageHeaders對象
MessageHeaders類型定義了幾個序列化MessageHeaders對象部分或者整體的 方法。與Message和MessageHeader類型一樣,MessageHeaders 類型上的序列化 方法也是以單詞Write開頭。最簡單的方法是WriteHeader方法。如其名字暗示的 一樣,這個方法就是序列化一個消息頭塊。它接受一個Int32 和一個 XmlDictionaryWriter作為參數。Int32表示消息頭塊的索引, XmlDictionaryWriter,正如你猜測的一樣,執行實際的序列化和編碼工作。 WriteHeader方法調用其它的MessageHeaders的序列化方法:WriteStartHeader 和WriteHeaderContents。WriteStartHeader方法,也和其名字暗示的一樣,序 列化開發的消息頭塊,而WriteHeaderContents方法序列化消息頭塊的內容。
沒有一步序列化全部MessageHeaders內容的機制。唯一的方法就是迭代所有 的消息頭塊,並序列化每一個消息頭塊。實際上,我們很少會有這樣的需求,就 是在序列化一個Message的上下文環境外,去序列化消息頭塊。因此,Message類 型定義了序列化真個內容的WriteMessage方法。此方法在Message類型上的實現 ,會迭代並序列化每一個消息頭塊。