zoukankan      html  css  js  c++  java
  • WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?

    服务端只有抛出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列表中。相应的实现基本上可以通过下面的伪代码体现:

       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>异常。

       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>异常相关的信息打印出来:

       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中指定的值是一致的):

       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
    作者:Artech
    出处:http://artech.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    转载:MyBatis获取插入记录的自增长字段值
    006---抽象类
    005---组合
    004---继承与派生
    003---属性查找和绑定方法
    002---类与对象
    001---面向对象和面向过程的区别
    017---Django的中间件解决跨域
    10---git安装
    007---归并排序
  • 原文地址:https://www.cnblogs.com/artech/p/1609774.html
Copyright © 2011-2022 走看看