創建一個簡單的WCF程序
2014-06-05
創建一個簡單的WCF程序
步驟一: 構建整個解決方案
步驟二:創建服務契約
步驟三:創建服務
步驟四:通過自我寄宿的方式寄宿服務
步驟五:創建客戶端調用服務
創建一個簡單的WCF程序 [1]
下面創建一個簡單的WCF,以便對WCF有個直觀的了解
在這個例子中,將實現一個簡單的計算服務(CalculatorService),提供加的運算。和傳統的分布式通信框架一樣,WCF本質上提供一個跨進程、跨機器以致跨網絡的服務調用。在本例中,客戶端和服務通過運行在相同的同一台機器上不同進程模擬,圖1體現了客戶端和服務端進程互相調用的關系。
圖1 計算服務應用運行環境
在這個例子中,采用了兩種服務寄宿方式:
- 通過自我寄宿(Self-Hosting)的方式創建一個控制台應用作為服務的宿主(寄宿進程為Hosting.exe);
- 通過IIS寄宿方式將服務寄宿於IIS中(寄宿進程為IIS的工作進行W3wp.exe)。
客戶端通過另一個控制台應用模擬(進程為Client.exe)
步驟一: 構建整個解決方案
通過VS 2010創建一個空白的解決方案,添加如下四個項目。項目的類型、承載的功能和相互引用關系如下,整個項目在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包含四種類型的契約:服務契約、數據契約、消息契約和錯誤契約,這里着重於服務契約。從功能上講,服務契約抽象了服務提供的所有操作;而站在消息交換的角度來看,服務契約則定義了基於服務調用的消息交換過程中,請求消息和回復消息的結構,以及采用的消息交換模式。

1 using System.ServiceModel; 2 namespace WcfServices.Contracts 3 { 4 [ServiceContract] 5 public interface ICalculator 6 { 7 [OperationContract] 8 double Add(double x, double y); 9 } 10 }
步驟三:創建服務
服務實現了服務契約接口ICalculator

1 using WcfServices.Contracts; 2 namespace 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 }
步驟四:通過自我寄宿的方式寄宿服務
WCF服務需要依存一個運行着的進程(宿主),服務寄宿就是為服務指定一個宿主的過程。WCF是一個基於消息的通信框架,采用基於終結點(Endpoint)的通信手段。終結點由地址(Address)、綁定(Binding)和契約(Contract)三要素組成。由於三要素應為首字母分別為ABC,所以就有了易於記憶的公式:Endpoint = ABC
- 地址(Address):地址決定了服務的位置,解決了服務尋址的問題;
- 綁定(Binding):綁定實現了通信的所有細節,包括網絡傳輸、消息編碼,以及其他為實現某種功能(比如安全、可靠傳輸、事務等)對消息進行的相應處理。WCF中具有一系列的系統定義綁定,比如BasicHttpBinding、WsHttpBinding、NetTcpBinding等;
- 契約(Contract):契約是對服務操作的抽象,也是對消息交換模式以及消息結構的定義。

1 using System; 2 using System.ServiceModel; 3 using System.ServiceModel.Description; 4 using WcfServices.Contracts; 5 using WcfServices.Services; 6 7 namespace WcfServices.Hosting 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 /** 14 * Create a host to provide service. 15 * ServiceType is HelloWorld 16 * BaseAddress is in localhost 17 */ 18 using (ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri("http://localhost:8080/calculatorservice"))) 19 { 20 /** 21 * Add an serviceEndpoint to this host 22 * Implemented Contract is ICalculator 23 * Binding pattern is BasicHttpBinding 24 * Address 'SVC' is a relative address 25 */ 26 host.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "SVC"); 27 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) 28 { 29 ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); 30 behavior.HttpGetEnabled = true; 31 behavior.HttpGetUrl = new Uri("http://localhost:8080/calculatorservice"); 32 host.Description.Behaviors.Add(behavior); 33 } 34 host.Opened += delegate 35 { 36 Console.WriteLine("CalculaorService have started, press any key to stop it!"); 37 }; 38 39 host.Open(); 40 Console.Read(); 41 } 42 } 43 } 44 }
WCF服務寄宿通過一個特殊的對象完成:ServiceHost。在上面的例子中,基於WCF服務的類型(typeof(CalculatorService))創建了ServieHost對象,並添加了一個終結點。具體的地址為http://localhost:8080/calculatorservice,采用了WSHttpBinding,並指定了服務契約的類型ICalculator。
松耦合是SOA的一個基本的特征,WCF應用中客戶端和服務端的松耦合體現在客戶端只須要了解WCF服務基本的描述,而無須知道具體的實現細節,就可以實現正常的服務調用。
WCF服務的描述通過元數據(Metadata)的形式發布出來。WCF中元數據的發布通過一個特殊的服務行為ServiceMetadataBehavior實現。在上面提供的服務寄宿代碼中,我們為創建的ServiceHost添加了ServiceMetadataBehavior,並采用了基於HTTP-GET的元數據獲取方式,元數據的發布地址通過ServiceMetadataBehavior的HttpGetUrl指定。在調用ServiceHost的Open方法對服務成功寄宿后,也就是Hosting控制台程序正常運行后,如圖3所示。
圖3 運行寄宿wcf服務的控制台程序
在你的瀏覽器地址欄中,輸入http://localhost:8080/calculatorservice,效果圖4所示:
圖4 calculatorservice service運行正常
點擊頁面中的鏈接,出現WSDL格式的XML文件,如下代碼:

1 <?xml version="1.0" encoding="utf-8" ?> 2 - <wsdl:definitions name="CalculatorService" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"> 3 - <wsdl:types> 4 - <xsd:schema targetNamespace="http://tempuri.org/Imports"> 5 <xsd:import schemaLocation="http://localhost:8080/calculatorservice?xsd=xsd0" namespace="http://tempuri.org/" /> 6 <xsd:import schemaLocation="http://localhost:8080/calculatorservice?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> 7 </xsd:schema> 8 </wsdl:types> 9 + <wsdl:message name="ICalculator_Add_InputMessage"> 10 <wsdl:part name="parameters" element="tns:Add" /> 11 </wsdl:message> 12 - <wsdl:message name="ICalculator_Add_OutputMessage"> 13 <wsdl:part name="parameters" element="tns:AddResponse" /> 14 </wsdl:message> 15 - <wsdl:portType name="ICalculator"> 16 - <wsdl:operation name="Add"> 17 <wsdl:input wsaw:Action="http://tempuri.org/ICalculator/Add" message="tns:ICalculator_Add_InputMessage" /> 18 <wsdl:output wsaw:Action="http://tempuri.org/ICalculator/AddResponse" message="tns:ICalculator_Add_OutputMessage" /> 19 </wsdl:operation> 20 </wsdl:portType> 21 - <wsdl:binding name="BasicHttpBinding_ICalculator" type="tns:ICalculator"> 22 <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> 23 - <wsdl:operation name="Add"> 24 <soap:operation soapAction="http://tempuri.org/ICalculator/Add" style="document" /> 25 - <wsdl:input> 26 <soap:body use="literal" /> 27 </wsdl:input> 28 - <wsdl:output> 29 <soap:body use="literal" /> 30 </wsdl:output> 31 </wsdl:operation> 32 </wsdl:binding> 33 - <wsdl:service name="CalculatorService"> 34 - <wsdl:port name="BasicHttpBinding_ICalculator" binding="tns:BasicHttpBinding_ICalculator"> 35 <soap:address location="http://localhost:8080/calculatorservice/SVC" /> 36 </wsdl:port> 37 </wsdl:service> 38 </wsdl:definitions>
步驟五:創建客戶端調用服務
服務被成功寄宿后,服務端便開始了服務調用請求的監聽工作。此外,服務寄宿將服務描述通過元數據的形式發布出來,相應的客戶端就可以獲取這些元數據創建客戶端程序進行服務的消費。在VS下,當我們添加服務引用的時候,VS在內部幫我們實現元數據的獲取,並借助這些元數據通過代碼生成工具(SvcUtil.exe)自動生成用於服務調用的服務代理相關的代碼和相應的配置。
在運行服務寄宿程序(Hosting.exe)的情況下,右鍵點擊Client項目,在彈出的上下文菜單中選擇“添加服務引用(Add Service References)”,如圖5所示的添加服務引用的對話會顯示出來。在地址欄上鍵入服務元數據發布的源地址:http://127.0.0.1:8080/calculatorservice,並指定一個命名空間,點擊OK按鈕,VS為為你生成一系列用於服務調用的代碼和配置。
圖5 添加服務引用
在一系列自動生成的類中,包含一個服務契約接口、一個服務代理對象和其他相關的類。被客戶端直接用於服務調用的是服務代理類CalculatorClient,它繼承字ClientBase<ICalculator>:

1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // This code was generated by a tool. 4 // Runtime Version:4.0.30319.2034 5 // 6 // Changes to this file may cause incorrect behavior and will be lost if 7 // the code is regenerated. 8 // </auto-generated> 9 //------------------------------------------------------------------------------ 10 11 namespace WcfServices.Client.CalculatorService { 12 13 14 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 15 [System.ServiceModel.ServiceContractAttribute(ConfigurationName="CalculatorService.ICalculator")] 16 public interface ICalculator { 17 18 [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICalculator/Add", ReplyAction="http://tempuri.org/ICalculator/AddResponse")] 19 double Add(double x, double y); 20 } 21 22 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 23 public interface ICalculatorChannel : WcfServices.Client.CalculatorService.ICalculator, System.ServiceModel.IClientChannel { 24 } 25 26 [System.Diagnostics.DebuggerStepThroughAttribute()] 27 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] 28 public partial class CalculatorClient : System.ServiceModel.ClientBase<WcfServices.Client.CalculatorService.ICalculator>, WcfServices.Client.CalculatorService.ICalculator { 29 30 public CalculatorClient() { 31 } 32 33 public CalculatorClient(string endpointConfigurationName) : 34 base(endpointConfigurationName) { 35 } 36 37 public CalculatorClient(string endpointConfigurationName, string remoteAddress) : 38 base(endpointConfigurationName, remoteAddress) { 39 } 40 41 public CalculatorClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 42 base(endpointConfigurationName, remoteAddress) { 43 } 44 45 public CalculatorClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 46 base(binding, remoteAddress) { 47 } 48 49 public double Add(double x, double y) { 50 return base.Channel.Add(x, y); 51 } 52 } 53 }
我們可以創建CalculatorServiceClient對象,執行相應方法調用服務操作。客戶端進行服務調用的代碼如下:

1 using System; 2 using WcfServices.Client.CalculatorService; 3 namespace WcfServices.Client 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 using (CalculatorClient proxy = new CalculatorClient()) 10 { 11 Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2)); 12 Console.Read(); 13 } 14 } 15 } 16 }
運行客戶端后,結果如圖6所示:
圖6 客戶端結果
參考:
[1] 我的WCF之旅(1):創建一個簡單的WCF程序 http://www.cnblogs.com/artech/archive/2007/02/26/656901.html
[2] 一個WCF的HelloWorld程序 http://www.cnblogs.com/judastree/archive/2012/08/26/2657190.html