WCF开发实战
一、 服务器应用开发
(一) 创建“WCF服务库”
“文件(F)”->“新建项目(P)...”打开新建项目对话框。在左侧的“项目类型”中选择“WCF”,然后再在右侧的“模板”中选择“WCF服务库”。
在下面的“名称”文本框中,填写我们要创建的WCF服务库的项目名称“WcfEmployee”。
点击确定,会创建出我们的WCF服务库项目,在解决方案中会自动为我们生成两个类文件“IService1.cs”和“Service1.cs”。
这两个类文件是两个WCF示例文件,对我们开发没有什么用处,现在我们删掉这两个文件。
创建Employee实体类
在“解决方案窗口”中,我们右击Services项目名,选择“添加”,再单击“类”
在弹出的“添加新项”窗口中,选择“类”,并在“名称”文本框中写入项名称“Employee.cs”。
为Employee实体类编写代码
using System.Runtime.Serialization;
namespace WcfEmployee
{
[DataContract]
public class Employee
{
[DataMember]
public string EmployeeId;
[DataMember]
public string EmployeeName;
[DataMember]
public int EmployeeAge;
}
}
为了保证此类在WCF调用中能够被序列化,我们在Employee类上面加入[DataContract]标签,在每个需要序列化的成员变量上加入[DataMember]标签。这两个标签在使用的进候需要导入using System.Runtime.Serialization命名空间。
到此为至,我们创建完了需要在服务中传输的复杂的数据类型Employee。
创建服务接口
创建服务接口,声明对外发布的类和方法。
在“解决方案窗口”中,我们右击WcfEmployee项目名,选择“添加”,再单击“类”
在弹出的“添加新项”窗口中,选择“类”,并在“名称”文本框中写入项名称“IEmployeeSrv.cs”。
在此类文件中我们编写服务接口,代码如下:
using System.Collections.Generic;
using System.ServiceModel;
namespace WcfEmployee
{
[ServiceContract]
public interface IEmployeeSrv
{
[OperationContract]
void AddEmployees(Employee employee);
[OperationContract]
List<Employee> GetAllEmployees();
[OperationContract]
void RemoveEmployee(string id);
}
}
在IEmployeeSrv接口上面,我们定义了[ServiceContract]标签,此标签代表此接口及实现此接口的类都是对外发布的Service类,在每个需要对外发布的方法上都加上[OperationContract]标签,以使外部可以访问到此方法。
[ServiceContract]和[OperationContract]这两个标签需要导入using System.ServiceModel命名空间。
创建实现服务接口的类
实现我们上面声明的服务接口,实现对Employee的添加、删除和检索的具体功能。
在“解决方案窗口”中,我们右击WcfEmployee项目名,选择“添加”,再单击“类”
在弹出的“添加新项”窗口中,选择“类”,并在“名称”文本框中写入项名称“EmployeeSrv.cs”。
在此类文件中编写代码实现IEmployeeSrv.cs服务接口。
using System.Collections.Generic;
using System.ServiceModel;
namespace WcfEmployee
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class EmployeeSrv:IEmployeeSrv
{
List<Employee> _Employee=new List<Employee>();
public void AddEmployees(Employee employee)
{
employee.EmployeeId=Guid.NewGuid().ToString();
_Employee.Add(employee);
}
public void RemoveEmployee(string id)
{
Employee employee=_Employee.Find(p=>p.EmployeeId==id);
_Employee.Remove(employee);
}
public List<Employee> GetAllEmployees()
{
return _Employee;
}
}
}
此类是对IEmployeeSrv接口的具体实现,在此类的上面我们声明了[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]标签,此标签代表这个类采用SingleTone(单类模式)来生成对象。
使用[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]接口需要导入using System.ServiceModel;命名空间。
将app.config中“Service1”改为EmployeeSrv”.
运行WCF进行测试。
在VS2008中为我们提供了测试WCF的工具,按F5启动WCF会出现两个东西
一个是在右下角的托盘图标中会出现WCFSVCHost(WCF服务主机),它为我们在开发时候提供了一个运行WCF的服务器,用来为测试客户端提供WCF服务。
另一个是“WCF测试客户端”
“测试客户端”从WcfSVCHost中取得WCF服务的元数据,解析为右侧的“服务结构树”,从这里面我们可以看到此WCF服务为我们提供了一个服务契约“IEmployeeSrv”,此服务契约中对外提供了三个可调用的方法。
双击AddEmployees()方法,我们可以从右面输入相关的数据然后点击“调用”,就可以把数据送给WCF服务器,去调用对应的方法了。
(二) 使用IIS发布WCF服务
上一篇中,我们创建了一个简单的WCF服务,在测试的时候,我们使用VS2008自带的WCFSVCHost(WCF服务主机)发布WCF服务,以便进行测试。这种VS2008内置的WCFSVCHost只适用于开发人员测试的使用,能进行WCF服务部署。这一篇文章中我们来看一下如何在IIS中部发布我们上一篇中做好的WCF服务。
第一步:打开我们上一篇文章中建立的WCF服务项目。
图《1》
第二步:新建WCF服务站点。在解决方案上右击,选择“添加”->“新建网站”,打开新建网站对话框。在“添加新网站”对话框中,我们选择“WCF服务”,并把网站的名子命名为“BookServiceHost”
图《2》
建立起来的新的WCF服务站点的结果如下,其中在App_Code文件中自动为我们生成两个类文件:IService.cs和Service.cs。这两个文件对我们来说没有用,我们删掉。
图《3》
第三步:在刚刚创建的WCF服务站点上添加对WCF服务库项目--Services项目的引用。
图《4》
第四步:配置Service.svc文件。
双击Service.svc文件,我们可以看到它的声明指示如下:
<%@ ServiceHost Language="C#" Debug="true" Service="Service" CodeBehind="~/App_Code/Service.cs" %>
由于在第二步中我们已经把IService.cs和Service.cs两个文件已经删除了,所以这里的声明指示内容修改一下,让这个Service.svc文件的后台代码指向我们上次创建的WCF服务库项目--Services项目中的类,改后的代码如下:
<%@ServiceHostLanguage="C#"Debug="true"Service="WcfEmployee.EmployeeSrv"%>
我们把其中的Service属性指定为WcfEmployee命名空间下的EmployeeSrv类,并把CodeBehind属性删去了。
第五步:配置此WCF服务站点与WCF服务库项目之间的类的对应。
虽然在第三步中我们添加了对Services项目的引用,并且在第四步中修改了Service.svc的类的对应,但此时我们的WCF服务站点并不能把WCF服务库中的服务和终结点发布出来,还需要我们对web.config进行一系列的配置工作。
在web.config上右击选择“编辑WCF配置”
在弹出的服务配置编辑器窗口中,单击”新建服务...”。
单击”浏览”.定位到本项目下的Bin目录,单击WcfEmployee.dll,再选打开.
选”WcfEmployee.EmployeeSrv”,单击”打开”
在Service.svc上右击,选择“在浏览器中查看”,在IE中运行此服务。
由此我们看到我们可以在ASP.NET Development Server中发布我们的WCF服务了。
第八步:在IIS布署此WCF服务站点。
打开iis管理器,在其中找到一个正常的网站,如下图中的wa3,右击之,
别名任意,物理路径必须准确为项目所在的目录.
使浏览器指向”http://localhost:83/EmployeeService/service.svc”,注意”EmployeeService”为刚才指定的别名.
在外网其他电脑上使浏览器指向”http://221.213.51.74:83/EmployeeService/service.svc”
也应该出现上图.
(三) 创建WCF服务宿主程序
1. 控制台应用程序作为宿主程序
建立控制台应用程序ConsSrvHostEmployee
《图1》
向ConsSrvHostEmployee程序中添加两个引用:一个是WCF服务库Services项目的引用,另一个是System.ServiceModel引用。
生成该项目。
在ConsSrvHostEmployee 项目中的Program.cs中编写代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WcfEmployee;
using System.ServiceModel;
namespace ConsSrvHostEmployee
{
class Program
{
static void Main(string[] args)
{
ServiceHost host = new ServiceHost(typeof(EmployeeSrv));
host.Credentials.ServiceCertificate.SetCertificate("CN=OujianpingServer");
host.Open();
Console.WriteLine("服务已启动......");
Console.ReadLine();
host.Close();
}
}
}
配置App.Config
在App.Config上右击选择选择“编辑WCF配置”,弹出服务配置管理窗口
第六步:点击右边的“新建服务...”弹出“新建服务元素向导”窗口,单击“浏览”按钮,选择Bin/Debug目录下Services.dll程序集中的Services.EmployeeSrv服务。
注意进行绑定配置,选择“NewBinding0 netTcpBinding”。
配置元数据终结点:
//如果没有这个过程,也可以正常启动服务,但客户端无法通过引用”添加服务引用”.
System.ServiceModel
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<services>
<service behaviorConfiguration="NewBehavior0" name="WcfEmployee.EmployeeSrv">
<clear />
<endpoint address="basic" binding="basicHttpBinding" contract="WcfEmployee.IEmployeeSrv"
listenUriMode="Explicit">
<identity>
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectName" findValue="OujianpingServer" />
</identity>
</endpoint>
<endpoint address="net.tcp://localhost:8082/Service" binding="netTcpBinding"
bindingConfiguration="NewBinding0" contract="WcfEmployee.IEmployeeSrv"
listenUriMode="Explicit">
<identity>
<certificateReference storeName="My" storeLocation="LocalMachine"
x509FindType="FindBySubjectName" findValue="OujianpingServer" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" bindingConfiguration=""
contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost/service" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="NewBehavior0">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="NewBinding0">
<security>
<transport clientCredentialType="None" />
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
注:今后可将以上文件当作一个模版来使用,比如新建了个了名为wcflib的wcf服务库项目,在此项目中建立了一个名为wcfserver的服务,并使用名为myCert的证书,则可将文中的WcfEmployee替换为wcflib,EmployeeSrv替换为wcfserver,OujianpingServer替换为myCert即可,而不必进行上述繁琐的操作。
用以下命令创建证书
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=OuJianpingServer -sky exchange -pe
(注:复制上述命令时须先整行显示,且只能不能复制行尾的多余空格,否则无法执行.
如下图
安装的证书可在控制台(cmd mmc)的本地计算机证书下看到
(文件-添加/删除管理单位-证书-计算机帐户-本地计算机)
测试一下。
选按Ctrl+F5运行打开浏览器,
2. Windows服务作为宿主程序
建立Windows服务程序WinSrvEmployee
注意:必须选.Net Framework 4.5,与WCF服务库项目保持一致,否则引入WCF服务库会出现问题。
《图2》
第二步:WinSrvEmployee程序中添加两个引用:一个是WCF服务库Services项目的引用,另一个是System.ServiceModel引用。
第三步:修改Service1的属性
在Service1的设计界面中右击,选择“属性”,把其中的(Name)和ServiceName都改为EmployeeSrv
《图3》
第四步:在Service1中编写代码如下
《图4》
把ConsSrvHostEmployeev项目中的App.Config复制过来.为防止元数据泄漏,应将其中以下内容删除:
<endpointaddress="mex"binding="mexHttpBinding"bindingConfiguration=""
contract="IMetadataExchange" />
到这里我们已经作好一个可以发布EmployeeSrv服务的Windows Service宿主程序了。
下面我们要看一看如何把这个Windows Service运行起来。
第六步:为服务添加安装程序。
在Service1设计界面中右击,选择“添加安装程序”
《图6》
生成安装程序组件,出现界面如下
《图7》
serviceProcessInstaller1:服务安装的信息
《图8》
serviceInstaller1:服务本身的信息
《图9》
生成项目
安装的服务
在计算机中搜索installutil.exe文件,找到版本最高的那个,将其路径写入环量变量path中。
打开VS2008命令窗口,进入当前项目的bin/Debug文件夹中,执行命令 :installutil WinSrvEmployee.exe
(注:卸载时 用InstallUtil.exe /u WinSrvEmployee.exe。)
启动EmployeeSrv服务
打开服务管理器,我们可以看到我们刚刚注册上的服务已经存在了,但还没有启动,右击“启动”
注意:上述通过windows server项目发布在.net 3.5 、vs 2008和windows server 2003的环境下无法通过,表现为启动服务会出错,会出现以下提示
,原因不明,但可以通过构建winform项目来代替,尤其是托盘型的winform应用。
二、 部署WCF服务器
三、 客户端程序开发
(一) 创建WCF客户端程序
(此步vs2008不必做)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace EmployeeIMS
{
publicpartialclassForm1 : Form
{
ServiceReference1.EmployeeSrvClient mClient = new ServiceReference1.EmployeeSrvClient("NetTcpBinding_IEmployeeSrv");
public Form1()
{
InitializeComponent();
}
privatevoid Form1_Load(object sender, EventArgs e)
{
mClient.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
ServiceReference1.Employee employee = new ServiceReference1.Employee();
employee.EmployeeName = "王芳";
employee.EmployeeAge = 18;
mClient.AddEmployees(employee);
employee.EmployeeName = "李明";
employee.EmployeeAge = 17;
mClient.AddEmployees(employee);
string msg = "";
foreach(ServiceReference1.Employee em in mClient.GetAllEmployees())
{
msg += em.EmployeeName + "" + em.EmployeeAge + "\r";
}
MessageBox.Show(msg);
}
}
}
(二) 修改app.config
Localhost
将app.config文件中的localhost替换为WCF服务器的实际ip地址,否则在其他计算机上运行时无法连接到服务器。
SendTimeout
如果客户端的请求需要服务花较长的时间才能回复,且这个过程会超过1分钟,则需要改变使用了进行数据传输的绑定(如netTcpBinding)的SendTimeout的值,并应该留有余量,否则超过1分钟后,客户端将抛出超时的错误。
maxBufferPoolSize和maxBufferSize
如果传输的数据过大,则需要改变使用了进行数据传输的绑定(如netTcpBinding)的maxBufferPoolSize和maxBufferSize的值,否则,客户端会抛出错误。