之前做项目的时候没怎么用到WCF,对它也是一知半解,只知道要想学好.net,掌握面向服务,分布式开发,WCF是很重要的一部分。现在进入到一家新的公司,现在的项目里都用WCF来做前端与服务端通信,虽然还只是简单的应用,但是借此机会,我觉得该好好学习WCF了,我也从最基础的开始学起。今天要总结的是如何创建一个简单的WCF应用(一个web版计算器),虽然这只是一个非常简单的例子,但麻雀虽小,五脏俱全。它还是很好地展现了创建WCF程序的各个方面。我准备从以下几点来总结它。

1,项目搭建
2,创建服务契约
3,创建服务
4,服务寄宿
5,客户端调用服务

1,项目搭建

构建一个完整的WCF应用一般需要四个工程,如图所示。

wcf01

下面来详细介绍一下每个工程的作用。

1) WCFDemo.Contracts,一个类库项目,定义了服务契约(Service Contract),需要引用System.ServiceModel程序集,因为WCF的绝大部分实现和API定义在该程序集中。
2) WCFDemo.Services,一个类库项目,提供对WCF服务的实现。该工程中所有的WCF服务实现了定义在WCFDemo.Contracts中的契约。所以需要添加对WCFDemo.Contracts的引用。
3) WCFDemo.Host,一个Web Application项目,实现对定义在WCFDemo.Services项目中的服务的寄宿。因为我这里采用的是IIS服务寄宿的方式,所以创建了一个web application项目。该项目需要添加对WCFDemo.Contracts,WCFDemo.Services和System.ServiceModel的引用。
4) WCFDemo.WebClient,一个Web Application项目,模拟服务调用的客户端。需要添加对System.ServiceModel的引用。

项目总体上搭建好了,下面我们开始创建服务契约

2,创建服务契约

一般地,我们通过定义接口的形式来创建服务契约。下面我们将一个接口ICalculatorContract定义成服务契约,通过在接口上应用特性ServiceContract将其定义为服务契约,在其方法成员上应用特性OperationContract而将其定义为服务操作。具体代码如下。

namespace WCFDemo.Contracts
{
    [ServiceContract]//通过ServiceContractAttribute特性将这个接口定义为服务契约
    public interface ICalculatorContract
    {
        [OperationContract]//通过OperationContractAttribute特性将方法定义为服务操作
        double Add(double x,double y);

        [OperationContract]
        double Subtract(double x,double y);

        [OperationContract]
        double Multiply(double x,double y);

        [OperationContract]
        double Divide(double x,double y);
    }
}

还有,接口的名称最好以Contract结尾,这样可读性更好一点,当然这只是我的个人习惯而已,没有强制要求。服务契约创建好了以后,下面我们就要创建服务来实现契约中定义的接口。

3,创建服务

我们通过实现服务契约来创建具体的WCF服务。因此我们创建一个CalculatorService类来实现ICalculatorContract这个契约接口,并且提供接口的所有实现。代码如下。

namespace WCFDemo.Services
{
    public class CalculatorService:ICalculatorContract//实现了契约接口
    {
        public double Add(double x, double y)
        {
            return x + y;
        }

        public double Subtract(double x, double y)
        {
            return x - y;
        }

        public double Multiply(double x, double y)
        {
            return x * y;
        }

        public double Divide(double x, double y)
        {
            return x / y;
        }
    }
}

WCF服务创建好了以后,我们就要进行服务的寄宿了。

4,服务寄宿

因为WCF服务必须依附着一个进程(或称为宿主)才可以运行。当然宿主可以是一个控制台程序,IIS,或者是Windows服务。这里我们选择IIS实现服务寄宿,下面一篇我会总结WCF服务的各种寄宿方式和优缺点。实现IIS寄宿一般有三个步骤,创建一个web application,为WCF服务创建.svc文件,和创建配置文件(主要是定义终结点和服务行为的定义)。这里web application已经创建好了(WCFDemo.Host),下面我们在这个web项目中创建一个名为CalculatorService的svc文件。

.svc文件的内容很简单,仅仅包含一个%@ServiceHost%指令,该指令具有一个必需的Service属性和一系列可选的属性。CalculatorService.svc文件内容如下。

注意,这里Service属性被指定为服务的全名(命名空间+类型名称),所以这里应该为WCFDemo.Services.CalculatorService。

接下来就要通过配置的方式来定义服务寄宿的终结点endpoint和用于元数据发布的服务行为ServiceMetadataBehavior,因此我们需要在WCFDemo.Host项止的web.config文件中添加这些信息。配置文件的创建我们一般采用的是vs自带的WCF服务配置编辑器,可以单击“工具”菜单,选择“WCF服务配置编辑器(WCF service Configuration Editor)”菜单项来打开WCF服务配置编辑器,如图所示。

wcf02

创建好的配置文件如下XML所示。

<system.serviceModel>
    <!--定义寄宿服务的终结点-->
    <services>
      <service behaviorConfiguration="metadataBehavior" name="WCFDemo.Services.CalculatorService">
        <endpoint binding="basicHttpBinding" bindingConfiguration="" contract="WCFDemo.Contracts.ICalculatorContract" />
      </service>
    </services>
    <!--定义服务行为-->
    <behaviors>
      <serviceBehaviors>
        <behavior name="metadataBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

注意,这里无需给endpoint指定地址address,因为svc文件所在的地址就是服务地址,我们可以通过在.svc文件的地址加上?wsdl查询字符串就可以访问WCF服务的元数据wsdl文件。

5,客户端调用服务

客户端调用WCF服务有两种方法,一种是通过添加服务引用并利用生成的服务代理类来调用WCF服务,第二种是通过System.ServiceModel.ChannelFactory<TChannel>直接创建服务代理对象。

方法一:添加服务引用

通过添加服务引用后,vs会为我们自动生成用于服务调用的类和配置。在生成的一系列类中,有一个继承自ClientBase<T>的服务代理类,我们可以通过实例化它来调用WCF服务。

生成的配置文件如下。

<system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_ICalculatorContract" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                    useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                        maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <!--vs自动生成的endpoint-->
        <client>
            <endpoint address="http://localhost:3721/CalculatorService.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICalculatorContract"
                contract="wcf_CalculatorService.ICalculatorContract" name="BasicHttpBinding_ICalculatorContract" />
        </client>
    </system.serviceModel>

生成的代理类如下。

wcf03

我们创建CalculatorContractClient对象来调用WCF服务,代码如下:

private void btnCalculate_ServerClick(object sender, EventArgs e)
        {
            //实例化服务代理类
            CalculatorContractClient proxy = new CalculatorContractClient();

            var op = this.opSelect.Value;
            try
            {
                switch (op)
                {
                    case "+":
                        this.txtResults.Value = proxy.Add(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();//通过代理类对象调用WCF服务
                        break;
                    case "-":
                        this.txtResults.Value = proxy.Subtract(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                        break;
                    case "*":
                        this.txtResults.Value = proxy.Multiply(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                        break;
                    case "/":
                        this.txtResults.Value = proxy.Divide(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                        break;
                }
            }
            catch(Exception ex)
            { }
        }

方法二:通过System.ServiceModel.ChannelFactory<TChannel>直接创建服务代理对象

vs在添加服务引用的过程中,会在客户端创建一个与服务端等效的服务契约接口,如下图。由于服务端与客户端都在同一个解决方案中,因此完全可以让服务端和客户端引用相同的契约。所以我们先将之前添加的服务引用移除,然后为WCFDemo.WebClient项目添加对WCFDemo.Contracts的引用。

为ChannelFactory创建的配置,指定了endpoint的地址,绑定和契约,并为endpoint添加了一个name配置名称。基本同服务端一样,除了添加了address地址。

<system.serviceModel>
      <client>
        <!--创建endpoint,指定了name供ChannelFactory使用,和abc三要素-->
        <endpoint name="calculatorservice"
                  address="http://localhost:3721/CalculatorService.svc" 
                  binding="basicHttpBinding" 
                  contract="WCFDemo.Contracts.ICalculatorContract" />
      </client>
    </system.serviceModel>

客户端调用代码:

private void btnCalculate_ServerClick(object sender, EventArgs e)
        {
            //创建ChannelFactory对象
            using (ChannelFactory<ICalculatorContract> channelFactory = new ChannelFactory<ICalculatorContract>("calculatorservice"))
            {
                //创建接口对象
                ICalculatorContract proxy= channelFactory.CreateChannel();

                var op = this.opSelect.Value;
                try
                {
                    switch (op)
                    {
                        case "+":
                            this.txtResults.Value = proxy.Add(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();//通过代理类对象调用WCF服务
                            break;
                        case "-":
                            this.txtResults.Value = proxy.Subtract(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                            break;
                        case "*":
                            this.txtResults.Value = proxy.Multiply(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                            break;
                        case "/":
                            this.txtResults.Value = proxy.Divide(double.Parse(txt1.Value), double.Parse(txt2.Value)).ToString();
                            break;
                    }
                }
                catch (Exception ex)
                { }
            }
        }
posted on 2013-06-15 14:35  永远的麦子  阅读(1210)  评论(0编辑  收藏  举报