理解 WCF 中的事件






3.61/5 (8投票s)
本文解释了 WCF 中事件的工作原理。
- 下载 ExeEventsLibHost - 4.9 KB
- 下载 ExeEventsClient - 3.5 KB
- 下载 SrcEventsLibHost - 2.8 KB
- 下载 SrcEventsLib - 2.5 KB
- 下载 SrcEventsClient - 11 KB
引言
事件允许客户端或多个客户端被通知在服务方“发生了某些事情”。事件可能源于直接的客户端调用,或者可能源于服务监控到的某些情况。
背景
虽然 WCF 中的事件“不过是回调操作”而已,但根据其性质,事件通常意味着发布者和订阅者之间的关系比典型的客户端和服务之间的关系“松散得多”。触发事件的服务称为发布者,接收事件的客户端称为订阅者。服务通常将同一个事件发布给多个订阅的客户端。发布者通常不关心订阅者的调用顺序或任何错误,发布者只知道它应该将事件传递给订阅者。因为服务不关心来自订阅者的返回结果。
因此,事件处理操作
- 应具有“void 返回类型”
- 应“不”具有任何“out/ref 参数”
- 应“标记为单向”
Using the Code
本文分为 3 个模块
- WCF 服务库 (EventsLib.dll):实际的服务逻辑,它定义了一个服务契约接口、
OperationContract
,并实现它们,然后向外界公开一些函数供使用。 - 基于控制台的宿主应用程序 (EventsLibHost.exe):宿主 WCF 服务库。
- 基于控制台的客户端应用程序 (EventsClient.exe):将使用此服务的客户端应用程序。
第一个模块:WCF 服务库 (EventsLib.dll)
要创建此项目,您只需选择一个“类库”项目,并在项目向导选项中进行选择。我们将其命名为“EventsLib
”,它实现了实际的服务并包含业务逻辑。该项目已包含一个文件 Class1.cs,在我们为服务库编写任何代码之前,先做一些整理工作。
稍微整理一下
- 从项目工作区中删除文件 Class1.cs。
- 向项目中添加一个名为
ICalcService
的新**接口**,将添加一个新文件 ICalcService.cs 到项目中。 - 向项目中添加一个名为
CalcService
的新**类**,该类将实现ICalcService
接口,将添加一个新文件 CalcService.cs 到项目中。 - 向项目中添加一个名为
ICalcServiceEvents
的新**接口**,将添加一个新文件 ICalcServiceEvents.cs 到项目中。
定义接口 ICalcServiceEvents (ICalcServiceEvents.cs)
因此,我们来定义服务发布的事件的接口。
using System;
using System.Text;
using System.ServiceModel;
namespace EventsLib
{
public interface ICalcServiceEvents
{
[OperationContract(IsOneWay=true)]
void Calculated(int nOp, double dblNum1, double dblNum2, double dblResult);
[OperationContract(IsOneWay=true)]
void CalculationFinished();
}
}
解释
该接口仅公开 2 个方法,这些方法基本上是订阅者的事件。请注意,此接口中的每个方法都已
- **标记为单向**
- **不返回值** (void)
- **没有 out/ref 参数**
第一个方法 Calculated
是订阅者的事件,当计算完成时触发,它还将操作数和操作类型与结果一起传递给订阅者。
第二个方法 CalculationFinished
是事件,当计算完成时触发。
定义接口 ICalcService (ICalcService.cs)
因此,我们来定义服务的接口。
// Listing of ICalcService.cs
using System;
using System.Text;
using System.ServiceModel;
namespace EventsLib
{
[ServiceContract(CallbackContract = typeof(ICalcServiceEvents))]
public interface ICalcService
{
[OperationContract]
void Calculate(int nOp, double dblNum1, double dblNum2);
[OperationContract]
void SubscribeCalculatedEvent();
[OperationContract]
void SubscribeCalculationFinishedEvent();
}
}
解释
该接口仅定义 3 个方法。
第一个方法……
void Calculate(int nOp, double dblNum1, double dblNum2);
……是与业务逻辑相关的方法,它执行实际工作。
另外两个方法……
void SubscribeCalculatedEvent () ;
void SubscribeCalculationFinishedEvent ();
……是由客户端调用以订阅服务发布的事件的辅助方法。由于您有两个事件,因此每个事件有一个方法。
实现 ICalcService 接口 (CalcService.cs)
让我们来实现定义好的每个方法。
// Listing of CalcService.cs
using System;
using System.Text;
using System.ServiceModel;
namespace EventsLib
{
public class CalcService : ICalcService
{
static Action<int, double, double, double> m_Event1 = delegate { };
static Action m_Event2 = delegate { };
public void SubscribeCalculatedEvent()
{
ICalcServiceEvents subscriber =
OperationContext.Current.GetCallbackChannel<ICalcServiceEvents>();
m_Event1 += subscriber.Calculated;
}
public void SubscribeCalculationFinishedEvent()
{
ICalcServiceEvents subscriber =
OperationContext.Current.GetCallbackChannel<ICalcServiceEvents>();
m_Event2 += subscriber.CalculationFinished ;
}
public void Calculate(int nOp, double dblX, double dblY)
{
double dblResult = 0;
switch (nOp)
{
case 0: dblResult = dblX + dblY; break;
case 1: dblResult = dblX - dblY; break;
case 2: dblResult = dblX * dblY; break;
case 3: dblResult = (dblY == 0) ? 0 : dblX / dblY; break;
}
m_Event1(nOp, dblX, dblY, dblResult);
m_Event2();
}
}
}
解释
成员变量
static Action<int, double, double, double> m_Event1 = delegate { };
static Action m_Event2 = delegate { };
Action
关键字可用于定义委托,可用于在“不显式声明自定义委托”的情况下将方法作为参数传递。
方法 SubscribeCalculatedEvent()
和 SubscribeCalculationFinishedEvent()
如下所示:
public void SubscribeCalculatedEvent()
{
ICalcServiceEvents subscriber =
OperationContext.Current.GetCallbackChannel<ICalcServiceEvents>();
m_Event1 += subscriber.Calculated;
}
public void SubscribeCalculationFinishedEvent()
{
ICalcServiceEvents subscriber=OperationContext.Current.GetCallbackChannel<ICalcServiceEvents>();
m_Event2 += subscriber.CalculationFinished ;
}
获取客户端回调方法的引用,并将此事件处理程序分配给委托变量 m_Event1
和 m_Event2
。此方法允许客户端选择性订阅公开的事件。
方法 public void Calculate(int nOp, double dblX, double dblY)
如下所示:
public void Calculate(int nOp, double dblX, double dblY)
{
double dblResult = 0;
switch (nOp)
{
case 0: dblResult = dblX + dblY; break;
case 1: dblResult = dblX - dblY; break;
case 2: dblResult = dblX * dblY; break;
case 3: dblResult = (dblY == 0) ? 0 : dblX / dblY; break;
}
m_Event1(nOp, dblX, dblY, dblResult);
m_Event2();
}
实现了服务的业务逻辑,并使用委托调用在客户端实现的回调方法,简而言之,就是触发事件。
编译类库,第一个部分完成。
第二个模块:宿主应用程序 (EventsLibHost.exe)
要创建此项目,您只需选择一个“基于控制台的应用程序”项目,并在项目向导选项中进行选择。我们将其命名为“EventsLibHost
”,将把新的 Project EventsLibHost
添加到工作区。
- 将
EventsLib
项目添加到宿主应用程序的引用中,因为我们要宿主该库。
假设
宿主应用程序将为服务公开以下终结点:
CalcService
将在“端口 9011”公开“HTTP 终结点”- 与 HTTP 终结点对应的“mex”终结点 (
IMetadatExchange
)
定义 CalcService 的配置
<!--********************************** Calc Service ********************************** -->
<service name="EventsLib.CalcService"
behaviorConfiguration="CalcServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="https://:9011/CalcService"/>
</baseAddresses>
</host>
<endpoint address="" binding="wsDualHttpBinding"
contract="EventsLib.ICalcService"/>
<endpoint address="mex" binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
解释
应用程序配置文件定义了一个终结点,该终结点使用 WSDualHttpBinding
在“9011 端口”上运行,以及相应的“mex”终结点。WSDualHttpBinding
与 WSHttpBinding
类似,但提供了更多 Web 服务功能。
定义服务的行为
<!-- ********************************** behaviors ********************************** -->
<behaviors>
<serviceBehaviors>
<!-- CalcService Behavior -->
<behavior name="CalcServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
解释
每个服务行为定义两个属性:
<serviceMetadata httpGetEnabled="true"/>
解释
获取或设置一个值,该值指示是否为通过 HTTP/GET 请求检索发布服务元数据,true 表示是,元数据可通过 HTTP/GET 请求检索。
<serviceDebug includeExceptionDetailInFaults="true "/>
解释
将 IncludeExceptionDetailsInFaults
设置为 true
以允许客户端获取有关内部服务方法异常的信息;这仅推荐作为临时调试服务应用程序的方法。此属性“在生产服务器上必须设置为 false”。配置完成后,让我们编写代码来宿主服务。
try
{
ServiceHost host = new ServiceHost(typeof(EventsLib.CalcService));
host.Open();
Console.WriteLine("Service is Hosted as https://:9011/CalcService");
Console.WriteLine("\nPress key to stop the service.");
Console.ReadLine();
host.Close();
}
catch (Exception eX)
{
Console.WriteLine("There was en error while Hosting Service [" + eX.Message +"]" );
Console.WriteLine("\nPress key to close.");
Console.ReadLine();
}
解释
ServiceHost
类为服务提供了一个宿主。您只需创建宿主对象并打开它,然后“无休止地等待”,直到按下某个键。当您按下某个键时,宿主将关闭。
构建项目。
打开一个具有“管理员权限(以管理员身份运行)”的新命令提示符,并按如下所示执行宿主应用程序:

第三个模块:客户端应用程序 (EventsClient.exe)
要创建此项目,您只需选择一个“基于控制台的应用程序”项目,并在项目向导选项中进行选择。我们将其命名为“EventsClient
”,将把新的 Project EventsClient
添加到工作区。
生成代理
当宿主应用程序正在运行时,右键单击客户端应用程序项目,然后单击
为 CalcService 生成代理
引用 –> 添加服务引用
在地址栏中,键入 CalcService
的 mex 终结点地址,如下所示:
现在,在添加了 CalcService
的引用后,接下来需要为服务触发的事件定义一个事件处理程序。
添加事件处理程序
向客户端应用程序添加一个新类,我们称之为 CalcServiceCallbackSink
,它继承自服务的回调接口 (ICalcServiceCallback
),并按照下述方式实现由 Callback
接口公开的方法:
class CalcServiceCallbackSink:CalcServiceReference.ICalcServiceCallback
{
public void Calculated(int nOp, double dblNum1, double dblNum2, double dblResult)
{
switch (nOp)
{
case 0: Console.WriteLine("\nOperation : Addition"); break;
case 1: Console.WriteLine("\nOperation : Subtraction"); break;
case 2: Console.WriteLine("\nOperation : Multiplication"); break;
case 3: Console.WriteLine("\nOperation : Division"); break;
}
Console.WriteLine("Operand 1 ...: {0}", dblNum1);
Console.WriteLine("Operand 2 ...: {0}", dblNum2);
Console.WriteLine("Result ......: {0}", dblResult);
}
public void CalculationFinished()
{
Console.WriteLine("Calculation completed");
}
}
一旦实现了服务事件的处理程序,现在就可以调用该服务了。
调用 CalcService
CalcServiceCallbackSink objsink = new CalcServiceCallbackSink ();
InstanceContext iCntxt = new InstanceContext(objsink);
CalcServiceReference.CalcServiceClient objClient =
new CalcServiceReference.CalcServiceClient(iCntxt);
objClient.SubscribeCalculatedEvent ();
objClient.SubscribeCalculationFinishedEvent ();
double dblNum1 = 1000, dblNum2 = 2000 ;
objClient.Calculate (0, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 4000;
objClient.Calculate(1, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 4000;
objClient.Calculate(2, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 400;
objClient.Calculate(3, dblNum1, dblNum2);
解释
创建一个 Events Sink 对象,创建一个 InstanceContext 对象,并将实现回调接口的类的对象作为服务实例上下文信息传递进去。
CalcServiceCallbackSink objsink = new CalcServiceCallbackSink ();
InstanceContext iCntxt = new InstanceContext(objsink);
创建一个服务代理对象。
CalcServiceReference.CalcServiceClient objClient =
new CalcServiceReference.CalcServiceClient(iCntxt);
选择您希望接收的事件。
objClient.SubscribeCalculatedEvent ();
objClient.SubscribeCalculationFinishedEvent ();
调用服务方法。
double dblNum1 = 1000, dblNum2 = 2000 ;
objClient.Calculate (0, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 4000;
objClient.Calculate(1, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 4000;
objClient.Calculate(2, dblNum1, dblNum2);
dblNum1 = 2000; dblNum2 = 400;
objClient.Calculate(3, dblNum1, dblNum2);
构建并执行,输出如下:
就这样,各位。
关注点
本文解释了 WCF 服务中的事件方面,如何触发带有或不带参数的事件,以及如何处理它们。
历史
- 初始版本