程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 服務站: WCF消息傳遞基礎

服務站: WCF消息傳遞基礎

編輯:關於.NET

開始分離 Windows® Communication Foundation 的各層時,您會發現一種復雜的基於 XML 的消息傳遞框架,它在使用各種協議和格式連接系統時提供了大量的可能性。在本月的專欄中,我將著重介紹一些主要的消息傳遞功能,它們提供了這樣的靈活性。

本專欄假定您對 Windows Communication Foundation 編程模型具有基本的了解。如果您不熟悉它,那麼在繼續之前,您應該閱讀 2006 年 2 月份的《MSDN® 雜志》中我的文章。

Windows Communication Foundation 消息傳遞體系結構的主要目的之一是,在提供統一編程模型的同時,還允許靈活地表示數據和傳遞消息。這是基於將 XML 作為數據模型以及將 SOAP 和 WS-Addressing 作為消息傳遞框架而實現的。但是,Windows Communication Foundation 構建在這些模型基礎上這一事實,並不意味著在傳遞消息時必須使用 XML 1.0、SOAP 或 WS-Addressing。您將會看到 Windows Communication Foundation 提供了很大的靈活性。

XML 表示形式

從早期的 XML 開始,軟件行業就依賴於為在 XML 文檔中找到的數據提供標准定義的鮮為人知的規范。此規范稱為 XML 信息集 (InfoSet),它根據元素和屬性所包含的信息來定義它們,在某種意義上這完全與字節表示形式無關。(有關詳細信息,請參閱 www.w3.org/TR/xml-infoset。)

InfoSet 規范使得其他 XML 規范和 API 為在 XML 文檔中找到的數據提供一致的視圖成為可能(盡管它們可能以完全不同的方式表示該數據)。最終,InfoSet 為使用 XML 數據的應用程序提供了共同的集合點,如圖 1 所示。最後要說的是,XML 處理器負責在字節表示形式和編程模型體驗之間的轉換。

圖 1 XML InfoSet 的角色

Windows Communication Foundation 為 System.Xml 命名空間引入了一些基本增強,這使得在讀寫 XML 文檔時利用替代的字節表示形式(而不是僅限文本的 XML 1.0)成為可能。此處關注的主要類 System.Xml.XmlDictionaryReader 和 System.Xml.XmlDictionaryWriter 位於 Microsoft® .NET Framework 3.0 附帶的新 System.Runtime.Serialization 程序集中。

XmlDictionaryReader 和 XmlDictionaryWriter 類都提供了靜態工廠方法,用於創建使用文本、二進制和 MTOM(消息傳輸優化機制)表示形式的讀取器和編寫器。例如,XmlDictionaryReader 提供了 CreateTextReader、CreateBinaryReader 和 CreateMtomReader 方法,而 XmlDictionaryWriter 提供了對應的 CreateTextWriter、CreateBinaryWriter 和 CreateMtomWriter 方法。

讓我們看一些示例。試考慮某個客戶的以下文本 XML 1.0 表示形式:

<!-- customer.xml -->
<Customer xmlns="http://example.org/customer">
  <Email>[email protected]</Email>
  <Name>Bob</Name>
</Customer>

以下代碼說明如何將 customer.xml 讀入 XmlDocument 對象,以及如何使用 XmlDictionaryWriter 以二進制表示形式重新將同一 XmlDocument 對象保存在磁盤上:

// read from XML 1.0 text representation
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");
// write to binary representation
FileStream custBinStream = new FileStream(
  "customer.bin", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateBinaryWriter(
  custBinStream))
{
  doc.WriteContentTo(xw);
}

運行此代碼後,您就將二進制表示形式保存在 customer.bin 中,如圖 2 所示。即使圖 2 所示的二進制編輯器碰巧顯示字符序列,這實際上也是二進制文件,顯然不像 XML 1.0 文件。不過,它確實表示 XML 文檔 (InfoSet)。盡管如此,此二進制格式是 3.0 版 System.Xml 專有的,它與其他 .NET 版本或 Web 服務框架不兼容。

圖 2 客戶 XML 文檔的二進制表示形式

圖 3 中的代碼示例演示如何將二進制表示形式讀回到 XmlDocument 對象中,以及如何以 MTOM 表示形式將它重新保存到磁盤上。MTOM 表示形式看起來與文本或二進制表示形式差別很大,如圖 4 所示。不過,它表示同一 XML 文檔,並可以使用相同的 System.Xml API 進行處理。MTOM 表示形式也與該 MTOM 中的二進制表示形式不同,因為 MTOM 是基於 InfoSet 的 W3C 推薦標准,在 Web 服務框架之間得到廣泛的支持。通過利用多部分 MIME 組幀,MTOM 使得優化 XML 文檔中二進制元素的傳輸成為可能。

Figure 3 從二進制表示形式讀取和寫入 MTOM 表示形式

// read from binary representation
XmlDocument doc = new XmlDocument();
FileStream custBinStream = new FileStream(
  "customer.bin", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateBinaryReader(
  custBinStream, XmlDictionaryReaderQuotas.Max))
{
  doc.Load(xr);
}
// write to MTOM representation
FileStream custMtomStream = new FileStream(
  "customer.mtom", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateMtomWriter(
  custMtomStream, Encoding.UTF8, 1024, "text/xml"))
{
  doc.WriteTo(xw);
}

圖 4 客戶 XML 文檔的 MTOM 表示形式

現在,通過將 MTOM 表示形式再讀入 XmlDocument 並以文本表示形式將其保存在磁盤上,我們可以執行一個完整循環,如下所示:

// read from MTOM representation
XmlDocument doc = new XmlDocument();
FileStream custMtomStream = new FileStream(
  "customer.mtom", FileMode.Open);
using (XmlReader xr = XmlDictionaryReader.CreateMtomReader(
  custMtomStream, Encoding.UTF8, XmlDictionaryReaderQuotas.Max))
{
  doc.Load(xr);
}
// write to text (XML 1.0) representation
doc.Save("customer.xml");

生成的 custom.xml 文件應該與原始的文本表示形式完全相同。正如您所看到的,通過提供三種方法來表示 XML 以實現持久性和傳輸,這些 System.Xml 增強功能提供了很大的靈活性。當然,將來可能會添加其他表示形式。此外,雖然是 Windows Communication Foundation 為表帶來了這些增強,但是可以在任何 .NET Framework 3.0 應用程序中利用它們。

您可以選擇在傳遞消息時使用的 XML 表示形式,Windows Communication Foundation 即基於此而構建。如果需要互操作性,則應該選擇 XML 1.0 文本表示形式。如果需要互操作性以及對二進制負載的高效支持,則應該選擇 MTOM 表示形式。在只有 .NET 的方案中,二進制表示形式可能會提供更佳的性能。此處的關鍵是您確實可以進行選擇了。

Message 類

任何消息傳遞框架的另一個主要功能是,通過任意標頭擴展消息負載。標頭不過是隨消息傳遞的額外信息,用於實現其他的消息處理功能(如安全性、可靠的消息傳遞和事務)。對於 XML 消息,這意味著用 XML 標頭擴展 XML 負載(兩者都表示為在容器元素中分幀的 XML 元素)。此功能與 SOAP 提供的完全相同。

SOAP 框架使得定義基於 XML 的協議(可通過任何傳輸使用,而不依賴於任何傳輸特定的功能)成為可能。WS-Addressing 是一種規范,用於擴展 SOAP 以提供與傳輸無關的機制來尋址/路由 SOAP 消息。SOAP 和 WS-Addressing 都基於 InfoSet,且允許(但不要求)在傳遞消息時使用 XML 1.0 語法。

Windows Communication Foundation 支持使用上一部分中討論的任何 XmlDictionaryReader/Writer 表示形式,這樣應用程序就可以無縫地滿足各種范圍和性能要求。Windows Communication Foundation 使用圖 5 所示的 Message 類模擬所有消息。正如您可以看到的,Message 類在本質上模擬消息正文以及消息標頭和屬性的集合。可用方法主要用於創建消息、讀寫消息正文以及操作標頭和屬性的集合。

Figure 5 System.ServiceModel.Channels.Message 類

public abstract class Message : IDisposable
{
  // numerous overloads for creating messages
  public static Message CreateMessage(...);
  // creates a buffered copy of a message
  public MessageBuffer CreateBufferedCopy(int maxBufferSize);
  // closes a message
  public void Close();
  // returns an XmlDictionaryReader for reading the body
  public XmlDictionaryReader GetReaderAtBodyContents();
  // deserializes the body into a .NET object
  public T GetBody<T>();
  public T GetBody<T>(XmlObjectSerializer serializer);
  // numerous methods/overloads for writing messages
  public void WriteMessage(XmlDictionaryWriter writer);
  public void WriteBody(XmlDictionaryWriter writer);
  public override string ToString();
  // properties
  public abstract MessageHeaders Headers { get; }
  public abstract MessageProperties Properties { get; }
  public MessageState State { get; }
  public abstract MessageVersion Version { get; }
  public virtual bool IsEmpty { get; }
  public virtual bool IsFault { get; }
  ...
}

通過調用各種靜態 CreateMessage 重載之一創建 Message 對象,並使用 IDisposable 或通過顯式調用 Close 處理 Message 對象。可以從頭開始創建新的 Message 對象,在發送消息時通常這樣做。也可以從消息流創建新的 Message 對象,在接收消息時通常這樣做。

如果從頭開始創建消息,則必須指定操作、消息版本以及要在消息中使用的正文。操作唯一地標識消息的目的或語義。Windows Communication Foundation 服務依賴於將傳入消息分派給相應方法的操作。消息版本標識傳輸時使用的 SOAP 和 WS-Addressing 版本(如果有)。指定消息版本時可以選擇各種不同的選項。讓我們了解一下這些選項。

消息版本

如上所述,通過 MessageVersion 類,可以指定要使用的 SOAP 和 WS-Addressing 版本。通過調用 CreateVersion 並提供 EnvelopeVersion 對象(用於標識 SOAP 版本)和 AddressingVersion 對象(用於標識 WS-Addressing 版本),可以創建 MessageVersion 對象。如下所示:

MessageVersion version = MessageVersion.CreateVersion(
  EnvelopeVersion.Soap12, AddressingVersion.WSAddressing10);

如果查看一下 EnvelopeVersion 類,則將看到 Windows Communication Foundation 當前支持三個選項(即 SOAP-None、Soap11 和 Soap12),如下所示:

public sealed class EnvelopeVersion
{
  public static EnvelopeVersion None { get; }
  public static EnvelopeVersion Soap11 { get; }
  public static EnvelopeVersion Soap12 { get; }
  ...
}

同樣,如果查看一下 AddressingVersion,則將看到 Windows Communication Foundation 當前也支持三個選項(即 WS-Addressing-None、WSAddressing10 和 WSAddressingAugust2004),如下所示:

public sealed class AddressingVersion
{
  public static AddressingVersion None { get; }
  public static AddressingVersion WSAddressing10 { get; }
  public static AddressingVersion WSAddressingAugust2004 { get;}
  ...
}

EnvelopeVersion.None 指定在傳輸過程中您不希望使用 SOAP,它還要求您使用 AddressingVersion.None。這是希望在傳統的 XML 消息傳遞方案中利用 Windows Communication Foundation 時的常見設置。Soap11 表示 SOAP 1.1 規范,該規范目前得到廣泛使用,而 Soap12 表示 SOAP 1.2 W3C 推薦標准(有關指向這兩種規范的鏈接,請參閱 www.w3.org/TR/soap)。

WSAddressingAugust2004 表示 2004 年 8 月開始提交的 WS-Addressing W3C 提案(請參閱 www.w3.org/Submission/ws-addressing),目前得到廣泛支持。而 WSAddressing10 表示最終的 WS-Addressing 1.0 W3C 推薦標准(請參閱 www.w3.org/2002/ws/addr)。

因此,根據進行通信的另一方支持的 SOAP 和 WS-Addressing 版本,可以仔細挑選合適的版本以便利互操作性。此外,為了使您操作起來更輕松一點,MessageVersion 類提供了幾個公共屬性,這些屬性返回表示這兩種規范的最常見組合的已緩存 MessageVersion 對象(參見圖 6)。因此,只需指定 MessageVersion.Soap12WSAddressing10(其作用與 MessageVersion.Default 相同),而不是顯式調用 CreateVersion。

Figure 6 已緩存的 MessageVersion 對象

public sealed class MessageVersion
{
  public static MessageVersion Default { get; }
  public static MessageVersion None { get; }
  public static MessageVersion Soap11 { get; }
  public static MessageVersion Soap11WSAddressing10 { get; }
  public static MessageVersion Soap11WSAddressingAugust2004
   { get; }
  public static MessageVersion Soap12 { get; }
  public static MessageVersion Soap12WSAddressing10 { get; }
  public static MessageVersion Soap12WSAddressingAugust2004
   { get; }
  ... //
}

讀寫消息

從頭開始創建消息時,可以使用許多重載來指定消息的正文。可以將正文作為 XmlDictionaryReader、XmlReader 或可序列化的對象提供。還可以為正文提供 MessageFault 對象以便創建故障消息。創建消息後,可以通過調用返回 XmlReader 的 GetReaderAtBodyContents 或調用 GetBody<T> 將正文反序列化為 .NET 對象來訪問正文。

在希望寫入消息時,可以通過分別調用 WriteMessage 或 WriteBody 方法寫入整個消息或僅寫入正文。這兩種方法都具有允許提供 XmlDictionaryWriter 或 XmlWriter 對象的重載。以下示例說明如何創建隨後寫入到名為 message.xml 的文件中的新消息:

// load body from customer.xml file
XmlDocument doc = new XmlDocument();
doc.Load("customer.xml");
Message m = Message.CreateMessage(
  MessageVersion.Soap11WSAddressingAugust2004,
  "urn:add-customer", new XmlNodeReader(doc));
FileStream fs = new FileStream("message.xml", FileMode.Create);
using (XmlWriter xw = XmlDictionaryWriter.CreateTextWriter(fs))
{
  m.WriteMessage(xw);
}

在這種情況下,為操作指定了“urn:add-customer”,為消息版本指定了 Soap11WSAddressing2004。正文是通過 XmlReader 提供的。然後調用 WriteMessage 將消息寫出到基於文本的 XmlDictionaryWriter 對象。生成的 message.xml 如下所示:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing">
 <s:Header>
  <a:Action s:mustUnderstand="1">urn:add-customer</a:Action>
 </s:Header>
 <s:Body>
  <Customer xmlns="http://example.org/customer">
   <Email>[email protected]</Email>
   <Name>Bob</Name>
  </Customer>
 </s:Body>
</s:Envelope>

請注意,XML 命名空間指示 SOAP 1.1 和 2004 年 8 月的 WS-Addressing。指出可以使用不同的 XmlDictionaryWriter 對象將消息輕松地寫出到二進制或 MTOM 表示形式是很重要的。

現在,如果希望,則可以將版本更改為 MessageVersion.Soap12WSAddressing10,而生成的文件則將如下所示:

<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">urn:add-customer</a:Action>
 </s:Header>
 <s:Body>
  <Customer xmlns="http://example.org/customer">
   <Email>[email protected]</Email>
   <Name>Bob</Name>
  </Customer>
 </s:Body>
</s:Envelope>

請注意 SOAP 和 WS-Addressing 的較新版本。

如果將版本更改為 MessageVersion.None,則生成的消息將不再包含 SOAP 信封。相反,它將僅包含正文:

<Customer xmlns="http://example.org/customer">
 <Email>[email protected]</Email>
 <Name>Bob</Name>
</Customer>

為了讀回消息,需要使用允許您提供用於讀取消息流的 XmlDictionaryReader 的不同 CreateMessage 重載。您還需要指定正確的消息版本。接下來的代碼說明一個讀取上面無信封的最後一條消息的示例(版本為 MessageVersion.None):

FileStream fs = new FileStream("message.xml", FileMode.Open);
using (XmlDictionaryReader reader =
  XmlDictionaryReader.CreateTextReader(
   fs, XmlDictionaryReaderQuotas.Max))
{
  Message m = Message.CreateMessage(reader, 1024,
    MessageVersion.None);
  XmlDocument doc = new XmlDocument();
  doc.Load(m.GetReaderAtBodyContents());
  Console.WriteLine(doc.InnerXml);
}

正如您可以看到的,Message 類為使用不同版本的 SOAP 和 WS-Addressing(如果使用了它們中的任一個)讀寫消息提供了很大的靈活性。此外,它基於 XmlDictionaryReader 執行讀寫操作的這一事實意味著,在傳遞消息時可以使用所有支持的 XML 表示形式。

鍵入的消息正文

到目前為止,我介紹了直接使用 XML 的示例。如果要將 XML 反序列化為 .NET 對象,則可以使用支持的序列化程序之一,如 DataContractSerializer、NetDataContractSerializer 或 XmlSerializer。在與 DataContractSerializer 一起使用時,以下類能夠表示在 customer.xml 中找到的 XML:

[DataContract(Namespace="http://example.org/customer")]
public class Customer
{
  public Customer() { }
  public Customer(string name, string email)
  {
    this.Name = name;
    this.Email = email;
  }
  [DataMember]
  public string Name;
  [DataMember]
  public string Email;
}

現在,可以通過為正文提供 Customer 對象創建消息,如下所示:

Customer cust = new Customer("Bob", "[email protected]");
Message msg = Message.CreateMessage(
  MessageVersion.Soap11WSAddressingAugust2004,
  "urn:add-customer", cust);

盡管使用一個對象來表示正文,但是生成的消息將與前面的示例完全相同。

現在,讀取正文時,可以使用 GetBody<Customer> 生成新的 Customer 對象,如下所示:

Customer c = msg.GetBody<Customer>();
Console.WriteLine("Name: {0}, Email: {1}", c.Name, c.Email);

這些版本的 CreateMessage 和 GetBody<T> 在後台使用 DataContractSerializer。每個方法還有一個版本,允許您顯式指定要使用的序列化程序。

有關詳細信息,請參閱我在 2006 年 8 月撰寫的題為“在 Windows Communication Foundation 中進行序列化”專欄(可以從 msdn.microsoft.com/msdnmag/issues/06/08/ServiceStation 下載)。

消息生存期

經過仔細地設計,Message 類已支持流處理。因此,在 Message 對象的生存期內只能處理一次正文。為了確保這一點,Message 類提供了描述對象當前狀態的 State 屬性。MessageState 定義以下五種可能的消息狀態:

public enum MessageState
{
  Created,
  Read,
  Written,
  Copied,
  Closed
}

Message 對象在開始時處於 Created 狀態,該狀態是處理正文的唯一有效狀態。處理正文有以下幾種不同的方式:可以對其進行讀取、寫入或復制。調用 GetReaderAtBodyContents 或 GetBody<T> 可將狀態更改為 Read。調用 WriteMessage 或 WriteBody 可將狀態更改為 Written。調用 CreateBufferedCopy 可將狀態更改為 Copied。例如,試考慮以下使消息在幾種狀態之間切換的示例:

Customer cust = new Customer("Bob", "[email protected]");
Message m = Message.CreateMessage(
  MessageVersion.Soap11WSAddressingAugust2004,
  "urn:add-customer", cust);
Console.WriteLine("State: {0}", m.State);
Customer c = m.GetBody<Customer>();
// cannot access body from here on
Console.WriteLine("State: {0}", m.State);
m.Close();
Console.WriteLine("State: {0}", m.State);

執行代碼時,會將此文本打印到控制台:

State: Created
State: Read
State: Closed

在 Message 對象不再處於 Created 狀態後,需要訪問正文的任何方法都將引發異常。例如,首次調用 GetBody<T> 後再次調用它將導致異常。在需要多次處理正文的情況下,可以創建緩沖副本(通過調用 CreateBufferedCopy)或者將正文加載到 XmlDocument 中或將它反序列化為 .NET 對象以便在內存中使用。

不用說,通過 State 屬性可以在運行時確定是否已使用特定消息的正文(它不再處於 Created 狀態時)以及如何使用它(它是被讀取、寫入還是復制)。

消息標頭和屬性

Message 類提供了 Headers 屬性,以模擬與正文關聯的標頭集合。以下示例說明如何將名為“ContextId”的自定義標頭添加到消息中:

Customer cust = new Customer("Bob", "[email protected]");
Message m = Message.CreateMessage(
  MessageVersion.Default, "urn:add-customer", cust);
m.Headers.Add(
  MessageHeader.CreateHeader(
    "ContextId", "http://example.org/customHeaders",
    Guid.NewGuid()));

在寫入時,此消息將如下所示:

<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">urn:add-customer</a:Action>
  <ContextId xmlns="http://example.org/customHeaders"
  >a200f76d-5b83-4496-a035-4f9e70a07959</ContextId>
 </s:Header>
 ...
</s:Envelope>

通常由中介來處理在消息中找到的標頭。這樣的中介通常需要在某處存儲其處理結果,供將來在處理管道時使用。為達到此目的,Message 類還提供了 Properties 集合來存儲任意的名稱/對象對。

與標頭(它始終在消息中傳輸)不同,消息屬性通常僅在本地處理期間使用(它們可能不對通信時發生的情況產生影響,盡管有時它們會產生影響)。例如,使用 HTTP 時,Windows Communication Foundation 在傳入 Message 對象的 Properties 集合中存儲 HTTP 請求詳細信息。詳細信息存儲在 HttpRequestMessageProperty 類型的對象(可以使用屬性名的 httpRequest 訪問它)中。還可以通過用 HttpReponseMessageProperty 類型的對象填充名為 httpResponse 的屬性,影響 HTTP 響應詳細信息。

Windows Communication Foundation 通道(尤其是實現各種 WS-* 規范的內置協議通道)層中的組件頻繁使用標頭和屬性是很常見的。

將消息映射到方法

必須首先定義將傳入消息映射到方法的服務約定,才能開始使用 Windows Communication Foundation 發送或接收消息。通過使操作值與方法簽名關聯,可以做到這一點。例如,以下接口為單向操作定義可能的最通用服務約定:

[ServiceContract]
public interface IUniversalOneWay
{
  [OperationContract(Action="*")]
  void ProcessMessage(Message msg);
}

此定義使 ProcessMessage 與所有傳入消息關聯,而不管其操作值如何(由於使用了 Action= "*" 子句)。也可以使用相同方法定義一般的請求-答復操作:

[ServiceContract]
public interface IUniversalRequestReply
{
  [OperationContract(Action="*", ReplyAction="*")]
  Message ProcessMessage(Message msg);
}

還可以通過將 Action 屬性設置為特定值(如 urn:add-customer)使方法與特定操作值關聯,如下所示:

[ServiceContract]
public interface ICustomer
{
  [OperationContract(Action="urn:add-customer")]
  void AddCustomer(Message msg);
}

在 Windows Communication Foundation 收到傳入消息時,它將查看操作以確定要調度的方法,然後它再執行任何必要的序列化。對請求/響應類型使用消息時,Windows Communication Foundation 不執行任何序列化,而是由您決定如何處理消息。但是,使用鍵入的簽名時,Windows Communication Foundation 使用序列化方法在後台自動執行讀寫消息正文的過程。例如,試考慮以下服務約定:

[ServiceContract]
public interface ICustomer
{
  [OperationContract(Action="urn:add-customer")]
  void AddCustomer(Customer cust);
}

在這種情況下,Windows Communication Foundation 負責在調用 AddCustomer 之前將傳入消息的正文反序列化為 Customer 對象。

還存在一種稱為消息約定的序列化快捷方式,用於自動將標頭添加到 Message 對象。消息約定允許您為類添加注釋,指定哪些字段映射到標頭(與正文相對):

[MessageContract(IsWrapped=false)]
public class AddCustomerRequest
{
  [MessageHeader(Name="ContextId",
    Namespace="http://example.org/customHeaders"]
  public Guid ContextId;
  [MessageBodyMember]
  public Customer customer;
}

如果此消息約定類已就緒,則可以定義一個將它用作請求類型的操作,如下所示:

[ServiceContract]
public interface ICustomer
{
  [OperationContract(Action = "urn:add-customer")]
  void AddCustomer(AddCustomerRequest request);
}

這樣會通知 Windows Communication Foundation 在對象中的 ContextId 字段和 SOAP 消息中的 ContextId 標頭之間自動映射,您就不必手動處理 Headers 集合了。

端點和綁定

為了布置 Windows Communication Foundation 服務,您需要提供幾則信息。首先,必須通知它要使用的傳輸和地址。第二,必須指定所需的 XML 表示形式和消息版本。第三,必須配置采用哪些 WS-* 協議。最後,必須提供傳入消息和服務方法之間的映射。在 Windows Communication Foundation 中,通過端點指定所有這些詳細信息。

端點配置僅僅是地址、綁定和約定的組合。如上所述,約定定義消息和方法之間的映射。綁定指定剩余的消息傳遞詳細信息。它指定在傳遞期間要使用的傳輸、XML 表示形式和消息版本。它還確定生成通道堆棧時應該包括的 WS-* 協議。

綁定還指定消息編碼器;它實際上控制 XML 表示形式和消息版本詳細信息。在運行時,消息編碼器是傳輸通道從數據流讀取和向其寫入消息所用的組件。Windows Communication Foundation 提供了三個內置的消息編碼器實現:TextMessageEncoder、BinaryMessageEncoder 和 MtomMessageEncoder。所有這些類都基於在前面討論的新增 XmlDictionaryReader 和 XmlDictionaryWriter 類支持給定的 XML 表示形式。每個類還配置為使用特定的消息版本。

正如您可能知道的,Windows Communication Foundation 附帶有內置綁定以便於處理常見方案。每個綁定都配置為使用特定的消息編碼器。例如,BasicHttpBinding 和 WSHttpBinding 都配置為使用 TextMessageEncoder。但是,對於消息版本,前者使用 MessageVersion.Soap11,而後者使用 MessageVersion.Soap12WSAddressing10。NetTcpBinding、NetNamedPipeBinding 和 NetMsmqBinding 都配置為使用 BinaryMessageEncoder 和 MessageVersion.Soap12WSAddressing10。

可以選擇在特定的綁定上使用不同的消息編碼器(通過其屬性或綁定配置)。例如,以下代碼說明如何將 BasicHttpBinding 的消息編碼器更改為 MtomMessageEncoder:

BasicHttpBinding basic = new BasicHttpBinding();
basic.MessageEncoding = WSMessageEncoding.Mtom;
... // use binding

在需要對綁定配置進行更多控制時,可以使用 CustomBinding 類定義自定義綁定:

MtomMessageEncodingBindingElement mtom = 
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(new HttpTransportBindingElement());
... // use binding

使用此方法可以配置消息編碼器使用的精確消息版本和文本編碼詳細信息。除了此靈活性外,Windows Communication Foundation 體系結構還允許您編寫和使用自定義消息編碼器 - SDK 附帶有一個說明如何執行此操作的示例。通常,使用 Windows Communication Foundation 可以輕松地將端點配置為使用不同的 XML 表示形式和消息版本,而不會對服務代碼產生任何影響。

綜述

讓我們看一下最後一個示例,該示例綜合了其中的大多數消息傳遞概念。圖 7 說明如何實現、承載和配置使用 IUniversalOneWay 約定以及自定義的綁定和編碼器的通用服務。圖 8 說明如何實現能夠將消息發送到所承載的服務的客戶端。服務實現使用 System.Xml 處理傳入消息,而客戶端使用 Customer 對象生成消息。

Figure 8 通用 WCF 客戶端

class Program
{
static void Main(string[] args)
{
Console.ReadLine();
// create and configure an MTOM encoder
MtomMessageEncodingBindingElement mtom =
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
// create a CustomBinding using MTOM encoder
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(new HttpTransportBindingElement());
// create a ChannelFactory using the custom binding
ChannelFactory<IUniversalOneWay> cf =
new ChannelFactory<IUniversalOneWay>(binding);
IUniversalOneWay channel = cf.CreateChannel(
new EndpointAddress(
"http://localhost:8080/customerservice"));
// create a Message and send through channel
Customer cust = new Customer("Bob", "[email protected]");
Message msg = Message.CreateMessage(
MessageVersion.Soap12, "urn:add-customer", cust);
channel.ProcessMessage(msg);
}
}

Figure 7 通用 WCF 服務

public class CustomerService : IUniversalOneWay
{
#region IUniversalOneWay Members
public void ProcessMessage(Message msg)
{
Console.WriteLine("Message received...");
XmlDocument doc = new XmlDocument();
doc.Load(msg.GetReaderAtBodyContents());
Console.WriteLine("Customer: {0} ({1})",
doc.SelectSingleNode("/*/*[local-name()='Name']").InnerText,
doc.SelectSingleNode("/*/*[local-name()=
'Email']").InnerText);
}
#endregion
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost host =
new ServiceHost(typeof(CustomerService),
new Uri("http://localhost:8080/customerservice")))
{
// create and configure an MTOM encoder
MtomMessageEncodingBindingElement mtom =
new MtomMessageEncodingBindingElement(
MessageVersion.Soap12, Encoding.UTF8);
// create a CustomBinding using MTOM encoder
CustomBinding binding = new CustomBinding();
binding.Elements.Add(mtom);
binding.Elements.Add(
new HttpTransportBindingElement());
// add an endpoint using the custom binding
host.AddServiceEndpoint(typeof(IUniversalOneWay),
binding, "");
host.Open();
Console.WriteLine("CustomerService is up and running...");
Console.ReadKey();
}
}
}

Windows Communication Foundation 消息傳遞體系結構提供了統一的編程模型,從而為消息傳遞提供了很大的靈活性。這樣就可以無縫地實現不使用 SOAP、WS-Addressing 或任何其他 WS-* 協議的傳統的 XML 服務。還可以配置更復雜的綁定以滿足在需要不同的協議和版本時特定集成方案的精確要求。

將您想向 Aaron 提出的問題和意見發送至:[email protected] [email protected].

代碼下載位置:http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/ServiceStation2007_04.exe

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