您的位置:首页 > 其它

在Win10上使用VS2015构建一个简单的WCF项目

2017-08-31 17:42 489 查看
本应用的功能虽然简单,但它具有一个完整WCF应用的基本结构。在这个例子中,我们将实现一个简单的计算服务来提供基本的加、减、乘、除运算。客户端和服务通过运行在同一台机器上的不同程序来模拟,下图体现了客户端和服务端进程互相调用的关系。



WCF的服务不能孤立地存在,需要寄宿在一个运行着的进程中,我们把承载WCF服务的进程称为宿主,为服务指定宿主的过程成为服务寄宿(Service Hosting)。我们采用自我寄宿(Self-Hostin)和IIS寄宿两种服务寄宿方式。对于前者,我们通过一个控制台应用作为服务的宿主;对于后者,则将服务寄宿于IIS的工作进程(W3WP.exe)。客户端通过另一个控制台应用模拟(进程为Client.exe)。接下来一步一步来构建这样的一个WCF应用。

步骤一:构建整个解决方案

通过VS2015创建一个空白的解决方案,起名为WcfServices并添加如下图所示的4个项目。



4个项目类型、承载的功能和相互引用如下。

Service.Interface:用于定义服务契约(Service Contract)的类库项目,引用WCF的核心程序集System.ServiceModel.dll
Service:用于定义服务类型的类库项目。由于服务类型需要实现定义在Service.Interface中相应的契约接口,因此该项目具有对Service.Interface项目的引用。
Hosting:作为服务宿主的控制台引用。该项目同时引用Service.InterfaceService项目和System.ServiceModel.dll程序集。
Client:一个控制台应用模拟服务的客户端,该项目引用System.ServiceModel程序集。

步骤二:创建服务契约

从功能上讲,服务契约抽象了服务提供的所有操作;而站在消息交换的角度来看,服务契约则定义了基于服务调用的消息交换过程中请求消息和回复消息的结构,以及采用的消息交换模式,我们一般讲服务契约定义成接口。
如下面的代码片段所示,我们通过将System.ServiceModel.ServiceContractAttribute特性应用在接口ICalculator上从而将其定义成了服务契约。在应用ServiceContractAttribute特性的同时,还可以指定服务契约的名称(CalculatorService)和命名空间(http://www.artech.com/)。
using System.ServiceModel;

namespace Artech.WcfService.Service.Interface
{
[ServiceContract(Name = "CalculatorService",
Namespace = "http://www.artech.com/")]
public interface ICalculator
{
[OperationContract]
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);
}
}

通过应用ServiceContractAttribute特性将接口定义成服务契约之后,接口的方法并不能自动成为服务操作。WCF采用的是显示选择(Explicit Opt-in)的策略,意味着我们需要在相应的操作方法上面显示地应用OperationContractAttribute特性。

步骤三:创建服务

当契约接口ICalculator成功创建后,我们在Service项目中创建实现它的服务CalculatorService。如下面的代码片段所示,CalculatorService实现了接口ICalculator中的4个表示加、减、乘和除的运算方法。
using Artech.WcfService.Service.Interface;

namespace Artech.WcfServices.Service
{
public class CalculatorService : ICalculator
{
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服务需要依存一个运行着的宿主进程,服务寄宿就是为了服务指定一个宿主的过程。WCF采用了基于终结点(Endpoint)的通信手段。终结点由如下图所示的地址(Address)、绑定(Binding)和契约(Contract)三要素组成。由于三要素的首字母分别为A、B、C,所有就有了易于记忆的公式:Endpoint = ABC。



一个终结点包含了通信所必需的所有信息,可以像下面那样认识终结点的三要素。

地址(Address):地址决定了服务的位置,解决了服务寻址的问题。
绑定(Binding):绑定了实现了通信的所有细节,包括网络传输、消息编码,以及其他为实现某种功能(比如传输安全、可靠消息传输、事务等)对消息进行的相应处理。
契约(Contarct):契约是对服务操作的抽象,也是对消息交换膜式及消息结构的定义。
服务寄宿的目的就是开启一个进程,为WCF服务提供一个运行的环境,并为服务添加一个或多个终结点,使之暴露给潜在的服务消费者。在Hosting项目中,我们编写了如下的代码将服务CalculatorService寄宿于控制台程序中。

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using Artech.WcfServices.Service;
using Artech.WcfServices.Service.Interface;

namespace Hosting
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.AddServiceEndpoint(typeof(ICalculator),
new WSHttpBinding(),
"http://127.0.0.1:3721/calculatorservice");
if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
{
ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
behavior.HttpGetUrl = new Uri("http://127.0.0.1:3721/calculatorservice/metadata");
host.Description.Behaviors.Add(behavior);
}
host.Opened += delegate
{
Console.WriteLine("CalculatorService已经启动,按任意键终止服务! ");
};

host.Open();
Console.Read();
}
}
}
}

WCF服务寄宿通过System.ServiceModel.ServiceHost对象完成。在上面的代码中,我们基于服务类型(typeof(CalculatorService))创建了ServiceHost对象,并添加了一个基于WSHttpBinding绑定的终结点。该终结点的地址为http://127.0.0.1:3721/calculatorservice,并指定了服务契约的类型ICalculator
松耦合是SOA的一个基本特征,WCF应用中客户端和服务端的松耦合体现在客户端只需要了解WCF服务基本的描述,而无须知道具体的实线细节,就可以实现正常的服务调用。WCF服务的描述通过元数据(Metadata)的形式发布出来。WCF中元数据的发布通过一个特殊的服务行为ServiceMetadataBehavior来实现。
在上面提供的服务寄宿代码中,我们为ServiceHost添加了ServiceMetadataBehavior这样一个服务行为,并采用了基于HTTP-GET的元数据获取方式,并且通过ServiceMetadataBehaviorHttpGetUrl属性指定元数据发布地址(http://127.0.0.1:3721/calculatorservice/metadata)。在服务成功寄宿后,可以通过该地址获取服务相关的元数据。如果在IE地址栏输入该地址,将会看到如下图所示的以WSDL形式表示的服务元数据。



注意,要以管理员模式来运行service,不然会如下图提示权限不足。建议在管理员模式下打开VS2015调试。



在进行真正的WCF应用开发时,一般采用配置而不是编程的方式进行终结点的添加和服务行为的定义。上面添加终结点和定义服务行为的代码可以用下面的配置代替。
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true"
httpGetUrl="http://127.0.0.1:3721/calculatorservice/metadata"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="Artech.WcfServices.Service.CalculatorService"
behaviorConfiguration="metadataBehavior" >
<endpoint address="http://127.0.0.1:3721/calculatorservice"
binding="wsHttpBinding"
contract="Artech.WcfServices.Service.Interface.ICalculator" />
</services>
</system.serviceModel>
</configuration>

对于初学者来说,WCF的配置显得过于复杂,直接对配置文件进行手工编辑不太现实。在这种情况下,可以直接使用Visual Studio提供的配置工具。可以单击“工具(Tools)”菜单,选择“WCF服务配置编辑器(WCF Service Configuration Editor)”菜单项,开启WCF服务配置编辑器。(此处不做探讨)



如果采用了上述配置方法,服务寄宿代码将会得到极大的精简。如下面的代码所示,由于我们将终结点和用于元数据发布的ServiceMetadataBehavior服务行为都定义在配置中,因此在进行服务寄宿的时候,只需要直接根据服务类型创建ServiceHost并调用Open方法将其开启就可以了。
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.Opened += delegate
{
Console.WriteLine("CalculatorService已经启动,按任意键终止服务! ");
};

host.Open();
Console.Read();
}

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

服务被成功寄宿后,服务端便开始了服务调用请求的监听。当通过Visual Studio添加服务引用的时候,Visual Studio在内部帮助我们实现元数据的获取,并借助这些元数据通过代码生成工具(SvcUtil.exe)自动生成用于服务调用的服务代理相关代码和相应的配置。
在运行服务寄宿程序(Hosting.exe)的情况下,右键单击Client项目并在弹出的快捷菜单中选择"添加服务引用",会弹出如下图所示的添加服务引用对话框。在地址栏输入服务元数据发布的源地址(http://127.0.0.1:3721/calculatorservce/metadata),并指定了一个命名空间,这里指定为CalculatorServices,单击"确定"按钮,Visual
Studio会生成一系列用于服务调用的代码和配置。



当服务引用被成功添加后,被客户端用于进行服务调用的服务契约接口CalculatorService会被生成出来。客户端的契约接口之所以会命名为CalculatorService,而不是ICalculator,是因为我们在定义契约接口的时候将ServiceContractAttribute特性的Name属性设置成了CalculatorServiceCalculatorService是与定义在Service.Interface项目中的ICalculator接口等效的契约接口。如下面代码所示,CalculatorServiceICalculator具有相同的方法成员定义。
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.artech.com/", ConfigurationName="CalculatorServices.CalculatorService")]
public interface CalculatorService {

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Add", ReplyAction="http://www.artech.com/CalculatorService/AddResponse")]
double Add(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Add", ReplyAction="http://www.artech.com/CalculatorService/AddResponse")]
System.Threading.Tasks.Task<double> AddAsync(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Subtract", ReplyAction="http://www.artech.com/CalculatorService/SubtractResponse")]
double Subtract(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Subtract", ReplyAction="http://www.artech.com/CalculatorService/SubtractResponse")]
System.Threading.Tasks.Task<double> SubtractAsync(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Multiply", ReplyAction="http://www.artech.com/CalculatorService/MultiplyResponse")]
double Multiply(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Multiply", ReplyAction="http://www.artech.com/CalculatorService/MultiplyResponse")]
System.Threading.Tasks.Task<double> MultiplyAsync(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Divide", ReplyAction="http://www.artech.com/CalculatorService/DivideResponse")]
double Divide(double x, double y);

[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/CalculatorService/Divide", ReplyAction="http://www.artech.com/CalculatorService/DivideResponse")]
System.Threading.Tasks.Task<double> DivideAsync(double x, double y);
}

真正被客户端用于服务调用的则是一个叫做CalculatorServiceClient的类。如下面的代码所示,CalculatorServiceClient的基类为System.ServiceModel.ClientBase<CalculatorService>CalculatorServiceClient同样实现了契约接口CalculatorService,并通过调用从基类继承的Channel属性的相应方法实现了4个运算操作方法。
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class CalculatorServiceClient : System.ServiceModel.ClientBase<Client.CalculatorServices.CalculatorService>, Client.CalculatorServices.CalculatorService {

public CalculatorServiceClient() {
}

public CalculatorServiceClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
}

public CalculatorServiceClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}

public CalculatorServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
}

public CalculatorServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
}

public double Add(double x, double y) {
return base.Channel.Add(x, y);
}

public System.Threading.Tasks.Task<double> AddAsync(double x, double y) {
return base.Channel.AddAsync(x, y);
}

public double Subtract(double x, double y) {
return base.Channel.Subtract(x, y);
}

public System.Threading.Tasks.Task<double> SubtractAsync(double x, double y) {
return base.Channel.SubtractAsync(x, y);
}

public double Multiply(double x, double y) {
return base.Channel.Multiply(x, y);
}

public System.Threading.Tasks.Task<double> MultiplyAsync(double x, double y) {
return base.Channel.MultiplyAsync(x, y);
}

public double Divide(double x, double y) {
return base.Channel.Divide(x, y);
}

public System.Threading.Tasks.Task<double> DivideAsync(double x, double y) {
return base.Channel.DivideAsync(x, y);
}
}

Client项目中可以直接利用通过添加服务引用生成的服务代理类来调用我们寄宿的CalculatorService服务。如下面的代码所示,我们创建CalculatorServiceClient对象并执行相应方法调用服务操作。
using System;
using Artech.WcfServices.Client.CalculatorServices;

namespace Artech.WcfServices.Client
{
class Program
{
static void Main(string[] args)
{
using (CalculatorServiceClient proxy = new CalculatorServiceClient())
{
Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));
Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));
Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));
Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));

Console.Read();
}
}
}
}
注意:由于Hosting.exe在绑定时使用了WSHttpBinding,这在win10中会产生UPN和SPN问题,即身份验证问题,此处暂时不解决这个问题,将WSHttpBinding改为BasicHttpBinding,就不会产生问题了。关于命名空间:Artech.WcfServices.Client.CalculatorService;由于这是自动生成的命名空间,所以要更改需要在工程的属性里面更改默认命名空间。

在服务寄宿程序Hosting开启的情况下运行客户端程序,控制台将会输出如下的运算结果。
x + y = 3 when x = 1 and y = 2

x - y = -1 when x = 1 and y = 2

x * y = 2 when x = 1 and y = 2

x / y = 0.5 when x = 1 and y = 2

关于IIS寄宿方式,后续在记录。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: