程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 《WCF技術內幕》翻譯12:第1部分_第2章_面向服務:概念匯總

《WCF技術內幕》翻譯12:第1部分_第2章_面向服務:概念匯總

編輯:關於.NET

概念匯總

我希望從本章的這個小節你能對面向服務有個清晰的認識。接下來的一些章 節,讓我們看看這些概念根本上如何在WCF系統裡工作的。在我們的例子裡,我 講構建一個簡單的接受客戶訂單的訂單處理服務。為了保證概念的簡潔明了,這 裡有2個消息參與者,如圖2-3所示。

圖2-3:一個簡單的消息交換示例

這個示例代碼的作用就是加強你對面向服務的認識和提供WCF的介紹,不會詳 細描述WCF的方方面面或者建立一個完整功能的訂單處理系統。例子裡的類型和 機制會在這本書將會詳細介紹。

契約

顯然地,面向服務系統開發首先應該是創建契約。為了例子簡單,一個訂單 包含一個產品ID(product ID)、數量(quantity)和狀態消息(status message)。有了這三個字段,一個訂單可以使用下面的偽schema代碼表示:

<Order>
  <ProdID>xs:integer</ProdID>
  <Qty>xs:integer</Qty>
  <Status>xs:string</Status>
</Order>

從我們消息自治和配置地址的討論,我們知道消息需要更多的地址結構,如 果我們想使用WS-Addressing。在我們的訂單處理服務裡,消息發送者和接收者 統一使用遵守WS-Addressing規范的SOAP消息來限制消息的結構。有了這個規則 ,下面就是一個結構合理的消息例子:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap- envelope" xmlns:wsa="http://
schemas.xmlsoap.org/ws/2004/08/addressing">
  <s:Header>
     <wsa:Action  s:mustUnderstand="1">urn:SubmitOrder</wsa:Action>
     <wsa:MessageID>4</wsa:MessageID>
     <wsa:ReplyTo>
       <wsa:Address>  http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
       </wsa:Address>
     </wsa:ReplyTo>
     <wsa:To  s:mustUnderstand="1">http://localhost:8000/Order</wsa:To>
  </s:Header>
   <s:Body>
     <Order>
       <ProdID>6</ProdID>
       <Qty>6</Qty>
       <Status>order placed</Status>
     </Order>
  </s:Body>
</s:Envelope>

在創建完描述我們的消息的schema之後,下一個步驟就是定義接受消息的終 結點。為此,我們要先看一下WSDL的概念。你也許會想:“我沒心情去處理原始 的schema和WSDL。”那好,你不會孤單(MJ的一首歌)。WCF團隊已經為我們在 Microsoft .NET Framework提供了使用我們選擇語言(本書裡,使用C#)去表示 契約的方法(schema和WSDL)。基本上,C#描述的契約都可以根據需要轉化為機 遇XSD和基於WSDL的契約。

當使用C#表示契約的時候,我們可以選擇定義一個類或者接口。下面是用C# 接口定義一個契約的例子:

// file: Contracts.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
// define the contract for the service
[ServiceContract(Namespace =  "http://wintellect.com/ProcessOrder")]
public interface IProcessOrder {
     [OperationContract(Action="urn:SubmitOrder")]
     void SubmitOrder(Message order);
}

注意ServiceContractAttribute 和 OperationContractAttribute屬性標記 。我們將在第9章:契約(Contracts)裡詳細討論這2個屬性。現在這個接口與 其它.NET Framework的接口不同,因為它們可以定制屬性。另外也注意一下 SubmitOrder方法的簽名。這個方法的唯一參數類型是 System.ServiceModel.Message。這個參數代表任何來自初始發送者和中介者的 消息。這個消息類型非常有趣,並且優點復雜,我們將會在第五章:消息 (Messages)裡詳細討論。當前,假設初始發送者發送的消息可以被 System.ServiceModel.Message表示。

無論我們選擇什麼方式表達契約,它都應該在發送者和接收者程序進一步開 發以前確定。實際上,接收者定義需要的消息結構契約,發送者通常嘗試按照這 個契約構建和發送消息。

沒什麼能阻止發送者不按照消息接收者定義的契約來發送消息。因此,接收 者第一個任務應該是驗證接收到的消息是否符合契約。這個方法保證了接收者的 數據結構不會被破壞。這些觀點經常在分布式開發社區裡討論。

這個契約可以被編譯為一個程序集。一旦編譯完成,程序集可以指派給發送 者和接收者。這個程序集代表了發送者和接收者之間的契約。當契約還會變化確 定的次數的時候,我們應該認為在共享以後它是不可變的。我們會在第9章討論 契約的版本問題。

選擇我們有了自己的契約,讓我們建立一個接收者程序。第一個商業訂單應 該構建一個實現我們定義好的契約的接口。

// File: Receiver.cs
// Implement the interface defined in the contract  assembly
public sealed class MyService : IProcessOrder {
  public void SubmitOrder(Message order) {
     // Do work here
  }
}

因為這是一個簡單的例子,所以我們打算在Console裡打印消息並且寫到文件 裡。

// File: Receiver.cs
using System;
using System.Xml;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
// Implement the interface defined in the contract assembly 實現程序集裡定義的契約接口
public sealed class MyService : IProcessOrder {
  public void SubmitOrder(Message order) {
     // Create a file name from the MessageID根據 MessageID創建文件名
     String fileName = "Order" +  order.Headers.MessageId.ToString() + ".xml";
     // Signal that a message has arrived提示消息到達
     Console.WriteLine("Message ID {0} received",
       order.Headers.MessageId.ToString());
     // create an XmlDictionaryWriter to write to a file 創建一個XmlDictionaryWriter去寫文件
     XmlDictionaryWriter writer =  XmlDictionaryWriter.CreateTextWriter(
       new FileStream(fileName, FileMode.Create));
     // write the message to a file寫消息到文件裡
     order.WriteMessage(writer);
     writer.Close();
  }
}

我們下一個任務就是讓MyService類型去接受請求消息。為了接受消息:

·MyService必須加載進AppDomain。

·MyService(或者另外一個類型)必須偵聽內部消息。

·適當的時候,比如創建一個此類型的實例,並且只要需要就要引用它(為 了阻止垃圾收集器GC釋放對象的內存)。

·當一個消息到來時,它必須被分發給MyService的實例,激活SubmitOrder 方法。

這些任務通常由宿主(Host)來執行。我們將在第10章裡詳細介紹宿主 (Host),但是現在,假設我們的AppDomain托管在一個控制台程序裡,管理和 分發消息到MyService對象的類型是System.ServiceModel.ServiceHost。我們的 控制台應用代碼如下:

// File: ReceiverHost.cs
using System;
using System.Xml;
using System.ServiceModel;
internal static class ReceiverHost {
  public static void Main() {
     // Define the binding for the service定義服務綁定
     WSHttpBinding binding = new WSHttpBinding (SecurityMode.None);
     // Use the text encoder
     binding.MessageEncoding = WSMessageEncoding.Text;
     // Define the address for the service定義服務地址
     Uri addressURI = new Uri (@"http://localhost:4000/Order");
    // Instantiate a Service host using the MyService  type使用MyService實例化服務宿主
     ServiceHost svc = new ServiceHost(typeof (MyService));
     // Add an endpoint to the service with the給服務增 加一個終結點
     // contract, binding, and address
     svc.AddServiceEndpoint(typeof(IProcessOrder),
                            binding,
                            addressURI);

     // Open the service host to start listening打開服務 宿主開始偵聽
     svc.Open();
     Console.WriteLine("The receiver is ready");
     Console.ReadLine();
     svc.Close();
  }
}

在我們的控制台程序裡,必須在托管之前設置服務的一些屬性。正如你將在 後續章節裡看到的一樣,每個服務包含一個地址、一個綁定和一個契約。這些機 制常常被成為WCF的ABC。現在,假設如下:

·一個地址描述了服務偵聽請求消息的地方

·一個綁定描述了服務如何偵聽消息

·一個契約描述了服務會將會接收什麼樣的消息

在我們的例子裡,我們將使用WSHttpBinding綁定去定義服務如何偵聽請求消 息。第8章我們會消息討論綁定(Binding)。我們的服務也會使用Uri類型來定 義服務偵聽的地址。我們的服務會創建一個ServiceHost實例去托管MyService。 ServiceHosts沒有缺省的終結點,所以我們必須通過AddServiceEndpoint方法增 加自己的終結點。這樣,我們的控制台程序就准備開始在 http://localhost:8000/Order地址上偵聽請求消息。對實例Open方法的調用啟 動了循環偵聽。

你也許想知道當一個消息到達at http://localhost:8000/Order時會發生什 麼。答案取決於到達終結點的消息類型。就此來說,我們來做個簡單的例子。更 高層次上,我們的消息發送者必須要知道下面三點:

·服務在哪裡(地址address)

·服務期望消息如何被傳遞(綁定binding)

·服務期望什麼類型的消息(契約contract)

假設這些大家都知道,下面就是一個合格的消息發送應用:

// File: Sender.cs
using System;
using System.Text;
using System.Xml;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.IO;
using System.ServiceModel.Channels;
public static class Sender {
  public static void Main(){
     Console.WriteLine("Press ENTER when the receiver is  ready");
     Console.ReadLine();
     // address of the receiving application接受程序的地址
     EndpointAddress address =
       new EndpointAddress (@"http://localhost:4000/Order");
     // Define how we will communicate with the service 定義一個我們通信的服務
     // In this case, use the WS-* compliant HTTP  binding這個例子裡,我們可以使用WS的HTTP綁定
     WSHttpBinding binding = new WSHttpBinding (SecurityMode.None);
     binding.MessageEncoding = WSMessageEncoding.Text;
     // Create a channel創建通道
     ChannelFactory<IProcessOrder> channel =
       new ChannelFactory<IProcessOrder>(binding,  address);
     // Use the channel factory to create a proxy使用通 道工廠創建代理
     IProcessOrder proxy = channel.CreateChannel();
     // Create some messages創建一些消息
     Message msg = null;
     for (Int32 i = 0; i < 10; i++) {
       // Call our helper method to create the message 調用我們的helper方法創建消息
       // notice the use of the Action defined in注意 使用在IProcessOrder契約裡定義的Action
       // the IProcessOrder contract
       msg = GenerateMessage(i,i);
       // Give the message a MessageID SOAP headerSOAP 消息頭裡加MessageID
       UniqueId uniqueId = new UniqueId(i.ToString());
       msg.Headers.MessageId = uniqueId;
       Console.WriteLine("Sending Message # {0}",  uniqueId.ToString());
       // Give the message an Action SOAP headerSOAP消 息頭裡加Action
       msg.Headers.Action = "urn:SubmitOrder";
       // Send the message發送消息
       proxy.SubmitOrder(msg);
     }
  }
  // method for creating a Message創建消息的方法
  private static Message GenerateMessage(Int32 productID,  Int32 qty) {
     MemoryStream stream = new MemoryStream();
     XmlDictionaryWriter writer =  XmlDictionaryWriter.CreateTextWriter(
       stream, Encoding.UTF8, false);
     writer.WriteStartElement("Order");
     writer.WriteElementString("ProdID", productID.ToString ());
     writer.WriteElementString("Qty", qty.ToString());
     writer.WriteEndElement();
     writer.Flush();
     stream.Position = 0;
     XmlDictionaryReader reader =  XmlDictionaryReader.CreateTextReader(
       stream, XmlDictionaryReaderQuotas.Max);
     // Create the message with the Action and the body 使用Action和body創建消息
     return Message.CreateMessage (MessageVersion.Soap12WSAddressing10,
                                  String.Empty,
                                  reader);
  }
}

不要被ChannelFactory類型嚇到,我們將會在第4章裡剖析這個類型。現在, 注意for循環裡的代碼。循環體裡代碼會生成10個消息,並且每個消息會指定一 個假的唯一的ID和一個Action(動作,這裡專有名詞,不做翻譯)。

這裡,我們應該有2個可執行文件(ReceiverHost.exe and Sender.exe)表 示最終接收者和初始發送者。如果我們運行2個控制台程序,等待接收者初始化 完畢,在發送程序裡按回車(ENTER)鍵,我們應該可以在接受程序看到下面的 結果:

The receiver is ready
Message ID 0 received
Message ID 1 received
Message ID 2 received
Message ID 3 received
Message ID 4 received
Message ID 5 received
Message ID 6 received
Message ID 7 received
Message ID 8 received
Message ID 9 received

恭喜!你已經使用WCF完成了一個面向服務的程序。記住服務把內部消息寫進 文件裡。如果我們檢查服務寫過的文件,會看到如下:

<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:SubmitOrder</a:Action>
     <a:MessageID>1</a:MessageID>
     <a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a: Address>
     </a:ReplyTo>
     <a:To  s:mustUnderstand="1">http://localhost:4000/Order</a:To>
  </s:Header>
  <s:Body>
     <Order>
       <ProdID>1</ProdID>
       <Qty>1</Qty>
     </Order>
  </s:Body>
</s:Envelope>

消息頭應該和我們在WS-Addressing規范裡看到的一樣看起來有些奇怪,並且 它們的值應該與我們在消息發送應用裡設置的屬性很一樣。事實上, System.ServiceModel.Message類型暴露了一個叫做Headers的屬性,它是屬於 System.ServiceModel.MessageHeaders類型。這個MessageHeaders類型暴露的其 它屬性可以表示WS-Addressing的消息頭。這裡的想法就是我們可以使用WCF面向 對象的編程模型去影響面向服務的SOAP消息。

【地址】:http://www.cnblogs.com/frank_xl/

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