之前做项目的时候没怎么用到WCF,对它也是一知半解,只知道要想学好.net,掌握面向服务,分布式开发,WCF是很重要的一部分。现在进入到一家新的公司,现在的项目里都用WCF来做前端与服务端通信,虽然还只是简单的应用,但是借此机会,我觉得该好好学习WCF了,我也从最基础的开始学起。今天要总结的是如何创建一个简单的WCF应用(一个web版计算器),虽然这只是一个非常简单的例子,但麻雀虽小,五脏俱全。它还是很好地展现了创建WCF程序的各个方面。我准备从以下几点来总结它。
1,项目搭建
2,创建服务契约
3,创建服务
4,服务寄宿
5,客户端调用服务
1,项目搭建
构建一个完整的WCF应用一般需要四个工程,如图所示。
下面来详细介绍一下每个工程的作用。
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服务配置编辑器,如图所示。
创建好的配置文件如下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>
生成的代理类如下。
我们创建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) { } } }