• [织梦吧]唯一域名:www.dedecms8.com,织梦DedeCMS学习平台.

当前位置: > 编程与数据库 > net编程 >

基于CallContextInitializer的WCF扩展导致的严重问题

来源: www.dedecms8.com 编辑:织梦吧 时间:2012-06-28点击:

 WCF是一个具有极高扩展度的分布式通信框架,无论是在信道层(Channel Layer)还是服务模型层(Service Model),我们都可以自定义相关组件通过相应的扩展注入到WCF运行环境中。在WCF众多可扩展点中,ICallContextInitializer可以帮助我们在服务操作执行前后完成一些额外的功能,这实际上就是一种AOP的实现方式。比如在《通过WCF Extension实现Localization》中,我通过ICallContextInitializer确保了服务操作具有和客户端一样的语言文化;在《通过WCF Extension实现Context信息的传递》中,我通过ICallContextInitializer实现上下文在客户端到服务端的自动传递。ICallContextInitializer的定义如下:

1: public interface ICallContextInitializer
2: {
3:   // Methods
4:   void AfterInvoke(object correlationState);
5:   object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
6: }

昨天,李永京同学问了我一个相关的问题。问题大概是这样的,他采用ICallContextInitializer实现WCF与NHibernate的集成。具体来说是通过ICallContextInitializer实现对事务 的提交,即通过BeforeInvoke方法初始化NHibernate的Session,通过AfterInvoke提交事务。但是,这中间具有一个挺严重的问题:当执行AfterInvoke提交事务的时候,是可能抛出异常的。一旦异常从AfterInvoke抛出,整个服务端都将崩溃。我们现在就来讨论一下这个问题,以及问题产生的根源。

一、问题重现

为了重现这个问题,我写了一个很简单的例子,你可以从这里下载该例子。首先我定义了如下一个实现了ICallContextInitializer接口的自定义CallContextInitializer:MyCallContextInitializer。在AfterInvoke方法中,我直接抛出一个异常。

1: public class MyCallContextInitializer : ICallContextInitializer
2: {
3:   public void AfterInvoke(object correlationState)
4:   {
5:     throw new Exception("调用MyCallContextInitializer.AfterInvoke()出错!");
6:   }
7:
8:   public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
9:   {
10:     return null;
11:   }
12: }

然后,我们通过ServiceBehavior的方式来应用上面定义的MyCallContextInitializer。为此,我们定义了如下一个实现了IServiceBehavior接口的服务行为:MyServiceBehaviorAttribute。在ApplyDispatchBehavior方法中,将我们自定义的 MyCallContextInitializer对象添加到所有终结点的分发运行时操作的CallContextInitializer列表中。

1: public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
2: {
3:   public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
4:
5:   public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
6:   {
7:     foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
8:     {
9:       foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
10:       {
11:         foreach (DispatchOperation operation in endpoint.DispatchRuntime.Operations)
12:         {
13:           operation.CallContextInitializers.Add(new MyCallContextInitializer());
14:         }
15:       }
16:     }
17:   }
18:
19:   public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
20: }然后,我们采用我们熟悉的计算服务的例子来验证MyCallContextInitializer对整个服务端运行时的影响。下面是服务契约和服务类型的定义,我们自定义的服务行为MyServiceBehaviorAttribute通过自定义特性的方式应用到CalculatorService 上面。

1: namespace Artech.Exception2CallContextInitializer.Contracts
2: {
3:   [ServiceContract(Namespace="http://www.artech.com/")]
4:   public interface ICalculator
5:   {
6:     [OperationContract]
7:     double Add(double x, double y);
8:   }
9: }

1: namespace Artech.Exception2CallContextInitializer.Services
2: {
3:   [MyServiceBehavior]
4:   public class CalculatorService:ICalculator
5:   {
6:     public double Add(double x, double y)
7:     {
8:       return x + y;
9:     }
10:   }
11: }

后然我们通过Console应用的方式来Host上面定义的CalculatorService,并创建另一个Console应用来模拟客户端对服务进行调用。由于相应的实现比较简单,在这里就不写出来了,对此不清楚的读者可以直接下载例子查看源代码。当你运行程序的时候,作为宿主的Console 应用会崩溃,相应的进程也会被终止。如果服务宿主程序正常终止,客户端会抛出如左图所示的一个CommunicationException异常。

如果在调用超时时限内,服务宿主程序没能正常终止,客户端则会抛出如右图所示的TimeoutException异常。

 

查看Event Log,你会发现两个相关的日志。它们的Source分别是:System.ServiceMode 3.0.0.0和.NET Runtime。两条日志相应的内容如下。如果你足够细心,你还会从中看到WCF一个小小的BUG。日志内容的第二行为“Message: ICallContextInitializer.BeforeInvoke threw an exception of type System.Exception: 调用MyCallContextInitializer.AfterInvoke()出错!”,实际上这里的 “ICallContextInitializer.BeforeInvoke”应该改成 “ICallContextInitializer.AfterInvoke”。下面一部分中你将会看到这个BUG是如何产生的。

About D8

  • ©2014 织梦吧(d8) DedeCMS学习交流平台
  • 唯一网址 www.DedeCMS8.com 网站地图
  • 联系我们 1170734538@qq.com ,  QQ