概念匯總
我希望從本章的這個小節你能對面向服務有個清晰的認識。接下來的一些章 節,讓我們看看這些概念根本上如何在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/