程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 《WCF技術內幕》24:第2部分_第5章_消息:創建一個消息(上)

《WCF技術內幕》24:第2部分_第5章_消息:創建一個消息(上)

編輯:關於.NET

創建一個消息

可以選擇眾多定義的CreateMessage工廠方法中的一個來創建Message對象。 絕大部分,這些方法接受的都是SOAP消息體的內容作為參數。非常重要的一點是 Message的body在創建以後就不能再做修改。SOAP消息頭塊,話句話說,在消息 創建以後還可以增加和修改。一般地說,Message類型的工廠方法可以根據處理 消息的路線圖來劃分的,從XmlReader提取數據的方法,把數據放進Message的方 法,和產生表示SOAP Fault Message的方法。在我們研究各個不同分類的工廠方 法之前,先看一些Message序列化和反序列化的上下文環境。

簡要介紹Message序列化和反序列化

序列化和反序列化在分布式計算裡很常見,因此,當它們牽扯到消息應用系 統的時候,就有必要弄清楚他們的意義。讓我們考慮一下基本的序列化和反序列 化Message對象的步驟。放發送程序需要發送一個Message到另外一個消息參與者 的時候,它必須創建一個包含適當信息的Message 對象,然後序列化和編碼 Message到一個Stream或Byte裡,最後發送Stream或Byte到目的地。當接收程序 收到Message的時候,一般來說,它還是Stream或Byte狀態(和發送者最後處理 的數據狀態一樣)。接收程序必須解碼和反序列化Stream或Byte[]為 Message對 象,或許還要反序列化消息頭部或者消息體為其它對象。

正如你看到的,序列化通常是與發送者規定的任務相關的,反序列化通常和 接收者的任務相關。發送者和接收者都要創建Message對象,但是發送者是內存 裡的其它對象創建Message對象,而接受者是通過解碼和反序列Stream 或 Byte 來創建一個Message對象。一旦接收程序解碼和反序列Stream 或 Byte為一個 Message對象,它就可以轉換消息的內容為一個對象,或者經過多次的對象轉換 。這個轉換,一般來說,也是另外一種形式的反序列化。

Message版本

因為Message對象是CLR對SOAP消息的抽象,而使用的SOAP消息有多種版本, 所以就需要考慮Message對象的表示的SOAP消息版本問題。

在Message對象模型裡,SOAP消息的版本一旦在創建時設定,就無法改變。 SOAP和WS-*規范在不斷的變化,因此,隨著時間推移,我們應該希望這些文檔版 本更新。隨著它們的改變,namespace 和消息結構變化也在情理之中。為了適應 這些必然發生的變化,WCF提供了幾個封裝SOAP規范和WS-*規范XML消息語義的類 型。這些類型的實例會傳遞給工廠方法來表示Message對象的版本,Message上定 義大部分工廠方法都接受這些類型的參數。

當設置Message對象時,System.ServiceModel.Channels.EnvelopeVersion類 型代表了一個Message類型遵循的SOAP規范。同時,System.ServiceModel. Channels.AddressingVersion表示Message序列化時,消息頭塊遵循的WS- Addressing規范。在WCF一次發布的時候,有2個SOAP老徐備注2規范(1.1 和1.2 ),WS-Addressing規范(2004年8月,1.0)。

System.ServiceModel.Channels.MessageVersion包裝了EnvelopeVersion 和 AddressingVersion類型。MessageVersion有幾個static屬性,它們表示 EnvelopeVersion和 AddressingVersion的幾種可能的組合。下面代碼顯示了 MessageVersion的所有公開可見的成員:

namespace System.ServiceModel.Channels {
  public sealed class MessageVersion {
     public AddressingVersion Addressing { get; }
     public static MessageVersion Default { get; }
     public EnvelopeVersion Envelope { get; }
     public static MessageVersion Soap11 { get; }
     public static MessageVersion Soap12 { get; }
     public static MessageVersion None { get; }
     public static MessageVersion Soap11WSAddressing10 {  get; }
     public static MessageVersion  Soap11WSAddressingAugust2004 { get; }
     public static MessageVersion Soap12WSAddressing10 {  get; }
     public static MessageVersion  Soap12WSAddressingAugust2004 { get; }
     public static MessageVersion CreateVersion(
       EnvelopeVersion envelopeVersion);
     public static MessageVersion CreateVersion(
       EnvelopeVersion envelopeVersion,
       AddressingVersion addressingVersion);
     public override bool Equals(object obj);
     public override string ToString();
  }
}

大部分成員都是可以自描述的;一些需要解釋。在WCF版本3裡, MessageVersion.Default返回了等價的MessageVersion.Soap12WSAddressing10 的static屬性。正如你從名字裡看的,這個屬性表示它遵守SOAP 1.2 和WS- Addressing 1.0規范。MessageVersion.Soap11 和MessageVersion.Soap12屬性 給AddressingVersion賦值為AddressingVersion.None,並且根據屬性名字給 EnvelopeVersion賦值。當你想創建一個發送SOAP消息,但是不想實現WS- Addressing規范的程序時,這個非常有用。和你想的一樣, MessageVersion.None對於(樸樹的舊的XML)POX消息場景非常有用。

重點

當任何一個規范的發展(不可避免的),在隨後的WCF版本裡,使用 MessageVersion.Default構建Message 的代碼也會悄悄地變化。這樣也好也壞, 取決於消息場景,例如,修改WCF的版本或許也要修改MessageVersion.Default 屬性產生的 XML相關構件。如果修改真的發生,所有使用新的消息交互的消息參 與者都必須知道新的消息語義。換句話說,一個使用 MessageVersion.Default 屬性的程序或許通過升級WCF帶來巨大的變化,因此,MessageVersion.Default 屬性應該謹慎使用。

下面代碼展示了MessageVersion 老徐備注1類型引用的SOAP和WS-Addressing 版本:

using System;
using System.ServiceModel.Channels;
using System.Xml;

class Program {
  static void Main(string[] args){
     MessageVersion version = MessageVersion.Default;
     PrintMessageVersion("Default",version);

     version = MessageVersion.Soap11WSAddressing10;
     PrintMessageVersion ("Soap11WSAddressing10",version);

    version = MessageVersion.Soap11WSAddressingAugust2004;
     PrintMessageVersion ("Soap11WSAddressingAugust2004",version);

     version = MessageVersion.Soap12WSAddressing10;
     PrintMessageVersion ("Soap12WSAddressing10",version);

     version =  MessageVersion.Soap12WSAddressingAugust2004;
     PrintMessageVersion("Soap12WSAddressingAugust2004",  version);
  }
  private static void PrintMessageVersion(String name,
                                            MessageVersion version) {
     Console.WriteLine("Name={0}\nEnvelope={1}\nAddressing={2} \n",
                        name,
                        version.Envelope.ToString(),
                        version.Addressing.ToString());
  }
}

運行代碼產生以下輸出:

Name=Default
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)

Name=Soap11WSAddressing10
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)

Name=Soap11WSAddressingAugust2004
Envelope=Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
Addressing=Addressing200408  (http://schemas.xmlsoap.org/ws/2004/08/addressing)

Name=Soap12WSAddressing10
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing10 (http://www.w3.org/2005/08/addressing)

Name=Soap12WSAddressingAugust2004
Envelope=Soap12 (http://www.w3.org/2003/05/soap-envelope)
Addressing=Addressing200408
  (http://schemas.xmlsoap.org/ws/2004/08/addressing)

序列化對象

幾個CreateMessage方法是設計來序列化一個對象到Message消息體裡的。為 了這個目的,這些方法要接受System.Object類型的參數。其中一個方法使用WCF 默認的序列化器,並且另外一個接受自定義序列化器。(我們會在第9章:契約 ,裡詳細討論)此外,除了這些參數,這些方法還接受一個String類型的參數。 這個參數,在相關的Message對象的頭塊裡設置WS-Addressing Action的值。如 下所示,這些工廠方法相當簡單:

// pass a String into the factory method
Message msg = Message.CreateMessage (MessageVersion.Soap12WSAddressing10,
                                      "urn:SomeAction",
                                      "Hello There");
// the ToString() method returns the entire Message
Console.WriteLine(msg.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:SomeAction</a:Action>
  </s:Header>
  <s:Body>
     <string  xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
       Hello There
     </string>
  </s:Body>
</s:Envelope>

如你在前面的例子看到的,String “Hello There”是自動賦值到Message的 消息體上。

讓我們修改Object從String參數到PurchaseOrder,如下所示,看看會發生什 麼:

sealed class MyApp {
  static void Main() {
     VendorInfo vInfo = new VendorInfo(5, "Contoso");
     PurchaseOrder po = new PurchaseOrder(50, vInfo);
     Message msg = Message.CreateMessage(
       MessageVersion.Soap12WSAddressing10,
       "urn:SomeAction",
       po);
     // the ToString() method returns the entire Message  。ToString()方法返回整個Message
     Console.WriteLine(msg.ToString());
  }

  private sealed class PurchaseOrder {
     Int32 poNumber;
     VendorInfo vendorInfo;

     internal PurchaseOrder(Int32 poNumber, VendorInfo  vendorInfo) {
       this.poNumber = poNumber;
       this.vendorInfo = vendorInfo;
     }
  }
  private sealed class VendorInfo {
     Int32 vendorNumber;
     String vendorName;

     internal VendorInfo(Int32 vendorNumber, String  vendorName) {
       this.vendorNumber = vendorNumber;
       this.vendorName = vendorName;
     }
  }
}

當代碼運行的時候,CreateMessage使用的默認的序列化器會拋出一個 InvalidDataContractException異常。我們會在第9 章裡詳細介紹數據契約和序 列化問題。如果我們想給這些方法傳遞一個對象參數,它必須可以序列化,並且 所有引用的對象也必須是可序列化的。第一個例子是 String類型的參數傳遞給 了CreateMessage方法成功執行,因為C#的基元類型都是可以序列化的。還有很 多隱式序列化的類型,也有幾個顯式指定類型序列化的方式。你也會在第9章裡 詳細了解到這些內容。現在,我們來給PurchaseOrder和VendorInfo類型標記 SerializableAttribute屬性,這樣可以使得他們支持序列化。如果我們使用可 序列化類型運行前面的代碼,我們會看到下面的輸出:

<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:SomeAction</a:Action>
  </s:Header>
  <s:Body>
     <MyApp.PurchaseOrder
       xmlns:i="http://www.w3.org/2001/XMLSchema-instance"  xmlns=
"http://schemas.datacontract.org/2004/07/CreatingMessageBySerializatio n">
       <poNumber>50</poNumber>
       <vendorInfo>
         <vendorName>Contoso</vendorName>
         <vendorNumber>5</vendorNumber>
       </vendorInfo>
     </MyApp.PurchaseOrder>
  </s:Body>
</s:Envelope>

注意到一個PurchaseOrder對象(一個VendorInfo對象)序列化到SOAP消息體 裡。

從Reader提取數據

幾個CreateMessage方法接受一個XmlReader或XmlDictionaryReader作為參數 。這些方法“pull” XmlDictionaryReader的整個內容到返回的Message對象裡 ,或者到Message的body裡(消息體)。著重指出的是CreateMessage方法接受一 個XmlReader作為參數,通過調用XmlDictionaryReader 的 CreateDictionaryReade工廠方法來創建一個XmlDictionaryReader對象。

這些方法非常有用,當你要把Message反序列化到一個Byte[] 或者Stream裡 的時,這是特別常見的,尤其是當接收程序接受一個包含序列化和編碼過的數據 的Stream時。當你使用這些方法構建一個Message對象,你必須知道Byte[]或 Stream是包含了全部消息還是僅僅是消息體。為了適應2種情況,CreateMessag 方法被重載為多個方法,他們包含了能讀取整個消息和只讀取消息體的參數。

為了說明問題,下面的文件包含了已經被序列化到entireMessage.xml文件裡 的消息內容:

<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:SomeAction</a:Action>
  </s:Header>
  <s:Body>
     <string  xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
       Hello Message
     </string>
  </s:Body>
</s:Envelope>

同樣,下面是一個包含消息體的文件名bodyContent.xml:

<string  xmlns="http://schemas.microsoft.com/2003/10/Serialization/">
  Hello Message
</string>

下面代碼例子展示了如何使這些資源構建消息:

const Int32 MAXHEADERSIZE = 500;

// ENVELOPE READER EXAMPLE
// Get data from the file that contains the entire  message
FileStream stream = File.Open("entireMessage.xml",  FileMode.Open);
XmlDictionaryReader envelopeReader =
     XmlDictionaryReader.CreateTextReader(stream, new
       XmlDictionaryReaderQuotas());
Message msg = Message.CreateMessage(envelopeReader,
                                      MAXHEADERSIZE,
                                      MessageVersion.Soap12WSAddressing10);
Console.WriteLine("{0}\n", msg.ToString());

//BODY READER EXAMPLE消息體讀取的例子
// Get data from a file that contains just the body從 文件裡只讀消息體數據
stream = File.Open("bodyContent.xml", FileMode.Open);
XmlDictionaryReader bodyReader =
     XmlDictionaryReader.CreateTextReader(stream, new
       XmlDictionaryReaderQuotas());
msg = Message.CreateMessage (MessageVersion.Soap12WSAddressing10,
                             "urn:SomeAction", bodyReader);
Console.WriteLine("{0}\n", msg.ToString());

注意第一次調用CreateMessage方法,接受一個表示最大Message 消息頭字節 的Int32參數。這個限制是必要的,因為我們要從一個Stream序列化Message的內 容。因為消息頭經常是緩存的,這個參數允許我們控制緩存區大小和處理 Message需要的資源。因為第二次調用CreateMessage方法只從Stream裡讀取消息 體的內容,控制消息頭緩存區大小就沒有必要了。

使用BodyWriter把數據放進Message。

其中一個CreateMessage的重載方法允許調用者使用 System.ServiceModel.Channels.BodyWriter 把數據“push”到Message裡。 BodyWriter是一個抽象類型,它暴露了一個接受XmlDictionaryWriter 作為參數 的保護方法。BodyWriter的一個子類型完全控制Message 消息體的創建過程,因 此BodyWriter對於控制Message的反序列化非常有用。絕大部分上, OnWriteBodyContents方法的實現包含調用XmlDictionaryWriter參數上的不同的 Write方法。下面的例子演示了BodyWriter子類型如何從XML文件裡讀取數據並放 進Message的消息體裡:

sealed class MyBodyWriter : BodyWriter {
  private String m_fileName;

  internal MyBodyWriter(String fileName) : base(true) {
     this.m_fileName = fileName;
  }

protected override void OnWriteBodyContents(XmlDictionaryWriter writer) {

    using (FileStream stream = File.Open(m_fileName,  FileMode.Open)) {
       XmlDictionaryReader reader1 =
         XmlDictionaryReader.CreateTextReader(stream,  new
           XmlDictionaryReaderQuotas());
       reader1.ReadStartElement();
       while (reader1.NodeType != XmlNodeType.EndElement)  {
         writer.WriteNode(reader1, true);
       }
     }
  }
}

Once the BodyWriter is subclassed, it can be used in a CreateMessage method, as shown here:

Message pushMessage = Message.CreateMessage(
                         MessageVersion.Soap12WSAddressing10,
                         "urn:SomeAction",
                         new MyBodyWriter ("bodyContent.xml"));

(下半部分會介紹 SOAP Fault 和MessageFault )

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved