65.9K
CodeProject 正在变化。 阅读更多。
Home

理解 WCF 中的操作类型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (9投票s)

2013年10月3日

CPOL

9分钟阅读

viewsIcon

32225

downloadIcon

509

本文解释了 WCF 支持的操作类型

 

介绍  

WCF 支持客户端调用服务的 3 种方式。本文将演示这 3 种方式。

背景

WCF 支持客户端调用服务的 3 种方式。本文介绍通过客户端调用服务的这 3 种基本方法。还有两种调用操作的方式:异步调用和排队调用,本文未涵盖。

  1. 请求-回复(Request Reply): 默认的调用方式,客户端发出调用,在调用进行时会阻塞方法返回后才会继续执行。如果在 receiveTimeout 指定的时间内服务未响应,客户端将收到 TimeOutException
  2. 单向调用(One way calls):“发送即忘记”类型的操作。这意味着客户端将请求发送给服务器,而不关心服务执行是否成功或失败。服务器端没有返回值。客户端在将调用分派给服务后只会阻塞片刻客户端可以在进行单向调用后继续执行其语句。有时调用会被排队在服务器端,一次处理一个。如果排队的邮件数量超过队列的容量,客户端将在消息排队期间被阻塞。一旦调用被排入队列,客户端就会被解除阻塞,可以继续执行,而服务会在后台处理该操作。
  3. 回调操作(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,与之前的接口不同,之前的接口绑定被定义为 wsHttpBindingwsDualHttpBinding 为 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);

这是客户端的综合输出。

 

兴趣点 

解释了 WCF 服务不同操作模式的工作原理。

历史 

初始版本。
© . All rights reserved.