前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?

WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?

作者头像
蒋金楠
发布2018-01-16 16:09:25
8420
发布2018-01-16 16:09:25
举报
文章被收录于专栏:大内老A

服务端只有抛出FaultException异常才能被正常地序列化成Fault消息,并实现向客户端传播。对于一般的异常(比如执行Divide操作抛出的DivideByZeroException),在默认的情况下,异常信息无法实现向客户端传递。但是,倘若为某个服务应用了ServiceDebugBehavior这么一个服务行为,并开启了IncludeExceptionDetailInFaults开关,异常信息将会原封不动地传播到客户端。WCF内部是如何处理抛出的非FaultException异常的呢?

实际上,WCF对非FaultException异常的处理并不复杂,我们现在就来简单介绍一下相关的流程:在执行服务操作过程中,如果抛出一个非FaultException异常,WCF会先判断IncludeExceptionDetailInFaults开发是否开启,如果没有,WCF会手工创建一个MessageFault对象,并根据当前线程的语言文化从资源文件中获取一段固定的文本作为MessageFaultFaultReason(就是我们在《WCF基本的异常处理模式[上篇]》的例子中看到的那段文字)。此外,固定的FaultCode被创建出来作为该MessageFault的Code。最终,WCF将该MessageFault转换成一个Fault消息,并采用固定的Action作为该消息的Action报头。所以,无论服务端抛出怎样的异常,客户端捕获的总是具有相同信息的FaultException异常。

注:客户端的错误信息总是这么一段文字:“由于内部错误,服务器无法处理该请求。有关该错误的详细信息,请打开服务器上的 IncludeExceptionDetailInFaults (从 ServiceBehaviorAttribute 或从 <serviceDebug> 配置行为)以便将异常信息发送回客户端,或在打开每个 Microsoft .NET Framework 3.0 SDK 文档的跟踪的同时检查服务器跟踪日志。”

上面说的是IncludeExceptionDetailInFaults开关关闭的情况。如果IncludeExceptionDetailInFaults开启,WCF则会基于该异常对象创建ExceptionDetail对象,并将该对象作为明细对象创建MessageFault(采用固定FaultCode)。最终,将此MessageFault转换生成Fault消息,当然Action也是采用固定的预定值。因此,在这种情况下,服务端抛出的信息总是能够原封不动地传递到客户端。而客户端捕获的总是一个泛型的FaultException<ExceptionDetail>异常。

一、ExceptionDetail对象为何能被反序列化?

对于异常对象的序列化和反序列化工作,最终都回落在FaultFormatter这么一个对象上(具体原理,可以参考《深入剖析WCF底层异常处理框架实现原理[中篇]》)。无论是虚列化还是反序列化,都具有一个根本的前提:确定对象的类型。FaultFormatter依赖创建时指定的一个FaultContractInfo列表来获知具体的类型,而该列表最初来源于应用在操作方法上FaultContractAttribute特性的定义。

那么,对于应用了ServiceDebugBehavior服务行为,并开启了IncludeExceptionDetailInFaults的场景,客户端如何能够把承载与Fault消息中的表示错误明细的XML片段通过反序列化生成ExceptionDetail对象的呢?由于我们不曾通过FaultContractAttribute特性将ExceptionDetail类型应用在相应的操作方法上面,FaultFormatter无法确定反序列化对象的类型,照理说反序列化是无法成功的,这是为何呢?

其实原因很简单,WCF在初始化FaultFormatter的时候会基于ExceptionDetail类型创建FaultContractInfo对象,并将其添加到属于自己的FaultContractInfo列表中。相应的实现基本上可以通过下面的伪代码体现:

代码语言:js
复制
   1: internal class FaultFormatter : IClientFaultFormatter, IDispatchFaultFormatter
   2: {
   3:     //其他成员
   4:     private FaultContractInfo[] faultContractInfos;
   5:     internal FaultFormatter(SynchronizedCollection<FaultContractInfo> faultContractInfoCollection)
   6:     {
   7:         List<FaultContractInfo> list= new List<FaultContractInfo>(faultContractInfoCollection);
   8:         faultContractInfoCollection.Add(new FaultContractInfo("http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault", typeof(ExceptionDetail)));
   9:         this.faultContractInfos = list.ToArray();
  10:     }    
  11: }

二、如果直接抛出FaultException<ExceptionDetail>异常呢?

既然FaultFormatter能够自动实现基于ExceptionDetail对象的序列化和反序列化,那么就意味着我们可以在具体的服务操作中直接抛出FaultException<ExceptionDetail>异常,而无须再将ExceptionDetail作为错误契约类型通过FaultContractAttribute特性应用到相应的服务操作上面了。同样以我们的计算服务为例,在Divide方法中我们直接用ExceptionDetail封装在运算过程中抛出的异常,最终抛出FaultException<ExceptionDetail>异常。

代码语言:js
复制
   1: using System.ServiceModel;
   2: using Artech.WcfServices.Contracts;
   3: using System;
   4: namespace Artech.WcfServices.Services
   5: {
   6:     [ServiceBehavior(Namespace="http://www.artech.com/")]
   7:     public class CalculatorService : ICalculator
   8:     {
   9:         public int Divide(int x, int y)
  10:         {            
  11:             try
  12:             {
  13:                 return x / y;
  14:             }
  15:             catch (Exception ex)
  16:             {
  17:                 throw new FaultException<ExceptionDetail>(new ExceptionDetail(ex), ex.Message);
  18:             }
  19:         }
  20:     }
  21: }

在客户端,我们就可以直接捕获FaultException<ExceptionDetail>异常了。下面的代码中,我们将捕获的FaultException<ExceptionDetail>异常相关的信息打印出来:

代码语言:js
复制
   1: using System;
   2: using System.ServiceModel;
   3: using Artech.WcfServices.Contracts;
   4: namespace Artech.WcfServices.Clients
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(
  11:                "calculatorservice"))
  12:             {
  13:                 ICalculator calculator = channelFactory.CreateChannel();
  14:                 using (calculator as IDisposable)
  15:                 {
  16:                     try
  17:                     {
  18:                         int result = calculator.Divide(1, 0);
  19:                     }
  20:                     catch (FaultException<ExceptionDetail> ex)
  21:                     {
  22:                         Console.WriteLine("Action: {0}", ex.Action);
  23:                         Console.WriteLine("Code: {0}:{1}", ex.Code.Namespace,ex.Code.Name);
  24:                         Console.WriteLine("Detail");
  25:                         Console.WriteLine("\tMessage: {0}", ex.Detail.Message);
  26:                         Console.WriteLine("\tType: {0}", ex.Detail.Type);
  27:                     }
  28:                 }
  29:             }
  30:             Console.Read();
  31:         }
  32: }
  33: }

输出结果(注意Action正好和在FaultFormatter中指定的值是一致的):

代码语言:js
复制
   1: Action: http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault
   2: Code:http://www.w3.org/2003/05/soap-envelope:Sender
   3: Detail:
   4:     Message:试图除以零。
   5:     Type:System.DivideByZeroException
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2009-11-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档