在本例中,我們將實現一個簡單的計算服務,提供基本的加、減、乘、除運算,通過客戶端和服務端運行在同一台機器上的不同進程實現。
一、新建WCF服務
1、新建一個空白解決方案,解決方案名稱為“WCFSolution”。
2、解決方案右鍵->添加->類庫項目,類庫名稱為CalculateWcfService。
3、創建服務契約
WCF采用基於契約的交互方式實現了服務的自制。服務契約:是相關操作的集合。契約就是雙方或多方就某個關注點達成的一種共識,是一方向另一方的一種承諾。簽署了某個契約就意味着自己有義務履行契約中的各項規定,一旦違約勢必影響契約雙方的正常交互。我們主張通過抽象將接口和實現相互分離,鼓勵接口的依賴,避免基於實現的依賴。接口是穩定的,而實現則是易變的,基於接口的服務調用能夠更有效地應對實現的變化帶來的影響。接口從本質上講就是一種契約,當某個類實現了某個接口,就相對於簽署了一份契約。所以契約關心的是“我能做到”,不在於“我如何做到”。所以,服務契約是以接口的形式進行定義的。
下面的代碼中,定義了一個接口,通過在接口上應用System.ServiceModel命名空間下的ServiceContractAttribute特性將ICalculateService接口定義成服務契約。在應用ServiceContractAttribute特性的同時,還可以指定服務契約的名稱和命名空間。每個服務契約都有一個確定的名稱,當在一個接口上應用了該屬性以后,默認的名稱就是接口的名稱。我們可以通過Name屬性顯示地指定需要的名稱。
NameSpace:服務契約的命名空間,其作用是解決命名沖突的問題,提倡將你所在的公司名稱或項目名稱的URN作為命名空間。WCF默認的命名空間是:http://tempuri.org/。
服務契約是一組相關服務操作的集合,當我們在一個接口上應用了ServiceContractAttribute,便賦予了服務契約的屬性。但是,對於這樣一個類型,它的成語並不會自動成為契約的服務操作,只有應用了OperationContractAttribute特性后,相應的方法成員才能成為能夠通過服務調用方式訪問的服務操作。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.ServiceModel; 7 8 namespace CalculateWcfService 9 { 10 /// <summary> 11 /// 定義服務契約:通過接口實現契約 12 /// </summary> 13 [ServiceContract(Name = "CalculateService")] 14 public interface ICalculateService 15 { 16 /// <summary> 17 /// 定義操作契約 18 /// </summary> 19 /// <param name="num1"></param> 20 /// <param name="num2"></param> 21 /// <returns></returns> 22 [OperationContract(Name = "AddOperation")] 23 double AddOperation(double num1, double num2); 24 25 /// <summary> 26 /// 定義操作契約 27 /// </summary> 28 /// <param name="num1"></param> 29 /// <param name="num2"></param> 30 /// <returns></returns> 31 [OperationContract(Name = "SubOperation")] 32 double SubOperation(double num1, double num2); 33 34 /// <summary> 35 /// 定義操作契約 36 /// </summary> 37 /// <param name="num1"></param> 38 /// <param name="num2"></param> 39 /// <returns></returns> 40 [OperationContract(Name = "MulOperation")] 41 double MulOperation(double num1, double num2); 42 43 /// <summary> 44 /// 定義操作契約 45 /// </summary> 46 /// <param name="num1"></param> 47 /// <param name="num2"></param> 48 /// <returns></returns> 49 [OperationContract(Name = "DivOperation")] 50 double DivOperation(double num1, double num2); 51 } 52 }
4、創建服務:新建一個類,實現第3步中創建的接口
當服務契約成功創建時,我們需要通過實現服務契約來創建具體的WCF服務。WCF服務CalculateService實現了服務契約接口ICalculateService,實現了接口里面定義的所有服務操作。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace CalculateWcfService 8 { 9 public class CalculateService:ICalculateService 10 { 11 /// <summary> 12 /// 加 13 /// </summary> 14 /// <param name="num1"></param> 15 /// <param name="num2"></param> 16 /// <returns></returns> 17 public double AddOperation(double num1, double num2) 18 { 19 return num1 + num2; 20 } 21 22 /// <summary> 23 /// 減 24 /// </summary> 25 /// <param name="num1"></param> 26 /// <param name="num2"></param> 27 /// <returns></returns> 28 public double SubOperation(double num1, double num2) 29 { 30 return num1 - num2; 31 } 32 33 /// <summary> 34 /// 乘 35 /// </summary> 36 /// <param name="num1"></param> 37 /// <param name="num2"></param> 38 /// <returns></returns> 39 public double MulOperation(double num1, double num2) 40 { 41 return num1 * num2; 42 } 43 44 /// <summary> 45 /// 除 46 /// </summary> 47 /// <param name="num1"></param> 48 /// <param name="num2"></param> 49 /// <returns></returns> 50 public double DivOperation(double num1, double num2) 51 { 52 return num1 / num2; 53 } 54 } 55 }
二、定義宿主寄宿WCF服務
WCF服務不能孤立地存在,必須要寄宿於一個運行着的進程中,我們把承載WCF服務的進程稱為宿主,為服務指定宿主的過程稱為服務寄宿(Service Hosting)。服務寄宿的目的就是開啟一個進程,為WCF服務提供一個運行的環境。WCF服務典型的宿主包括以下四種:
"Self-Hosting" in a Managed Application(自托管宿主)
Managed Windows Services(Windows Services宿主)
Internet Information Services(IIS宿主)
Windows Process Activation Service(WAS宿主)
1、以自托管宿主的方式寄宿。
1.1 通過代碼的方式配置WCF服務
利用WCF提供的ServiceHost<T>提供的Open()和Close()方法,可以便於開發者在控制台、Windows應用程序乃自於ASP.NET應用程序中托管服務,不管自宿主的環境是何種應用程序,實質上托管服務的方式都是一致的。例如在控制台應用程序中:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.ServiceModel; 7 using System.ServiceModel.Description; 8 using CalculateWcfService; 9 10 namespace ConsoleHosting 11 { 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 17 using (ServiceHost host = new ServiceHost(typeof(CalculateService), new Uri("http://127.0.0.1:9099/CalculateWcfService"))) 18 { 19 BasicHttpBinding bind = new BasicHttpBinding 20 { 21 Name = "basichttpbinding" 22 }; 23 host.AddServiceEndpoint(typeof(ICalculateService), bind, "CalculateWcfService"); 24 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) 25 { 26 ServiceMetadataBehavior behavior = new ServiceMetadataBehavior() { 27 HttpGetEnabled=true 28 }; 29 host.Description.Behaviors.Add(behavior); 30 } 31 host.AddServiceEndpoint(typeof(IMetadataExchange), bind, "mex"); 32 if (host.State != CommunicationState.Opened) 33 { 34 host.Open(); 35 } 36 37 Console.WriteLine("服務已啟動"); 38 Console.Read(); 39 } 40 } 41 } 42 }
WCF服務寄宿通過一個特殊的對象完成:ServiceHost。在上面的例子中,基於WCF服務的類型(typeof(CalculateService))創建了ServiceHost對象,並添加了一個終結點,終結點地址為http://127.0.0.1:9099/CalculateWcfService,采用了BasicHttpBinding的綁定方式,並指定了服務契約的類型ICalculateService。
松耦合是SOA的一個基本的特征,WCF應用中客戶端和服務端的松耦合體現在客戶端只須要了解WCF服務基本的描述,而無須知道具體的實現細節,就可以實現正常的服務調用。WCF服務的描述通過元數據(Metadata)的形式發布出來。WCF中元數據的發布通過一個特殊的服務行為ServiceMetadataBehavior實現。在上面提供的服務寄宿代碼中,我們為創建的ServiceHost添加了ServiceMetadataBehavior,並采用了基於HTTP-GET的元數據獲取方式。在調用ServiceHost的Open方法對服務成功寄宿后,我們可以通過該地址獲取服務相關的元數據。在IE地址欄上鍵入http://127.0.0.1:9099/CalculateWcfService?wsdl,你將會得到以WSDL形式體現的服務元數據,如下圖所示。
由於ServiceHost實例是被創建在應用程序域中,因此我們必須保證宿主進程在調用服務期間不會被關閉,因此我們利用Console.Read()來阻塞進程,以使得控制台應用程序能夠一直運行,直到認為地關閉應用程序。如果是Windows應用程序,則可以將創建ServiceHost實例的代碼放在主窗體的相關代碼中,保證服務宿主不會被關閉。
在通常的企業應用中,我們很少會采用自宿主方式托管服務,這是因為這種方式必須要在應用程序運行下,客戶端才能夠調用服務,且並不便於隨時啟動和停止服務。除了不具有易用性與易管理性之外,在可靠性、性能等諸多方面受到很多限制。但由於它簡單、易於實現,因而往往用於開發期間的調試或演示環境。
注意:自托管宿主支持所有的綁定。
1.2 通過配置文件的方式配置WCF服務
在進行真正的WCF應用開發時,一般不會直接通過編碼的方式進行終結點的添加和服務行為的定義,而是通過配置文件的方式進行。
配置文件基本結構如下圖所示:
WCF的配置文件共分為兩部分:服務端配置與客戶端配置。兩者由於功能的不同,在配置文件的使用上也略有不同。
1.2.1 WCF的服務端配置
服務端的配置文件主要包括endpoint、binding、behavior的配置。一個標准的服務端配置文件所包含的主要xml配置節如下所示:
<system.ServiceModel>
<services>
<service>
<endpoint/>
</service>
</services>
<bindings>
<!—定義一個或多個系統提供的binding元素,例如<basicHttpBinding> -->
<!—也可以是自定義的binding元素,如<customBinding>. -->
<binding>
<!—例如<BasicHttpBinding>元素. -->
</binding>
</bindings>
<behaviors>
<!—一個或多個系統提供的behavior元素. -->
<behavior>
<!—例如<throttling>元素. -->
</behavior>
</behaviors>
</system.ServiceModel>
1.2.1.1 <services>配置節點
在<services>配置節中可以定義多個服務,每一個服務都被放到<service>配置節中,WCF的宿主程序可以通過配置文件找到這些定義的服務並發布這些服務。
<service>配置節包含name和behaviorConfiguration屬性。其中,name配置了實現Service Contract的類型名。類型名必須是完整地包含了命名空間和類型名。而behaviorConfiguration的配置值則與其后的<behaviors>配置節的內容有關。<endpoint>是<service>配置節的主體,其中,<endpoint>配置節包含了endpoint的三個組成部分:Address、Binding和Contract。由於具體的binding配置是在<bindings>配置節中完成,因而,在<endpoint>中配置了bindingConfiguration屬性,指向具體的binding配置。如下所示:
<services>
<service name="CalculateWcfService.CalculateService" behaviorConfiguration="MyBehavior">
<endpoint address=""
binding="netTcpBinding"
bindingConfiguration="DuplexBinding"
contract="CalculateWcfService.ICalculateService" />
</service>
</services>
1.2.1.2 <Endpoint>配置節點
終結點由地址(Address)、綁定(Binding)和契約(Contract)三要素組成。由於三要素應為首字母分別為ABC,所以終結點一般簡稱為ABC。
address:指定服務的統一資源標識符(URI),它可以是一個絕對地址或者是一個相對於服務基址給定的地址。如果設置為空字符串,則表示在創建服務的ServiceHost時,終結點在指定的基址上可用。
binding:通常,指定一個類似WsHttpBinding的系統提供的綁定,但也可以指定一個用戶定義的綁定。指定的綁定確定傳輸協議類型、安全和使用的編碼,以及是否支持或啟用可靠會話、事務或流。
bindingConfiguration:如果必須修改綁定的默認值,則可通過在bindings元素中配置相應的binding元素來執行此操作 此屬性應賦予與用於更改默認值的binding 元素的name 屬性相同的值。
contract:指定定義協定的接口。 這是在由service 元素的name 屬性指定的公共語言運行庫(CLR) 類型中實現的接口。
我們也可以在一個<service>節點中定義多個endpoint,例如:
<services>
<service
name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<endpoint address=""
binding="wsHttpBinding"
contract="Microsoft.ServiceModel.Samples.ICalculator" />
<endpoint address="mex"
binding="mexHttpBinding"
contract=" Microsoft.ServiceModel.Samples.IMetadataExchange" />
</service>
</services>
如果address值為空,那么endpoint的地址就是默認的基地址(Base Address)。例如ICalculator服務的地址就是http://localhost/servicemodelsamples/service.svc,而IMetadataExchange服務的地址則為http://localhost/servicemodelsamples/service.svc/mex。這里所謂的基地址可以在<service>中通過配置<host>來定義:
<service
name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<host>
<baseAddresses>
<add baseAddress=
"http://localhost/ServiceModelSamples/service.svc"/>
</baseAddresses>
</host>
<endpoint … />
</service>
1.2.1.3 <behaviors>配置節
當我們在定義一個實現了Service Contract的類時, binding和address信息是客戶端必須知道的,否則無法調用該服務。然而,如果需要指定服務在執行方面的相關特性時,就必須定義服務的behavior。在WCF中,定義behavior就可以設置服務的運行時屬性,甚至於通過自定義behavior插入一些自定義類型。例如通過指定ServiceMetadataBehavior,可以使WCF服務對外公布Metadata。配置如下:
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
<serviceBehaviors>
<behaviors>
在WCF中,behavior被定義為Attribute,其中,System.ServiceModel.ServiceBehaviorAttribute和System.ServiceModel.OperationBehaviorAttribute是最常用的behavior。雖然,behavior作為Attribute可以通過編程的方式直接施加到服務上,但出於靈活性的考慮,將behavior定義到配置文件中才是最好的設計方式。
利用ServiceBehavior與OperationBehavior可以控制服務的如下屬性:
1、 對象實例的生命周期;
2、 並發與異步處理;
3、 配置行為;
4、 事務行為;
5、 序列化行為;
6、 元數據轉換;
7、 會話的生命周期;
8、 地址過濾以及消息頭的處理;
9、 模擬(Impersonation);
例如,通過ServiceBehavior設置對象實例的生命周期:
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<instanceContextMode httpGetEnabled="true" httpGetUrl=""/>
</behavior>
<serviceBehaviors>
<behaviors>
除了直接手動修改配置文件以外,還可以直接使用VS提供的配置工具。可以通過VS的工具(Tools)菜單,選擇“WCF 服務配置編輯”子項,開啟這樣的一個配置編輯器,如下圖所示:
或者在配置文件上直接點右鍵,選擇“編輯WCF配置”打開WCF配置編輯器,如下圖所示:
如果采用了配置文件的方式,服務寄宿代碼將會得到極大的精簡,只需包含下面幾行代碼:
1 using System.Collections.Generic; 2 using System.Linq; 3 using System.Text; 4 using System.Threading.Tasks; 5 using System.ServiceModel; 6 using System.ServiceModel.Description; 7 using CalculateWcfService; 8 9 namespace ConsoleHosting 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 using (ServiceHost host = new ServiceHost(typeof(CalculateService))) 16 { 17 host.Open(); 18 Console.WriteLine("服務已啟動"); 19 Console.Read(); 20 } 21 } 22 } 23 }
2、以Windows Services宿主的方式寄宿。
Windows Services宿主則完全克服了自托管宿主的缺點,它便於管理者方便地啟動或停止服務,且在服務出現故障之后,能夠重新啟動服務。我們還可以通過Service Control Manager(服務控制管理器),將服務設置為自動啟動方式,省去了服務的管理工作。此外,Windows Services自身還提供了一定的安全性以及檢測機制和日志機制。
Windows Services宿主的實現也非常簡單。我們可以在Visual Studio中創建Windows Services項目。在創建項目之后,就可以創建一個繼承了System.ServiceProcess.ServiceBase類的Windows服務類。Windows服務類繼承了ServiceBase類的OnStart()和OnStop()方法,完成Windows服務的啟動與停止。我們可以重寫這兩個方法,將ServiceHost的啟動與關閉對應地放入這兩個方法的實現中。
1 using CalculateWcfService; 2 using System; 3 using System.Collections.Generic; 4 using System.ComponentModel; 5 using System.Data; 6 using System.Diagnostics; 7 using System.Linq; 8 using System.ServiceModel; 9 using System.ServiceModel.Description; 10 using System.ServiceProcess; 11 using System.Text; 12 using System.Threading.Tasks; 13 14 namespace WindowsServiceHosting 15 { 16 public partial class CalculateService : ServiceBase 17 { 18 public CalculateService() 19 { 20 InitializeComponent(); 21 } 22 23 protected override void OnStart(string[] args) 24 { 25 using (ServiceHost host = new ServiceHost(typeof(CalculateService), new Uri("http://127.0.0.1:9099/CalculateWcfService"))) 26 { 27 BasicHttpBinding bind = new BasicHttpBinding 28 { 29 Name = "basichttpbinding" 30 }; 31 host.AddServiceEndpoint(typeof(ICalculateService), bind, "CalculateWcfService"); 32 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) 33 { 34 ServiceMetadataBehavior behavior = new ServiceMetadataBehavior() 35 { 36 HttpGetEnabled = true 37 }; 38 host.Description.Behaviors.Add(behavior); 39 } 40 host.AddServiceEndpoint(typeof(IMetadataExchange), bind, "mex"); 41 if (host.State != CommunicationState.Opened) 42 { 43 host.Open(); 44 } 45 } 46 } 47 48 protected override void OnStop() 49 { 50 51 } 52 } 53 }
如果在企業應用中要使用WCF技術,最佳的宿主方式我認為就是Windows Services,尤其是服務器的操作系統不是Vista的情況之下。它便於服務的管理,能夠維持服務長時期的運行,同時它還支持所有的綁定,因而受到的限制最小。然而,這種方式唯一的缺點卻是對宿主的部署相對比較復雜,必須通過.NET提供的Installutil.exe工具完成對服務宿主的安裝(也可以通過安裝包的自定義操作完成)。
將WCF服務寄宿到Windows服務中,也可以采用配置文件的方式實現,配置方式和自托管寄宿中的配置方式一樣。
3、以IIS宿主的方式寄宿WCF服務
我們知道,每一個ASP.NET Web服務都具有一個.asmx文本文件,客戶端通過訪問.asmx文件實現對相應web服務的調用。與之類似,每個WCF服務也具有一個對應的文本文件,其文件擴展名為.svc。基於IIS的服務寄宿要求相應的WCF服務具有相應的.svc文件,.svc文件部署於IIS站點中,對WCF服務的調用體現在對.svc文件的訪問上。
第一步、新建WCF服務站點
在解決方案上右擊,選擇“添加”->“新建網站”,打開新建網站對話框。在“添加新網站”對話框中,我們選擇“WCF服務”,並把網站的名子命名為“CalculateIISHost”
建立起來的新的WCF服務站點的App_Code文件中自動為我們生成兩個類文件:IService.cs和Service.cs。這兩個文件對我們來說沒有用,我們刪掉。
第二步、添加引用
在剛剛創建的WCF服務站點上添加對WCF服務庫項目--CalculateWcfService項目的引用。
第三步、配置Service.svc文件
雙擊Service.svc文件,我們可以看到它的聲明指示如下:
<%@ ServiceHost Language="C#" Debug="true" Service="Service" CodeBehind="~/App_Code/Service.cs" %>
由於在第二步中我們已經把IService.cs和Service.cs兩個文件已經刪除了,所以這里的聲明指示內容修改一下,讓這個Service.svc文件的后台代碼指向我們上次創建的WCF服務庫項目--CalculateWcfService項目中的類,改后的代碼如下:
<%@ ServiceHost Language="C#" Debug="true" Service="CalculateWcfService.CalculateService" %>
我們把其中的Service屬性指定為CalculateWcfService命名空間下的CalculateService類,並把CodeBehind屬性刪去了。
第四步、配置此WCF服務站點與WCF服務庫項目之間的類的對應。
雖然在第二步中我們添加了對Services項目的引用,並且在第三步中修改了Service.svc的類的對應,但此時我們的WCF服務站點並不能把WCF服務庫中的服務和終結點發布出來,還需要我們對web.config進行一系列的配置工作。web.config配置文件的配置和上面講過的配置方式一樣,在此不再重復。
第五步、測試運行WCF服務站點
在Service.svc上右擊,選擇“在瀏覽器中查看”,在IE中運行此服務。
由此我們看到我們可以在ASP.NET Development Server中發布我們的WCF服務了。
第六步、在IIS布署此WCF服務站點。
在IIS建立Web應用程,指向我們的WCF服務站點所在的目錄。然后在IIS運行我們發布的WCF服務 。
到此為至我們在IIS中發布WCF服務成功
4、創建客戶端調用服務
服務被成功寄宿后,服務端便開始了服務調用請求的監聽工作。此外,服務寄宿將服務描述通過元數據的形式發布出來,相應的客戶端就可以獲取這些元數據創建客戶端程序進行服務的消費。在VS下,當我們添加服務引用的時候,VS在內部幫我們實現元數據的獲取,並借助這些元數據通過代碼生成工具(SvcUtil.exe)自動生成用於服務調用的服務代理相關的代碼和相應的配置。
第一步、創建客戶端
在解決方案上面右鍵,選擇“添加”->"新建項目",打開新建項目對話框,在“添加新項目”對話框中,選擇“控制台應用程序”,名稱為“ConsoleClientTest”。
第二步、添加服務引用
在服務寄宿程序運行的情況下,為客戶端添加服務引用。在客戶端程序上面,右鍵“引用”,選擇“添加服務引用”,打開添加服務引用對話框,在地址里面輸入地址“http://127.0.0.1:9099/CalculateService”,並指定一個命名空間,點擊確定。
第三步、編寫客戶端測試代碼
1 namespace ConsoleClientTest 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //引用服務 8 ServiceReference1.CalculateServiceClient serviceClient = new ServiceReference1.CalculateServiceClient(); 9 Console.WriteLine(string.Format("x + y = {2} when x = {0} and y = {1}", 1, 2, serviceClient.AddOperation(1,2))); 10 Console.WriteLine(string.Format("x - y = {2} when x = {0} and y = {1}", 1, 2, serviceClient.SubOperation(1, 2))); 11 Console.WriteLine(string.Format("x * y = {2} when x = {0} and y = {1}", 1, 2, serviceClient.MulOperation(1, 2))); 12 Console.WriteLine(string.Format("x / y = {2} when x = {0} and y = {1}", 1, 2, serviceClient.DivOperation(1, 2))); 13 14 Console.ReadKey(); 15 } 16 } 17 }
運行后輸出結果如下: