WCF快速入門
在本節,我要建立一個HelloWCF應用程序以向計算機科學之神表示我們的敬意。在建立這個應用後,我們分成不同的部分細看。為例子盡量簡單明了,我們會把發送者和接受放在一個控制台應用裡。讓我們現在就開始在控制台應用裡構建需要的基礎架構。
// File: HelloWCFApp.cs using System; sealed class HelloWCF { static void Main(){ } }
定義服務契約
構建HelloWCF應用的第一步是創建服務契約。第9章裡會詳細介紹契約,現在,可以肯定地說,契約式表示消息應用外形
的主要方式。對於外形,我是意思是說我們服務暴露的操作,操作生成、使用的消息schema和每個操作實現的消息交換模式(MEP)。總之,契約定義了我們消息應用生成和使用的東西。大多數契約是帶有WCF API定義的屬性標記的類型定義。
在我們的例子裡,服務契約是一個帶有System.ServiceModel.ServiceContractAttribute 和System.ServiceModel.OperationContractAttribute標記的接口,如下所示
// File: HelloWCFApp.cs [ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
在高層次上,我們的服務契約表示我們接收消息的應用包含一個名字為Say的操作,並且這個操作接收一個String類型的參數
和void返回類型。發送消息的應用可以用它來構造和發送消息給接收程序。既然我們已經定義的服務契約,那就到了該定義
接收程序偵聽地址和如何與其它消息參與者交換消息的時候了。
定義地址和綁定
定義偵聽請求消息的地址需要使用System.Uri類型,定義如何與其它消息參與者交換消息需要我們使用System.ServiceModel.Channels.Binding類型。或者這些類型的繼承類型。下面的代碼說明了如何在我們的應用裡使用
Uri和Binding類型。
// File: HelloWCFApp.cs static void Main(){ // define where to listen for messages定義偵聽消息的地址 Uri address = new Uri("http://localhost:8000/IHelloWCF"); // define how to exchange messages定義如何交換消息 BasicHttpBinding binding = new BasicHttpBinding(); }
注意局部變量address使用的是HTTP格式的統一資源標識符(URI)。選擇這個地址強制要求我們使用HTTP傳輸。更高層次上,綁定是指定傳輸、消息編排和消息編碼的主要方式。局部變量binding是BasicHttpBinding類型的實例。和你從名字看到的一樣,BasicHttpBinding創建的是一個用於HTTP傳輸的消息架構。
創建一個終結點並啟動偵聽
接下來我們要使用地址(address)、綁定(binding)和契約(contract)來構建一個終結點(endpoint)並在此終結點上
偵聽發送進來的消息。通常來說,一個WCF接受程序可以構建和使用多個終結點,並且每個終結點都需要一個地址、一個綁定和一個契約。System.ServiceModel.ServiceHost類型構建和托管終結點,並管理接受應用底層結構的其他部分,比如線程
和對象的生命周期。下面代碼塊演示了如何實例化ServiceHost,如何添加終結點和如何開始偵聽進入的消息:
// File: HelloWCFApp.cs static void Main(){ // define where to listen for messages定義偵聽消息的地址 Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages定義如何交換消息 BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate實例化ServiceHost,傳遞服務類型 // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract增加終結點、綁定和契約 svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening開始偵聽 svc.Open(); // indicate that the receiving application is ready and指示應用准備接受消息 // keep the application from exiting immediately保持應用程序不會立即退出 Console.WriteLine("The HelloWCF receiving application is ready"); Console.ReadLine(); // close the service host關閉宿主 svc.Close(); }
注意調用ServiceHost的構造函數的參數。ServiceHost構造函數被重載多次,在某些形式上,每個重載接受的都是WCF底層結構分發請求消息的對象的類型定義。前面代碼所示ServiceHost構造函數表示消息基礎結構會分發接受到的消息給HelloWCF服務類型的一個實例。
也會發現,前面代碼裡調用了svc.AddServiceEndpoint 和svc.Open。AddServiceEndpoint實例方法設置ServiceHost對象的屬性,這樣它將使用地址、綁定和契約參數執行的行為來偵聽消息。要著重指出的是AddServiceEndpoint方法沒有開始循環偵聽;它僅僅是簡單地改變了ServiceHost對象的狀態(第10章會詳細討論)。ServiceHost實例的Open方法構建了消息基礎結構,並開始循環偵聽。Open方法會驗證ServiceHost對象的狀態,從它的狀態裡構建終結點,並且開始偵聽。
映射接受的消息到HelloWCF的成員
在目前狀態,我們編譯程序,當程序試圖構建一個終結點的時候,會出現一個異常:InvalidOperationException。原因一目了然:在ServiceHost類型的構造函數裡,我們傳遞了HelloWCF作為參數,因此,這就表示消息基礎結構要分發消息給我們的HelloWCF對象。因此,必然存在消息到服務成員的映射關系。最簡單的創建映射的方式就是使HelloWCF服務類實現服務契約IHelloWCF。
// File: HelloWCFApp.cs using System; using System.ServiceModel; using System.ServiceModel.Channels; // implement the IHelloWCF service contract sealed class HelloWCF : IHelloWCF { // indicate when a HelloWCF object is created HelloWCF() { Console.WriteLine("HelloWCF object created"); } static void Main(){ // define where to listen for messages Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening svc.Open(); // indicate that the receiving application is ready and // keep the application from exiting immediately Console.WriteLine("The HelloWCF receiving application is ready"); // wait for incoming messages Console.ReadLine(); // close the service host svc.Close(); } // received messages are dispatched to this instance // method as per the service contract public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); } } [ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
改變HelloWCF的類型定義會使得消息的基礎結構分發接受到的消息到服務實例的Say操作上,因此會在控制台界面上輸出一個簡單的語句。
編譯、運行和檢驗接受者
我們現在准備使用下面的命令行編譯並運行這個應用:
C:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\System.ServiceModel.dll" HelloWCFApp.cs C:\temp>HelloWCFApp.exe The HelloWCF receiving application is ready
此時,接受消息的應用在被動地等待請求消息的到來。我們是用netstat.exe可以檢查一下應用是否確實在偵聽,如下所示:
c:\temp>netstat –a –b TCP kermit:4000 0.0.0.0:0 LISTENING 1104 [HelloWCFApp.exe]
其實在這個例子裡你可以看到更多的輸入信息,但是你將會看到2行與此類似的結果(我的電腦名字是Kermit)。
向接受者發送消息
發送消息的基礎結構也需要依靠地址、綁定和契約,這與接收消息的基礎結構類似。非常典型的是發送者使用的地址、綁定和
契約和接受者。
與接受者不同,發送代碼使用的是不同的類型。概念上,這樣非常有用,因為發送者和接受者在消息交換中扮演著不同的角色。放棄直接使用Uri類型,絕大多數接受者使用System.Service-Model.EndpointAddress類型去表示消息發送的目標。你將在第5章:消息裡看到,EndpointAddress類型是WCF對於WS-Addressing 終結點參考的抽象。此外,發送者不使用ServiceHost類型,而是使用ChannelFactory<T>類型(T是服務契約類型)。ChannelFactory<T>類型構建發送消息的基礎結構和ServiceHost構建接受消息的基礎結構類似。下面的代碼演示了如何使用EndpointAddress類型和ChannelFactory<T>構建發送基礎結構。
// File: HelloWCFApp.cs using System; using System.ServiceModel; using System.ServiceModel.Channels; // implement the IHelloWCF service contract sealed class HelloWCF : IHelloWCF { // indicate when a HelloWCF object is created HelloWCF() { Console.WriteLine("HelloWCF object created"); } static void Main(){ // define where to listen for messages Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening svc.Open(); // indicate that the receiving application is ready and // keep the application from exiting immediately Console.WriteLine("The HelloWCF receiving application is ready"); // begin the sender code發送者代碼開始 // create a channelFactory<T> with binding and address ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, new EndpointAddress(address)); // use the factory to create a proxy使用工廠創建代理 IHelloWCF proxy = factory.CreateChannel(); // use the proxy to send a message to the receiver使用代理發送消息給接受者 proxy.Say("Hi there WCF"); // end the sender code發送者代碼結束 Console.ReadLine(); // close the service host svc.Close(); } // received messages are dispatched to this instance // method as per the service contract public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); } } [ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
注意到我們調用ChannelFactory<T>實例的CreateChannel方法,並且使用其返回的類型調用我們服務契約的方法。
更高層次上,ChannelFactory<T>對象是一個可以制造產生和發送消息給接受者(因此需要在構造函數裡傳遞綁定和地址)
的基礎結構的類型。ChannelFactory<T>實例的CreateChannel方法實際創建的是發送基礎結構,並且通過實現服務契約
的一個對象返回這個基礎結構的引用。我們可以通過調用服務契約的方法來與發送基礎結構交互,這些都會在本章後面,和
第6章:通道裡詳細介紹。
編譯、運行和檢驗發送者
既然我們已經完成了發送和接受基礎結構代碼,現在應該是編譯和運行程序的時候了,如下所示:
c:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\System.ServiceModel.dll" HelloWCFApp.cs c:\temp>HelloWCFApp.exe The HelloWCF receiving application is ready HelloWCF object created Message received, the body contains: HelloWCF!
如期望的一樣,我們的程序執行步驟如下:
1. 為接受來自http://localhost:4000/IHelloWCF的消息構建基礎結構。
2. 開始在http://localhost:4000/IHelloWCF上偵聽消息。
3. 構建發送到http://localhost:4000/IHelloWCF消息的基礎結構。
4.生成和發送消息到http://localhost:4000/IHelloWCF。
5. 接受消息,實例化一個HelloWCF對象,分發消息到服務實例的Say方法上。
看一下消息
現在代碼寫完了,貌似沒看到我們HelloWCF例子裡哪裡使用到了消息。對於開發者,一個WCF應用看起來和感覺都很
像面向對象或者面向組件的應用。在運行時,WCF應用要生成、發送和接受消息,同樣也要處理消息。通過修改Say
方法的實現我們能看到WCF接觸結構的消息:
public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); // Show the contents of the received message顯示接受消息內容 Console.WriteLine( OperationContext.Current.RequestContext.RequestMessage.ToString()); }
修改Say方法後的輸出如下:
The HelloWCF receiving application is ready HelloWCF object created Message received, the body contains: HelloWCF! <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/adessing/none"> http://localhost:8000/IHelloWCF </To> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"> http://tempuri.org/IHelloWCF/Say </Action> </s:Header> <s:Body> <Say xmlns="http://tempuri.org/"> <input>HelloWCF!</input> </Say> </s:Body> </s:Envelope>
注意到打印的SOAP消息,消息的Body部分包含我們傳遞給局部變量的channel 上Say方法的字符串。宏觀上講,我們的應用程序使用這個字符來構建一個SOAP消息,然後發送這個SOAP消息到我們程序的接受部分。接受部分,換句話說,它
要接受SOAP消息,創建一個HelloWCF實例,提取SOAP Body的內容,調用HelloWCF 實例的Say方法,傳遞字符串參數。
小變化大影響
WCF基礎結構為我們做了大部分消息處理工作,一般的對象模型都不會揭示我們的WCF程序是如何在發送者和接受者之間
傳遞消息的事實。實際上,從開發者角度來看,我們的程序裡展示的代碼更像是在使用分布式對象編程而不是消息應用。
通過修改一行代碼我們能夠很容易地看出HelloWCF程序實際上是一個消息應用,並且我們可以觀察一下這個變化帶來對
消息組成帶來的影響。
如果我們修改代碼
BasicHttpBinding binding = new BasicHttpBinding();
變為如下:
WSHttpBinding binding = new WSHttpBinding();
我們會看到如下的輸入:
The HelloWCF receiving application is ready Creating and sending a message to the receiver HelloWCF object created Message received, the body contains: HelloWCF! <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" u:Id="_2" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401- wss-wssecurity-utility-1.0.xsd"> http://tempuri.org/IHelloWCF/Say </a:Action> <a:MessageID u:Id="_3" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-utility-1.0.xsd"> urn:uuid: </a:MessageID> <a:ReplyTo u:Id="_4" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-utility-1.0.xsd"> <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> </a:ReplyTo> <a:To s:mustUnderstand="1" u:Id="_5" xmlns:u="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-wssecurity-utility-1.0.xsd"> http://localhost:8000/IHelloWCF </a:To> <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-secext-1.0.xsd"> <u:Timestamp u:Id="uuid--12" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-utility-1.0.xsd"> <u:Created>2006-08-29T01:57:50.296Z</u:Created> <u:Expires>2006-08-29T02:02:50.296Z</u:Expires> </u:Timestamp> <c:SecurityContextToken u:Id="uuid--6" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:u="http://docs.oasis-open.org/wss/ 2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> <c:Identifier> urn:uuid: </c:Identifier> </c:SecurityContextToken> <c:DerivedKeyToken u:Id="uuid--10" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss- wssecurity-utility-1.0.xsd"> <o:SecurityTokenReference> <o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" URI="#uuid--6" /> </o:SecurityTokenReference> <c:Offset>0</c:Offset> <c:Length>24</c:Length> <c:Nonce>A170b1nKz88AuWmWYONX5Q==</c:Nonce> </c:DerivedKeyToken> <c:DerivedKeyToken u:Id="uuid--11" xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss- wssecurity-utility-1.0.xsd"> <o:SecurityTokenReference> <o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/sct" URI="#uuid--6" /> </o:SecurityTokenReference> <c:Nonce>I8M/H2f3vFuGkwZVV1Yw0A==</c:Nonce> </c:DerivedKeyToken> <e:ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#"> <e:DataReference URI="#_1" /> <e:DataReference URI="#_6" /> </e:ReferenceList> <e:EncryptedData Id="_6" Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:e="http://www.w3.org/2001/04/xmlenc#"> <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <o:SecurityTokenReference> <o:Reference ValueType="http://schemas.xmlsoap.org/ws/2005/02/sc/dk" URI="#uuid--11" /> </o:SecurityTokenReference> </KeyInfo> <e:CipherData> <e:CipherValue> vQ+AT5gioRS6rRiNhWw2UJmvYYZpA+cc1DgC/K+6Dsd2enF4RUcwOG2 xqfkD/ EZkSFRKDzrJYBz8ItHLZjsva4kqfx3UsEJjYPKbxihl2GFrXdPwTmrHWt35Uw0L2rTh8kU9rtj44NfULS59CJbXE6PC7 Af1qWvnobcPXBqmgm4NA8wwSTuR3IKHPfD/Pg/ 3WABob534WD4T1DbRr5tXwNr+yQl2nSWN8C0aaP9+LCKymEK7AbeJXAaGoxdGu/ t6l7Bw1lBsJeSJmsd4otXcLxt976kBEIjTl8/ 6SVUd2hmudP2TBGDbCCvgOl4c0vsHmUC1SjXE5vXf6ATkMj6P3o0eMqBiWlG26RWiYBZ3OxnC1fDs60uSvfHtfF8CD0I LYGHLgnUHz5CFY0rPomT73RCkCfmgFuheCgB9zHZGtWedY6ivNrZe2KPx0ujQ2Mq4pv4bLns2qoykwK03ma7YGiGExGc ZBfkZ2YAkYmHWXJ0Xx4PJmQRAWIKfUCqcrR6lwyLjl5Agsrt0xHA5WEk3hapscW3HZ8wOgwv0fcHlZ1e3EAm0dZr5Ose 3TAKMXf7FC1tMy5u0763flA6AZk9l7IpAQXcTLYicriH5hzf1416xbTJCtt2rztiItSkYizkiJCUMJLanc6ST5i+GVHz J5oRCEWgfOTcQpHmri8y1P1+6jYe9ELla8Mj </e:CipherValue> </e:CipherData> </e:EncryptedData> </o:Security> </s:Header> <s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-utility-1.0.xsd"> <Say xmlns="http://tempuri.org/"> <input>HelloWCF!</input> </Say> </s:Body> </s:Envelope>
正如你看到的,一個簡單的修改給我們的消息應用生成的消息結構帶來的了巨大的影響。BasicHttpBinding 到WSHttpBinding的轉換使得我們的程序從通過HTTP發送簡單的SOAP消息到通過HTTP發送遵守WS-*規范和編排的復雜消息。這個影響不單單是一個冗長的消息。因為我們的程序現在可以發送和接受基於WS-Security、
WS-SecureConversation和其它消息規范的復雜消息。
注釋:實際上,WCF宏觀編程模型去除了“WCF是一個消息應用”的外觀,而提供了更多的分布式對象的“感覺”。在我個人看來,這是平台最大的好處,但是同時充滿了危險。作為開發人員,我們必須抵抗住WCF是分布式對象平台的誘惑,而接受消息的概念。此外,作為一個應用程序和框架的開發人員,我們必須理解如何改變使用WCF類型的方式來影響我們應用系統處理的消息。
暴露元數據
我們的Hello WCF程序使用一個相當簡單的方法就實現了接收者和發送者之間的兼容性。因為接收者和發送者駐留在同一個AppDo-main裡,並且接收者使用的對象對於發送者來說是可見的,我們在發送者代碼裡簡單地重用了地址、綁定和契約。
在發部分消息應用裡,這個方法是可行的。絕大多數情況,我們希望發送者和接收者去駐留在不同的機器上的AppDomains裡。在這些場景裡,接收者顯示指定消息需求,發送者遵守這些需求。
WS- MetadataExchange規范規定了發送者和接收者在平台無關時如何交換這些數據信息。在更多的條款裡,WS-MetadataExchange規范限定了方便終結點之間進行消息交換的schema和編排。在大部分現實世界的應用系統裡(或者至
少比我們的Hello WCF程序復雜的應用)。有一個暴露信息方式的需求,這個方式就是發送者詢問接收者的終結點去提取元數據,並使用這些元數據構建能發送給接收終結點消息的基礎結構。
默認情況下,我的Hello WCF程序不會暴露任何元數據,不會是廣泛接受的Web服務描述語言(WSDL)和擴展 Schema 定義 (XSD)。(不要把消息應用的元數據和程序集或者類型元數據混淆,即使一個可以用來創建另外一個。)事實上,WCF
缺省的情況下不會暴露元數據,這些原因都是因為對安全的考慮。元數據暴露的信息包含應用系統的安全需求。以保護秘密的
名義,這個團隊選擇缺省情況下關閉這個特性。
如果決定暴露系統的元數據,我們可以構建一個暴露元數據的終結點,並且構建元數據終結點的方式和其它終結點非常相似:
使用地址、綁定和契約。但是目前為止你看到的終結點不太一樣,就是服務契約已經定義到WCF的API裡了。
構建元數據終結點的第一步是修改ServiceHost到可以托管元數據的狀態。我們可以通過System.ServiceModel. Description.ServiceMetadataBehavior對象增加到ServiceHost行為集合裡。行為是WCF基礎結構用來改變本地消息處理
的特定信息。下面代碼演示了如何增加ServiceMetadataBehavior對象到活動的ServiceHost對象:
// instantiate a ServiceHost, passing the type to instantiate // when the application receives a messageServiceHost svc = new ServiceHost(typeof(HelloWCF), address); // BEGIN NEW METADATA CODE // create a ServiceMetadataBehavior創建服務元數據行為ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();metadata.HttpGetEnabled = true; // add it to the servicehost descriptionsvc.Description.Behaviors.Add(metadata);
下一步就是為元數據終結點定義Binding。元數據綁定的對象模型與其它綁定區別很大,我們通過調用工廠方法上的
System.ServiceModel.Description.MetadataExchangeBindings的類型創建元數據綁定,如下所示(WCF程序的代碼
其它部分忽略):
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior創建服務元數據行為
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding創建TCP元數據綁定
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
由於ASMX(ASP.NET Web Service)的影響,你也許會認為元數據只能通過HTTP傳輸呈現。事實上,元數據可以通過多種傳輸協議傳遞,並且WS-MetadataExchange說明了這個靈活性。在我們的例子裡,我們調用CreateMexTcpBinding方法,它返回了一個繼承自Binding類型的TCP 傳輸綁定。因為我們使用的是TCP傳輸,所以我們必須全包元數據地址使用了TCP
地址格式,如下所示:
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
//創建元數據交換偵聽地址
Uri mexAddress = new Uri("net.tcp://localhost:5000/IHelloWCF/Mex");
既然我們定義了元數據終結點需要的地址和綁定,我們要添加終結點到ServiceHost上,方式很像我們定義的第一個消息終結點。當添加元數據終結點時,我們要使用WCF API定義的名為System.ServiceModel.Description.IMetadataExchange的服務契約。下面代碼演示了如何添加一個元數據終結點到ServiceHost上,使用適當的地址、綁定和契約。
// instantiate a ServiceHost, passing the type to instantiate
// when the application receives a message
ServiceHost svc = new ServiceHost(typeof(HelloWCF));
// BEGIN NEW METADATA CODE
// create a ServiceMetadataBehavior
ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();
// add it to the servicehost description
svc.Description.Behaviors.Add(metadata);
// create a TCP metadata binding
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
// create an address to listen on WS-Metadata exchange traffic
Uri mexAddress = new Uri("net.tcp://localhost:5000/IHelloWCF/Mex");
// add the metadata endpoint添加元數據終結點
svc.AddServiceEndpoint(typeof(IMetadataExchange),
mexBinding,
mexAddress);
// END METADATA CODE
如果我們構建和運行 Hello WCF程序,就會看到程序確實在2個不同的地址上偵聽。一個地址是用作服務元數據地址,另外
一個地址是為了IHelloWCF.Say功能。讓我們看一下如何從元數據終結點提取元數據並使用它來構建程序裡的發送端基礎結構。
使用元數據
Microsoft .NET Framework SDK安裝了一個功能強大的工具名字是svcutil.exe,它的一個功能就是詢問一個運行的消息
應用並基於獲得的信息生成代理。從內部來說,svcutil.exe使用的是WS-MetadataExchange協議,像與ASMX一起普及的WSDL裡的“get”語義一樣。因為我們的接收程序暴露了一個元數據終結點,我們可以把svcutil.exe指向這個運行的終結點,svcutil.exe會自動生成一個代理類型和與服務終結點兼容的配置信息,這些信息都是參考元數據生成。當使用這種方式的時候,svcutil.exe 依照WS-MetadataExchange的方式發送消息給接收程序,並轉化這些消息為.NET Framework的類型以
方便發送消息程序的開發。
使用Svcutil.exe生成代理
在你運行svcutil.exe以前,檢查一下HelloWCFApp.exe正常運行並偵聽請求消息。下一步就是打開一個新的Windows SDK命令行窗口,輸入下面的命令:
C:\temp>svcutil /target:code net.tcp://localhost:5000/IHelloWCF/Mex
Svcutil.exe會創建2個文件:HelloWCFProxy.cs 和output.config。如果你檢查一下HelloWCFProxy.cs文件,
你就會看到svcutil.exe產生了一個包含IHelloWCF、IHelloWCFChannel,和HelloWCFClient的代碼文件。
注釋:在svcutil.exe生成的所有類型裡,HelloWCFClient類型是使用最頻繁的。我的觀點是,名稱後面加上Client後綴毫無疑問將會在開發人員裡帶來誤解。毋容置疑,Client言外之意就是Client 和Server。HelloWCFClient類型幫助我們構建消息基礎結構,不似乎傳統的客戶端/服務器結構。一定要記住,即使它使用Client後綴,我們仍然是構建消息應用。
合起來看,這些類型定義就是幫助我們創建於接受程序兼容的發送代碼。注意HelloWCF.cs文件裡沒有任何接受程序的地址,
也沒有與HelloWCF.cs源文件匹配的綁定。這些信息都存貯在svcutil.exe生成的另外一個文件裡(output.config)。WCF提供了豐富的配置選項允許我們來給發送和接受程序通過XML配置文件定制不同的行為。為了演示如何利用svcutil創建的數據,
讓我們創建另外一個發送消息控制台程序。我們把它命名為HelloWCFSender。這裡我們要重命名一下配置文件以便新的發送程序可以讀取這個配置文件(修改為HelloWCFSender.exe.config)。
使用Svcutil.exe生成的類型為HelloWCFSender編寫代碼
總之,svcutil.exe為我們的發送程序生成了大部分代碼和配置信息。創建發送程序與HelloWCF.exe非常相似。
using System; using System.ServiceModel; sealed class HelloWCFSender { static void Main(){ // wait for the receiver to start Console.WriteLine("Press ENTER when the Receiver is ready"); Console.ReadLine(); // print to the console that we are sending a message Console.WriteLine("Sending a message to the Receiver"); // create the HelloWCFClient type created by svcutil HelloWCFClient proxy = new HelloWCFClient(); // invoke the Say method proxy.Say("Hi there from a new Sender"); proxy.Close(); // print to the console that we have sent a message Console.WriteLine("Finished sending a message to the Receiver"); } }
注意到我們只實例化了HelloWCFClient類型並調用Say方法。實際復雜的工作都由svcutil.exe生產的類型和WCF配置基礎結構完成了。在我們寫完這些代碼以後,我們可以使用下面的命令編譯為一個程序集:
C:\temp>csc /r: "C:\WINDOWS\Microsoft.Net\v3.0\Windows CommunicationFoundation\System.ServiceModel.dll" HelloWCFProxy.cs HelloWCFSender.cs
接下來我們啟動接受程序(HelloWCFApp.exe),然後啟動發送程序(HelloWCFSender.exe),我們就會在發送端看到下面這樣的輸出消息:
C:\temp>HelloWCFSender.exe
Press ENTER when the Receiver is ready
Sending a message to the Receiver
Finished sending a message to the Receiver
概括地說,我們程序的輸出結果證實了發送部分和以前一樣工作,而沒有重用我們構建接收者部分使用的對象。我們可以檢查接受消息的應用去驗證接收者確實收到了一個新的消息。
既然我們有了2個功能完善的WCF應用,那我們就來從總體上看看WCF的架構。