學習WCF之路,長期更新

猴神發表於2019-06-12

我學習WCF之路:建立一個簡單的WCF程式

 

為了使讀者對基於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>

摘自;蔣金楠 
微信公眾賬號:大內老A
微博:www.weibo.com/artech

相關文章