理解 WCF 中的操作类型






4.79/5 (9投票s)
本文解释了 WCF 支持的操作类型
- 下载源代码 WCF 类库 - 3.7 KB
- 下载源代码客户端应用程序 - 28.4 KB
- 下载源代码宿主应用程序 - 3.2 KB
- 下载可执行宿主应用程序和类库 - 5.3 KB
- 下载可执行客户端应用程序 - 4.5 KB
介绍
WCF 支持客户端调用服务的 3 种方式。本文将演示这 3 种方式。
背景
WCF 支持客户端调用服务的 3 种方式。本文介绍通过客户端调用服务的这 3 种基本方法。还有两种调用操作的方式:异步调用和排队调用,本文未涵盖。
- 请求-回复(Request Reply): 默认的调用方式,客户端发出调用,在调用进行时会阻塞,方法返回后才会继续执行。如果在
receiveTimeout
指定的时间内服务未响应,客户端将收到TimeOutException
。 - 单向调用(One way calls):“发送即忘记”类型的操作。这意味着客户端将请求发送给服务器,而不关心服务执行是否成功或失败。服务器端没有返回值。客户端在将调用分派给服务后只会阻塞片刻。客户端可以在进行单向调用后继续执行其语句。有时调用会被排队在服务器端,一次处理一个。如果排队的邮件数量超过队列的容量,客户端将在消息排队期间被阻塞。一旦调用被排入队列,客户端就会被解除阻塞,可以继续执行,而服务会在后台处理该操作。
- 回调操作(Call back Operation):WCF 支持允许服务回调到其客户端。这对于通知客户端有事情发生在服务方特别有用。回调也通常被称为双工操作。并非所有绑定类型都支持回调操作。例如 HTTP(由于其无连接的性质)。只有两种常用的支持回调的绑定是 NetTcpBinding 和 NetNamedBinding。为了通过 HTTP 提供回调,WCF 提供了
WSDualHttpBinding
,它实际上设置了两个 WS 通道。
使用代码
本文分为 3 个模块
- WCF 服务类库(OperationLib.dll):实际的服务逻辑,它定义了一个服务契约接口
OperationContract
,并实现它们,向外界公开一些可供使用的函数。 - 控制台宿主应用程序以宿主 WCF 服务类库 OperationLibHost.exe):宿主 WCF 类库。
- 控制台客户端应用程序(OperationClient.exe): 使用此服务的客户端应用程序。
第一个模块:WCF 服务类库(OperationLib.dll)
要创建此项目,您可以在项目向导选项中选择一个“类库”项目。将其命名为“OperationLib
”,它是实现业务逻辑的实际服务。该项目已包含一个文件 Class1.cs,在编写任何服务库代码之前,让我们做一些整理工作。
小整理
- 从项目中删除文件 Class1.cs。
- 向项目中添加一个名为
IRequestReplyService
的新接口,项目中将添加一个新文件 IRequestReplyService.cs。 - 向项目中添加一个名为
RequestReplyService
的新类,它将实现IRequestReplyService
接口,项目中将添加一个新文件 RequestReplyService.cs。 - 向项目中添加一个名为
IOneWayCallService
的新接口,项目中将添加一个新文件 IOneWayCallService.cs。 - 向项目中添加一个名为
OneWayCallService
的新类,它将实现IOneWayCallService
接口,项目中将添加一个新文件 OneWayCallService.cs。 - 向项目中添加一个名为
IDuplexService
的新接口,项目中将添加一个新文件 IDuplexService.cs。 - 向项目中添加一个名为
DuplexService
的新类,它将实现IDuplexService
接口,项目中将添加一个新文件 DuplexService.cs。
定义接口
定义 IRequestReply 接口
// Listing of IRequestReplyService.cs
using System;
using System.Text;
using System.ServiceModel;
namespace OperationLib
{
[ServiceContract]
interface IRequestReplyService
{
[OperationContract]
double AddNumer(double dblNum1, double dblNum2);
}
}
解释
该接口简单地定义了一个方法,该方法接受两个 double 类型的参数,并返回一个 double 值。
定义 IOneWayCallService 接口
// Listing of IOneWayCallService.cs
using System;
using System.ServiceModel;
using System.Text;
namespace OperationLib
{
[ServiceContract(SessionMode = SessionMode.Required)]
interface IOneWayCallService
{
[OperationContract(IsOneWay = true)]
void AddNumer(double dblNum1, double dblNum2 );
[OperationContract]
double GetResult();
}
}
解释
该接口简单地定义了一个方法,该方法接受一个 double 类型的参数,并且不返回任何值。注意 [ServiceContract(SessionMode = SessionMode.Required)]
属性。这是为了在会话服务中使用单向操作,以保留上次操作的值,因为标记为 [OperationContract(IsOneWay = true)]
的操作不应返回任何值。为了获取操作的结果,我们定义了另一个方法。
double GetResult();
定义 IDuplexService 接口
// Listing of IDuplexService.cs
using System;
using System.ServiceModel;
using System.Text;
namespace OperationLib
{
public interface IDuplexServiceCallback
{
[OperationContract]
void OnValueAdded(double dblNum1, double dblNum2, double dblResult);
}
[ServiceContract(CallbackContract = typeof(IDuplexServiceCallback))]
public interface IDuplexService
{
[OperationContract()]
void AddNumer(double dblNum1, double dblNum2);
}
}
解释
可以通过 ServiceContract 属性中的 CallbackContract 属性来启用回调服务。如所示,它定义了两个接口:一个接口(IDuplexService
)将由服务实现,IDuplexService
接口简单地定义了一个接受两个 double 参数且不返回任何值的方法。另一个接口(IDuplexServiceCallback
)被定义为由客户端实现。IDuplexServiceCallback
被定义为 IDuplexService
接口的 callback 属性,IDuplexServiceCallback
简单地定义了一个方法原型。
另一个接口(IDuplexServiceCallback
)被定义为由客户端实现。IDuplexServiceCallback
被定义为(IDuplexService
)接口的 callback 属性,(IDuplexServiceCallback
)简单地定义了一个方法原型。
void OnValueAdded(double dblNum1, double dblNum2, double dblResult);
实现接口
让我们为每个服务实现接口。
实现 IRequestReply 接口
// Listing of RequestReplyService.cs
using System;
using System.Text;
namespace OperationLib
{
public class RequestReplyService : IRequestReplyService
{
public double AddNumer(double dblNum1, double dblNum2)
{
return (dblNum1 + dblNum2);
}
}
}
说明
该方法简单地将两个值相加并返回结果。
实现 IOneWayCallService 接口
// Listing of OneWayCallService.cs
using System;
using System.Text;
namespace OperationLib
{
public class OneWayCallService : IOneWayCallService
{
private double m_dblResult = 0;
public void AddNumer(double dblVal1, double dblVal2)
{
m_dblResult = dblVal1 + dblVal2;
}
public double GetResult()
{
return m_dblResult;
}
}
}
说明
简单地将两个值相加并将结果存储在一个私有变量中。接口被标记为 SessionMode.Required
,以使其成为一个会话服务,因为我们需要返回操作的值,而标记为 [OperationContract(IsOneWay = true)]
的方法不能返回任何值,所以我们添加了另一个方法,该方法将简单地返回私有变量(result)的值。这意味着客户端在调用 AddNumber
方法后,需要调用 double GetResult ()
方法来获取结果。
实现 IDuplexService 接口
// Listing of DuplexService.cs
using System;
using System.ServiceModel ;
using System.Text;
namespace OperationLib
{
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DuplexService : IDuplexService
{
public void AddNumber(double dblNum1, double dblNum2)
{
double dblResult = dblNum1 + dblNum2;
IDuplexServiceCallback callbackInstance = OperationContext.Current.GetCallbackChannel<IDuplexServiceCallback>();
callbackInstance.OnNumberAdded(dblNum1, dblNum2, dblResult);
}
}
}
说明
您可以看到我们将 ConcurrencyMode 设置为 Multiple。如果您不将 ConcurrencyMode
设置为 Multiple 或 Reentent,您将最终遇到死锁异常,如下所示。这是因为当客户端调用服务时,WCF 服务会创建并锁定通道。如果您在服务方法内调用回调方法。服务将尝试访问锁定的通道,这可能导致死锁。因此,您可以将 ConcurrencyMode
设置为 Multiple
或 Reentent
,这样它将静默释放锁。
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DuplexService : IDuplexService
{
. . .
}
OperationContext
类通过通用的 GetCallbackChannel<T>( )
方法为服务提供了对回调引用的便捷访问。
IDuplexServiceCallback callbackInstance = OperationContext.Current.GetCallbackChannel<IDuplexServiceCallback>();
然后,服务可以使用获取的引用调用客户端回调方法。以下代码显示了回调方法的调用。
callbackInstance.OnNumberAdded(dblNum1, dblNum2, dblResult);
构建类库,第一个部分就完成了。第二个模块:宿主应用程序(OperationLibHost.exe)
要创建此项目,您可以在项目向导选项中选择一个“基于控制台的应用程序”项目。将其命名为“OperationLibHost
”,
一个新的项目 OperationLibHost 将被添加到工作区。
假设
宿主应用程序将为服务公开以下终结点。
RequestReplyService
将在端口 9011 公开 HTTP 终结点。- 对应于 HTTP 终结点的 mex 终结点(IMetadatExchange)。
RequestReplyService
将在端口 9012 公开 TCP 终结点。- 对应于 TCP 终结点的 mex 终结点(IMetadatExchange)。
OneWayCallService
将在端口 9013 公开 HTTP 终结点。- 对应于 HTTP 终结点的 mex 终结点(IMetadatExchange)。
OneWayCallService
将在端口 9014 公开 TCP 终结点。- 对应于 TCP 终结点的 mex 终结点(IMetadatExchange)。
DuplexService
将在端口 9015 公开 HTTP 终结点。- 对应于 HTTP 终结点的 mex 终结点(IMetadatExchange)。
DuplexService
将在端口 9016 公开 TCP 终结点。- 对应于 TCP 终结点的 mex 终结点(IMetadatExchange)。
定义 RequestReplyService 的配置
<service name="OperationLib.RequestReplyService" behaviorConfiguration="RequestReplyServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://:9011/RequestReplyService"/>
<add baseAddress="net.tcp://:9012/RequestReplyService"/>
</baseAddresses>
</host>
<endpoint address="https://:9011/RequestReplyService" binding="wsHttpBinding" contract="OperationLib.IRequestReplyService"/>
<endpoint address="net.tcp://:9012/RequestReplyService" binding="netTcpBinding" contract="OperationLib.IRequestReplyService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
说明
应用程序配置文件定义了 2 个终结点,一个用于 WSHttpBinding,一个用于 TCP Binding,分别在端口 9011 和 9012 ,以及每个终结点的 1 个 mex 终结点。WSHttpBinding 类似于 BasicHttpBinding,但提供了更多的 Web 服务功能。
定义 OneWayCallService 的配置
<service name="OperationLib.OneWayCallService" behaviorConfiguration="OneWayCallServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://:9013/OneWayCallService"/>
<add baseAddress="net.tcp://:9014/OneWayCallService"/>
</baseAddresses>
</host>
<endpoint address="https://:9013/OneWayCallService" binding="wsHttpBinding" contract="OperationLib.IOneWayCallService"/>
<endpoint address="net.tcp://:9014/OneWayCallService" binding="netTcpBinding" contract="OperationLib.IOneWayCallService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
定义 DuplexService 的配置
<service name="OperationLib.DuplexService" behaviorConfiguration="DuplexServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://:9015/DuplexService"/>
<add baseAddress="net.tcp://:9016/DuplexService"/>
</baseAddresses>
</host>
<endpoint address="https://:9013/DuplexService" binding="wsDualHttpBinding" contract="OperationLib.IDuplexService"/>
<endpoint address="net.tcp://:9014/DuplexService" binding="netTcpBinding" contract="OperationLib.IDuplexService"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
重要
在 Duplex 服务中,绑定被定义为 wsDualHttpBinding
,与之前的接口不同,之前的接口绑定被定义为 wsHttpBinding
。wsDualHttpBinding
为 Web 服务协议提供与 WSHttpBinding
相同的支持,但用于双工契约。
定义服务的行为
<!-- ********************************** behaviors ********************************** -->
<behaviors>
<serviceBehaviors>
<!-- Single Call Service Behavior -->
<behavior name="RequestReplyServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
<!--Single Session Service Behavior -->
<behavior name="OneWayCallServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
<!--Singleton Service Behavior -->
<behavior name="DuplexServiceBehavior" >
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
解释
每个服务行为定义了两个属性,
<serviceMetadata httpGetEnabled="true" />
获取或设置一个值,该值指示是否通过 HTTP/GET 请求发布服务元数据以供检索,true 表示是,可以通过 HTTP/GET 请求检索元数据。
<serviceDebug includeExceptionDetailInFaults="true "/>
将 IncludeExceptionDetailsInFaults
设置为 true
以允许客户端获取有关内部服务方法异常的信息;这仅推荐作为临时调试服务应用程序的方法。此属性在生产服务器上必须设置为 false。
一旦配置完成,让我们编写代码来宿主服务。
宿主 RequestReplyService
ServiceHost m_RequestReplyHost = null;
try
{
m_RequestReplyHost = new ServiceHost(typeof(OperationLib.RequestReplyService));
m_RequestReplyHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting RequestReplyService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_RequestReplyHost != null) Console.WriteLine("RequestReplyService hosted successfully . . .");
宿主 OneWayService
ServiceHost m_OneWayCallHost = null;
try
{
m_OneWayCallHost = new ServiceHost(typeof(OperationLib.OneWayCallService));
m_OneWayCallHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting OneWayCallService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_OneWayCallHost != null) Console.WriteLine("OneWayCallService hosted successfully . . .");
宿主 DuplexService
ServiceHost m_DuplexServiceHost = null;
try
{
m_DuplexServiceHost = new ServiceHost(typeof(OperationLib.DuplexService));
m_DuplexServiceHost.Open();
}
catch (Exception eX)
{
Console.WriteLine("Failed while starting DuplexService [" + eX.Message + "]");
m_RequestReplyHost = null;
}
if (m_DuplexServiceHost!= null) Console.WriteLine("DuplexService hosted successfully . . .");
构建并执行宿主。
打开命令提示符并执行宿主。这是输出结果。
第三模块:客户端应用程序(OperationClient.exe)
要创建此项目,您可以在项目向导选项中选择一个“基于控制台的应用程序”项目。将其命名为“OperationClient
”,
一个新的项目 OperationClient
将被添加到工作区。
生成代理
当宿主应用程序正在运行时,右键单击客户端应用程序项目,然后单击
为 RequestReplyService 生成代理
引用 –> 添加服务引用
在地址栏中键入 RequestReplyService
的 mex 终结点地址,如下所示。
为 OneWayCallService 生成代理
引用 –> 添加服务引用
在地址栏中键入 OneWayCallService
的 mex 终结点地址,如下所示。
为 DuplexService 生成代理
引用 –> 添加服务引用
在地址栏中键入 DuplexService
的 mex 终结点地址,如下所示。
现在,当您添加了所有 3 个服务的引用后,让我们编写客户端代码来使用这些服务。
调用 RequestReplyService
double dblVal1 = 100; double dblVal2 = 200;
try
{
RequestReplyServiceReference.RequestReplyServiceClient obj1 = new RequestReplyServiceReference.RequestReplyServiceClient("WSHttpBinding_IRequestReplyService");
RequestReplyServiceReference.RequestReplyServiceClient obj2 = new RequestReplyServiceReference.RequestReplyServiceClient("NetTcpBinding_IRequestReplyService");
Console.WriteLine("\nCalling Request Reply Service");
double dblResult1 = obj1.AddNumber (dblVal1, dblVal2);
Console.WriteLine("Using HTTP Binding >> Value 1: {0:F2} Value 2: {1:F2} Returns : {2:F2}", dblVal1, dblVal2, dblResult1);
dblVal1 = 100; dblVal2 = 200;
double dblResult2 = obj2.AddNumber(dblVal1, dblVal2);
Console.WriteLine("Using TCP Binding >> Value 1: {0:F2} Value 2: {1:F2} Return : {2:F2}", dblVal1, dblVal2, dblResult2);
}
catch (Exception eX)
{
Console.WriteLine("Error while calling Request Reply Service [ " + eX.Message + "]");
}
说明
创建服务代理的对象并调用方法,然后显示结果。
调用 OneWayCallService
try { OneWayCallServiceReference.OneWayCallServiceClient obj3 = new OneWayCallServiceReference.OneWayCallServiceClient("WSHttpBinding_IOneWayCallService"); OneWayCallServiceReference.OneWayCallServiceClient obj4 = new OneWayCallServiceReference.OneWayCallServiceClient("NetTcpBinding_IOneWayCallService"); Console.WriteLine("\nCalling OneWayCall Service"); obj3.AddNumber(dblVal1, dblVal2 ); double dblResult3 = obj3.GetResult(); Console.WriteLine("Using HTTP Binding >> Value 1: {0:F2} Value 2: {1:F2}", dblVal1, dblVal2 ); Console.WriteLine("Result : {0:F2}", dblResult3); obj4.AddNumber (dblVal1, dblVal2); double dblResult4 = obj4.GetResult(); Console.WriteLine("Using TCP Binding >> Value 1: {0:F2} Value 2: {1:F2}", dblVal1, dblVal2); Console.WriteLine("Result : {0:F2}", dblResult4); } catch (Exception eX) { Console.WriteLine("Error while calling One way Service [ " + eX.Message + "]"); }
说明
创建服务代理的对象并调用方法,传递值,因为该方法被定义为单向的,所以它不返回任何值。
调用另一个方法来收集结果。
double dblResult3 = obj4.GetResult();
和
double dblResult4 = obj4.GetResult();
调用 Duplex Service
在调用双工服务之前,您需要做一些准备工作。您需要在客户端实现回调接口。因此,定义一个派生自 IDuplexServiceCallback
的类。我们称之为 DuplexServiceCallbackSink
。
在客户端实现 IDuplexServiceCallback 接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OperationClient
{
class DuplexServiceCallbackSink : DuplexServiceReference.IDuplexServiceCallback
{
public void OnNumberAdded(double dblNum1, double dblNum2, double dblResult)
{
Console.WriteLine(">> Duplex Service <<CallBack>> Value 1 : {0:F2} Value 2 : {1:F2} Result : {2:F2}", dblNum1, dblNum2, dblResult);
}
}
}
现在,当您编写了客户端回调接口的实现后,就可以调用双工服务了。
try
{
DuplexServiceCallbackSink callback = new DuplexServiceCallbackSink();
InstanceContext insCntxt = new InstanceContext(callback);
DuplexServiceReference.DuplexServiceClient obj5 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "WSDualHttpBinding_IDuplexService");
DuplexServiceReference.DuplexServiceClient obj6 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "NetTcpBinding_IDuplexService");
Console.WriteLine("\nCalling Duplex Service");
obj5.AddNumber(dblVal1, dblVal2);
obj6.AddNumber(dblVal1, dblVal2);
}
catch (Exception eX)
{
Console.WriteLine("Error while calling Duplex Service [ " + eX.Message + "]");
}
解释
创建回调 Sink,因为回调方法调用需要一个实现回调方法的对象实例。
DuplexServiceCallbackSink callback = new DuplexServiceCallbackSink();
InstanceContext insCntxt = new InstanceContext(callback);
使用适当的绑定调用服务,并传递 InstanceContext 对象。
DuplexServiceReference.DuplexServiceClient obj5 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "WSDualHttpBinding_IDuplexService");
obj5.AddNumber(dblVal1, dblVal2);
或/和
DuplexServiceReference.DuplexServiceClient obj6 = new DuplexServiceReference.DuplexServiceClient(insCntxt, "NetTcpBinding_IDuplexService");
obj6.AddNumber(dblVal1, dblVal2);
这是客户端的综合输出。