http://www.cnblogs.com/artech/archive/2007/02/26/656901.html
爲了使讀者對基於WCF的編程模型有一個直觀的映像,我將帶領讀者一步一步地創建一個完整的WCF應用。本應用功能雖然簡單,但它涵蓋了一個完整WCF應用的基本結構。對那些對WCF不是很瞭解的讀者來說,這個例子將帶領你正式進入WCF的世界。
在這個例子中,我們將實現一個簡單的計算服務(CalculatorService),提供基本的加、減、乘、除的運算。和傳統的分佈式通信框架一樣,WCF本質上提供一個跨進程、跨機器以致跨網絡的服務調用。在本例中,客戶端和服務通過運行在相同的同一臺機器上不同進程模擬,圖1體現了客戶端和服務端進程互相調用的關係。
圖1 計算服務應用運行環境
WCF的服務不能孤立地存在,需要寄宿於一個運行着的進程中,我們把承載WCF服務的進程稱爲宿主,爲服務指定宿主的過程稱爲服務寄宿(Service Hosting)。在我們的計算服務應用中,採用了兩種服務寄宿方式:通過自我寄宿(Self-Hosting)的方式創建一個控制檯應用作爲服務的宿主(寄宿進程爲Hosting.exe);通過IIS寄宿方式將服務寄宿於IIS中(寄宿進程爲IIS的工作進行W3wp.exe)。客戶端通過另一個控制檯應用模擬(進程爲Client.exe)。接下來,我們就一步一步來構建這樣的一個WCF應用。
步驟一:構建整個解決方案
通過VS 2008創建一個空白的解決方案,添加如下四個項目。項目的類型、承載的功能和相互引用關係如下,整個項目在VS下的結構如圖2所示。
- Contracts:一個類庫項目,定義服務契約(Service Contract),引用System.ServiceMode程序集(WCF框架的絕大部分實現和API定義在該程序集中);
- Services:一個類庫項目,提供對WCF服務的實現。定義在該項目中的所有WCF服務實現了定義在Contracts中相應的服務契約,所以Services具有對Contracts項目的引用;
- Hosting:一個控制檯(Console)應用,實現對定義在Services項目中的服務的寄宿,該項目須要同時引用Contracts和Services兩個項目和System.ServiceMode程序集;
- Client:一個控制檯應用模擬服務的客戶端,該項目引用System.ServiceMode程序集。
圖2 計算服務在VS中的結構
步驟二:創建服務契約
WCF採用基於契約的交互方式實現了服務的自治,以及客戶端和服務端之間的鬆耦合。WCF包含四種類型的契約:服務契約、數據契約、消息契約和錯誤契約,這裏着重於服務契約。從功能上講,服務契約抽象了服務提供的所有操作;而站在消息交換的角度來看,服務契約則定義了基於服務調用的消息交換過程中,請求消息和回覆消息的結構,以及採用的消息交換模式。第4章將提供對服務契約的詳細介紹。
一般地,我們通過接口的形式定義服務契約。通過下面的代碼,將一個接口ICalculator定義成服務契約。WCF廣泛採用基於自定義特性(Custom Attribtue)的聲明式編程模式,我們通過在接口上應用System.ServiceModel.ServiceContractAttribute特性將一個接口定義成服務契約。在應用ServiceContractAttribute特性的同時,還可以指定服務契約的名稱和命名空間。至於契約名稱和命名空間的含義和作用,在本人拙著《WCF技術剖析(卷1)》第4章,在這裏我們將契約名稱和命名空間設置成CalculatorService和http://www.artech.com/)。
通過應用ServiceContractAttribute特性將接口定義成服務契約之後,接口的方法成員並不能自動成爲服務的操作。在此方面,WCF採用的是顯式選擇(Explicit Opt-in)的策略:我們須要在相應的操作方法上面顯式地應用OperationContractAttribute特性。
1: using System.ServiceModel;
2: namespace Artech.WcfServices.Contracts
3: {
4: [ServiceContract(Name="CalculatorService", Namespace="http://www.artech.com/")]
5: public interface ICalculator
6: {
7: [OperationContract]
8: double Add(double x, double y);
9:
10: [OperationContract]
11: double Subtract(double x, double y);
12:
13: [OperationContract]
14: double Multiply(double x, double y);
15:
16: [OperationContract]
17: double Divide(double x, double y);
18: }
19: }
步驟三:創建服務
當服務契約成功創建時,我們需要通過實現服務契約來創建具體的WCF服務。WCF服務CalculatorService定義在Services項目中,實現了服務契約接口ICalculator,實現了所有的服務操作。CalculatorService定義如下:
1: using Artech.WcfServices.Contracts;
2: namespace Artech.WcfServices.Services
3: {
4: public class CalculatorService:ICalculator
5: {
6: public double Add(double x, double y)
7: {
8: return x + y;
9: }
10:
11: public double Subtract(double x, double y)
12: {
13: return x - y;
14: }
15:
16: public double Multiply(double x, double y)
17: {
18: return x * y;
19: }
20:
21: public double Divide(double x, double y)
22: {
23: return x / y;
24: }
25: }
26: }
步驟四:通過自我寄宿的方式寄宿服務
WCF服務需要依存一個運行着的進程(宿主),服務寄宿就是爲服務指定一個宿主的過程。WCF是一個基於消息的通信框架,採用基於終結點(Endpoint)的通信手段。終結點由地址(Address)、綁定(Binding)和契約(Contract)三要素組成,如圖3所示。由於三要素應爲首字母分別爲ABC,所以就有了易於記憶的公式:Endpoint = ABC。一個終結包含了實現通信所必需的所有信息,我們可以這樣認識終結點的ABC:
- 地址(Address):地址決定了服務的位置,解決了服務尋址的問題,《WCF技術剖析(卷1)》第2章提供了對地址和尋址機制的詳細介紹;
- 綁定(Binding):綁定實現了通信的所有細節,包括網絡傳輸、消息編碼,以及其他爲實現某種功能(比如安全、可靠傳輸、事務等)對消息進行的相應處理。WCF中具有一系列的系統定義綁定,比如BasicHttpBinding、WsHttpBinding、NetTcpBinding等,《WCF技術剖析(卷1)》第3章提供對綁定的詳細介紹;
- 契約(Contract):契約是對服務操作的抽象,也是對消息交換模式以及消息結構的定義。《WCF技術剖析(卷1)》第4章提供對服務契約的詳細介紹。
圖3 終結點三要素
服務寄宿的目的就是開啓一個進程,爲WCF服務提供一個運行的環境。通過爲服務添加一個或多個終結點,使之暴露給潛給的服務消費者。服務消費者最終通過相匹配的終結點對該服務進行調用。我們可以完全通過代碼的方式完成所有的服務寄宿工作,下面的代碼體現了通過一個控制檯應用對CalculatorService的寄宿:
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Description;
4: using Artech.WcfServices.Contracts;
5: using Artech.WcfServices.Services;
6: namespace Artech.WcfServices.Hosting
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
13: {
14: host.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "http://127.0.0.1:9999/calculatorservice");
15: if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
16: {
17: ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
18: behavior.HttpGetEnabled = true;
19: behavior.HttpGetUrl = new Uri("http://127.0.0.1:9999/calculatorservice/metadata");
20: host.Description.Behaviors.Add(behavior);
21: }
22: host.Opened += delegate
23: {
24: Console.WriteLine("CalculaorService已經啓動,按任意鍵終止服務!");
25: };
26:
27: host.Open();
28: Console.Read();
29: }
30: }
31: }
32: }
WCF服務寄宿通過一個特殊的對象完成:ServiceHost。在上面的例子中,基於WCF服務的類型(typeof(CalculatorService))創建了ServieHost對象,並添加了一個終結點。具體的地址爲http://127.0.0.1:9999/calculatorservice,採用了WSHttpBinding,並指定了服務契約的類型ICalculator。
鬆耦合是SOA的一個基本的特徵,WCF應用中客戶端和服務端的鬆耦合體現在客戶端只須要了解WCF服務基本的描述,而無須知道具體的實現細節,就可以實現正常的服務調用。WCF服務的描述通過元數據(Metadata)的形式發佈出來。WCF中元數據的發佈通過一個特殊的服務行爲ServiceMetadataBehavior實現。在上面提供的服務寄宿代碼中,我們爲創建的ServiceHost添加了ServiceMetadataBehavior,並採用了基於HTTP-GET的元數據獲取方式,元數據的發佈地址通過ServiceMetadataBehavior的HttpGetUrl指定。在調用ServiceHost的Open方法對服務成功寄宿後,我們可以通過該地址獲取服務相關的元數據。在IE地址欄上鍵入http://127.0.0.1:9999/calculatorservice/metadata,你將會得到以WSDL形式體現的服務元數據,如圖4所示。
圖4 通過HTTP-GET的方式獲取WCF服務的元數據
在進行真正的WCF應用開發時,一般不會直接通過編碼的方式進行終結點的添加和服務行爲的定義,而是通過配置的方式進行。上面添加終結點和定義服務行爲的代碼可以用下面的配置代替:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <behaviors>
5: <serviceBehaviors>
6: <behavior name="metadataBehavior">
7: <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:9999/calculatorservice/metadata" />
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <services>
12: <service behaviorConfiguration="metadataBehavior" name="Artech.WcfServices.Services.CalculatorService">
13: <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
14: </service>
15: </services>
16: </system.serviceModel>
17: </configuration>
對於初學者來說,WCF的配置顯得過於複雜,直接對配置文件進行手工編輯不太現實。在這種情況下,可以直接使用VS提供的配置工具。你可以通過VS的工具(Tools)菜單,選擇“WCF Service Configuration Editor”子項,開啓這樣的一個配置編輯器,如圖5所示。
如果採用了上訴的配置,服務寄宿代碼將會得到極大的精簡,只需包含下面幾行代碼:
1: namespace Artech.WcfServices.Hosting
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
8: {
9: host.Opened += delegate
10: {
11: Console.WriteLine("CalculaorService已經啓動,按任意鍵終止服務!");
12: };
13:
14: host.Open();
15: Console.Read();
16: }
17: }
18: }
19: }
圖5 如何獲得WCF服務配置編輯器
步驟五:創建客戶端調用服務
服務被成功寄宿後,服務端便開始了服務調用請求的監聽工作。此外,服務寄宿將服務描述通過元數據的形式發佈出來,相應的客戶端就可以獲取這些元數據創建客戶端程序進行服務的消費。在VS下,當我們添加服務引用的時候,VS在內部幫我們實現元數據的獲取,並藉助這些元數據通過代碼生成工具(SvcUtil.exe)自動生成用於服務調用的服務代理相關的代碼和相應的配置。
在運行服務寄宿程序(Hosting.exe)的情況下,右鍵點擊Client項目,在彈出的上下文菜單中選擇“添加服務引用(Add Service References)”,如圖6所示的添加服務引用的對話會顯示出來。在地址欄上鍵入服務元數據發佈的源地址:http://127.0.0.1:9999/calculatorservice/metadata,並指定一個命名空間,點擊OK按鈕,VS爲爲你生成一系列用於服務調用的代碼和配置。
圖6 添加服務引用
在一系列自動生成的類中,包含一個服務契約接口、一個服務代理對象和其他相關的類。被客戶端直接用於服務調用的是一個繼承自ClientBase<CalculatorService>並實現了CalculatorService接口(CalculatorService爲客戶端生成的服務契約接口類型)的服務代理類。ClientBase<CalculatorService>的定義如下所示:
1: namespace Artech.WcfServices.Client.CalculatorServices
2: {
3: //其他類型成員
4: [System.Diagnostics.DebuggerStepThroughAttribute()]
5: [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
6: public partial class CalculatorServiceClient : System.ServiceModel.ClientBase<Artech.WcfServices.Client.CalculatorServices.CalculatorService>, Artech.WcfServices.Client.CalculatorServices.CalculatorService {
7:
8: public CalculatorServiceClient() {
9: }
10:
11: public CalculatorServiceClient(string endpointConfigurationName) :
12: base(endpointConfigurationName) {
13: }
14:
15: public CalculatorServiceClient(string endpointConfigurationName, string remoteAddress) :
16: base(endpointConfigurationName, remoteAddress) {
17: }
18:
19: public CalculatorServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
20: base(endpointConfigurationName, remoteAddress) {
21: }
22:
23: public CalculatorServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
24: base(binding, remoteAddress) {
25: }
26:
27: public double Add(double x, double y) {
28: return base.Channel.Add(x, y);
29: }
30:
31: public double Subtract(double x, double y) {
32: return base.Channel.Subtract(x, y);
33: }
34:
35: public double Multiply(double x, double y) {
36: return base.Channel.Multiply(x, y);
37: }
38:
39: public double Divide(double x, double y) {
40: return base.Channel.Divide(x, y);
41: }
42: }
我們可以創建CalculatorServiceClient對象,執行相應方法調用服務操作。客戶端進行服務調用的代碼如下:
1: using System;
2: using Artech.WcfServices.Client.CalculatorServices;
3: namespace Artech.WcfServices.Client
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: using (CalculatorServiceClient proxy = new CalculatorServiceClient())
10: {
11: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
12: Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
13: Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
14: Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));
15: }
16: }
17: }
18: }
運行後輸出:
x + y = 3 when x = 1 and y = 2
x - y = -1 when x = 1 and y = 2
x * y = 2 when x = 1 and y = 2
x / y = 0.5 when x = 1 and y = 2
客戶端通過服務代理對象進行服務的調用,上面的例子通過創建自動生成的、繼承自ClientBase<T>的類型對象進行服務調用。實際上,我們還具有另外一種創建服務代理的方法,就是通過ChannelFactory<T>。此外,WCF採用基於契約的服務調用方法,從上面的例子我們也可以看到,VS在進行服務引用添加的過程中,會在客戶端創建一個與服務端等效的服務契約接口。在我們的例子中,由於服務端和客戶端都是在同一個解決方案中,完全可以讓服務端和客戶端引用相同的契約。
爲了演示這種場景,我們將添加的服務引用移除,併爲Client項目添加對Contracts項目的引用。藉助於這個服務契約,並通過ChannelFactory<ICalculator>創建服務代理對象,直接進行相應的服務調用。下面的代碼演示了基於ChannelFacotory<T>進行服務代理的創建和服務調用的方式。
1: using System;
2: using System.ServiceModel;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:9999/calculatorservice"))
11: {
12: ICalculator proxy = channelFactory.CreateChannel();
13: using (proxy as IDisposable)
14: {
15: Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
16: Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
17: Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
18: Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));
19: }
20: }
21: }
22: }
23: }
由於終結點是WCF進行通信的唯一手段,ChannelFactory<T>本質上是通過指定的終結點創建用於進行服務調用的服務代理。在上面的代碼中,在創建ChannelFactory<T>的時候再在構造函數中指定終結點的相關要素(契約通過範型類型表示,地址和綁定則通過參數指定)。在真正的WCF應用中,大都採用配置的方式進行終結點的定義。我們可以通過下面的配置指定終結點的三要素,併爲相應的終結點指定一個終結點配置名稱(calculatorservice)。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <client>
5: <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" name="calculatorservice" />
6: </client>
7: </system.serviceModel>
8: </configuration>
那麼在創建ChannelFactory<T>的時候,就無須再指定終結點的綁定和地址了,而只須制定對應的終結點配置名稱。
1: using System;
2: using System.ServiceModel;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>( "calculatorservice"))
11: {
12: //省略代碼
13: }
14: }
15: }
16: }
步驟六:通過IIS寄宿服務
上面演示了通過自我寄宿的方式寄宿服務,現在我們來演示如何將WCF服務寄宿到IIS中。寄宿IIS的服務寄宿比較簡單,基本上包含兩個步驟:爲WCF服務創建.svc文件和創建IIS虛擬目錄。
1、爲WCF服務創建.svc文件
我們知道,每一個ASP.NET Web服務都具有一個.asmx文本文件,客戶端通過訪問.asmx文件實現對相應Web服務的調用。與之類似,每個WCF服務也具有一個對應的文本文件,其文件擴展名爲.svc。基於IIS的服務寄宿要求相應的WCF服務具有相應的.svc文件,.svc文件部署於IIS站點中,對WCF服務的調用體現在對.svc文件的訪問上。
.svc文件的內容很簡單,僅僅包含一個ServiceHost指令(Directive),該指令具有一個必須的Service屬性和一些可選的屬性。所以最簡單的.svc僅僅具有一個包含Service屬性(該屬性指明瞭相應的WCF服務的有效類型)的ServiceHost指令。CalculatorService對應的.svc如下所示,我們把該.svc放在Services項目的根目錄下,並將文件命名爲CalculatorService.svc。
1: <%@ServiceHost Service="Artech.WcfServices.Services.CalculatorService"%>
2、爲WCF服務創建虛擬目錄
和一般的寄宿於IIS下的Web應用一樣,需要在IIS下創建相應的虛擬目錄。在本應用中,爲了方便,我們直接把Services項目的根目錄映射爲IIS虛擬目錄,並把該虛擬目錄的命名爲WcfServices。
接下來需要爲通過IIS寄宿的CalculatorService創建配置文件,我們只須在Services的根目錄下創建一個Web.config,將WCF相應的配置添加到該配置文件中即可。Web.config所有配置內容如下所示,可以看出,這基本上和上面通過自我寄宿方式定義的配置一致。唯一不同的是在添加的終結點中無須指定地址,因爲.svc所在的地址就是服務的地址。也就是說,CalculatorService的地址爲http://127.0.0.1/wcfservices/calculatorservice.svc。你可以通過http://127.0.0.1/wcfservices/calculatorservice.svc?wsdl得到相應的元數據。由於WSHttpBinding在默認情況下采用Windows認證,所以在IIS中將Windows集成認證開啓。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <behaviors>
5: <serviceBehaviors>
6: <behavior name="metadataBehavior">
7: <serviceMetadata httpGetEnabled="true"/>
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <services>
12: <service behaviorConfiguration="metadataBehavior" name="Artech.WcfServices.Services.CalculatorService">
13: <endpoint binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
14: </service>
15: </services>
16: </system.serviceModel>
17: </configuration>
由於在創建Services項目的時候,我們並不曾引用System.ServiceMode程序集,所以須要加上這樣一個引用。此外,一個Web應用在運行的時候會默認從位於根目錄下的Bin目錄加載程序集,而默認的情況下,我們編譯後的程序集會自動保存到Bin\Debug|Release目錄下,所以須要通過VS修改Services項目屬性,將編譯輸出目錄設置成Bin。
客戶端僅僅須要修改終結點的地址,從而轉向對寄宿於IIS下的CalculatorService的訪問,該地址即爲.svc文件的網絡地址:http://127.0.0.1/wcfservices/calculatorservice.svc。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <client>
5: <endpoint address="http://127.0.0.1/wcfservices/calculatorservice.svc" binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" name="calculatorservice" />
6: </client>
7: </system.serviceModel>
8: </configuration>