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程序集。

    步骤二:创建服务契约

    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章提供对服务契约的详细介绍。
      在进行真正的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服务的描述通过元数据(Metadata)的形式发布出来。WCF中元数据的发布通过一个特殊的服务行为ServiceMetadataBehavior实现。在上面提供的服务寄宿代码中,我们为创建的ServiceHost添加了ServiceMetadataBehavior,并采用了基于HTTP-GET的元数据获取方式,元数据的发布地址通过ServiceMetadataBehavior的HttpGetUrl指定。在调用ServiceHost的Open方法对服务成功寄宿后,我们可以通过该地址获取服务相关的元数据。在IE地址栏上键入http://127.0.0.1:9999/calculatorservice/metadata,你将会得到以WSDL形式体现的服务元数据,如图4所示。
      
      
    • 对于初学者来说,WCF的配置显得过于复杂,直接对配置文件进行手工编辑不太现实。在这种情况下,可以直接使用VS提供的配置工具。你可以通过VS的工具(Tools)菜单,选择“WCF Service Configuration Editor”子项,开启这样的一个配置编辑器,如图5所示。

       

    • 步骤五:创建客户端调用服务

      服务被成功寄宿后,服务端便开始了服务调用请求的监听工作。此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据创建客户端程序进行服务的消费。在VS下,当我们添加服务引用的时候,VS在内部帮我们实现元数据的获取,并借助这些元数据通过代码生成工具(SvcUtil.exe)自动生成用于服务调用的服务代理相关的代码和相应的配置。

      在运行服务寄宿程序(Hosting.exe)的情况下,右键点击Client项目,在弹出的上下文菜单中选择“添加服务引用(Add Service References)”,如图6所示的添加服务引用的对话会显示出来。在地址栏上键入服务元数据发布的源地址:http://127.0.0.1:9999/calculatorservice/metadata,并指定一个命名空间,点击OK按钮,VS为为你生成一系列用于服务调用的代码和配置。

    • 在一系列自动生成的类中,包含一个服务契约接口、一个服务代理对象和其他相关的类。被客户端直接用于服务调用的是一个继承自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: }

      步骤六:通过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>