【读书笔记】

在进行分布式应用的异常处理时需要解决和考虑的基本要素:

  1. 异常的封装:服务端抛出的异常如何序列化传递到客户端
  2. 敏感信息的屏蔽:抛出的异常往往包含一些敏感的信息,直接将服务操作执行过程抛出的异常信息抛出到客户端,存在极大风险
  3. 系统的集成和互操作:基于不同厂商和技术平台系统之间的有效集成和互操作也给异常处理提出了新的要求,基于平台A的服务队异常的描述弱项被基于平台B的客户端理解,需要按照一种厂商中立的标准来规范对异常的描述

1. 异常处理模式

异常从服务端抛出

  • 异常类型:应用异常(Application Exception)和基础结构异常(Infrastructure Exception). 前者与业务相关; 后者业务无关,主要体现在:对象序列化,消息的处理,消息传输和消息分发等.

异常细节的传播

  • 将IncludeExceptionDetailInFaults设置为true,可将异常细节暴露出来.
  • 客户端捕获的实际上是一个泛型的System.ServiceModel.FaultException异常. FaultException<
    TDetail>继承自FaultException
  • 所有从服务端抛出的异常,只有类型为FaultException(或其子类)的异常才能直接被序列化并最终通过消息返回给客户端.FaultException允许将错误细节定义在一个对象上,而泛型参数TDetail表示这个对象的类型.

        [Serializable]
    public class FaultException<TDetail>:FaultException
    {
    //Else Member
    public FaultException(TDetail detail);
    public TDetail Detail {get;} //只读属性,表示指定的错误细节对象
    }
  • 不特殊指定,客户端捕获的异常类型实际上是FaultException. 也就是说,其具体的泛型参数为System.ServiceModel.ExceptionDetail.

        [DataContract]
    public class ExceptionDetail
    {
    //Else Member
    public ExceptionDetail(Exception ex); [DataMember]
    public string HelpLink {get; private set;}
    [DataMember]
    public ExceptionDetail InnerException{get; private set;}
    [DataMember]
    public string Message {get;private set;}
    [DataMember]
    public string StackTrace {get; private set;}
    [DataMember]
    public string Type {get; private set;} }

    实际上,ExceptionDetail是WCF专门设计出来用于封装服务端抛出的异常信息的,属性HelpLinek,InnerException,StackTrack对应Exception的同名属性,属性Type表示异常的类型.

  • 两种极端的异常传播机制:要么完全对客户端屏蔽,要么完全暴露于客户端

自定义异常信息

  • 通过FaultException直接指定错误信息
  • 通过FaultException采用自定义类型封装错误(可序列化对象,有SerializableAttribute或者DataContract)
        [DataContract(Namespace="http://www.xiaoxiao.com")]
    public class CalculationError
    {
    public CalculationError(string operation,string message)
    {
    this.Operation=operation;
    this.Message = message;
    } [DataMember]
    public string Operation {get; set;}
    [DataMembar]
    public string Message {get; set;}
    } //Usage
    var error=new CalcuationError("Divide","被除数y不能为0");
    throw new FaultException<CalcuationError>(error,error.Message);

    但是客户端并不会捕获异常类型FaultException<CalcuationError>, 而是未被处理的FaultException.

错误契约(Fault Contract)

客户端能够捕获到服务端抛出的FaultException异常,并不是说客户端捕获的异常就是服务端抛出的异常. 这个过程经历了对异常的序列化和反序列化过程. 错误细节对象序列化为XML并通过SOAP消息的形式返回到客户端,客户端需要通过反序列化以重建错误细节对象.而反序列化的前提是需要知道其类型. 错误契约(FaultContract)帮助将作为错误细节对象的类型确定下来.

  • WCF通过System.ServiceModel.FaultContractAttribute特性定义错误契约. 由于错误契约是基于操作级别的,所以该特性应用于服务契约类型的操作契约方法成员上.

        [AttributeUsage(AttributeTargets.Methord,AllowMultiple=true, Inherited=false)]
    public sealed class FaultContractAttribute:Attribute
    {
    public FaultContractAttribute(Type detailType);
    public string Action {get; set;} //错误消息<Action>报头的值.如果没有被显示指定,那么它将按照先的规则进行指定:{服务契命名空间}/{服务契约名称}/{操作契约名称}{细节类型名称}Fault; public Type DetailType {get;} //用于封装错误信息的错误细节类型 public string Name {get; set;} //错误细节被序列化成XML元素的名称, 未显示指定,将使用DetailType对应的数据契约名称
    public string Namespace {get;set;} //命名空间 未显示指定,将使用DetailType对应的数据契约命名空间 public bool HasProtectionLevel {get;} //保护级别
    public ProtectionLevel ProtectionLevel {get; set;} //保护级别
    } [ServiceContract(Namespace="http://www.xiaoxiao.com")]
    public interface ICalculator
    {
    [OperationContract]
    [FaultContract(typeof(CalculatorError))]
    int Divide(int x, int y);
    }
  • 错误契约是服务元数据(Metadata)的一部分. 当服务元数据通过WSDL的形式被发布后,错误契约作为操作描述的一部分被写入.
  • 对应错误契约的应用,不仅仅是在将自定义的错误细节类型(比如我们定义的CalculatorError)应用到服务契约响应操作上时才需要显示的在操作方法上应用FaultContractAttribute特性,对于一些基元类型(如:Int32,string等),也需要这么做.
        //usage
    throw new FaultException<string>("y can't to be zero!"," the parameter y can't to be zero!"); [OperationContract]
    [FaultContract(typeof(string))]
    int Divide(int x, int y);

通过XmlSerializer对错误细节对象序列化

  • WCF提供了DataContractSerializer和XmlSerializer. 默认序列化器是DataContractSerializer
  • 可以通过使用XmlSerializerFormatAttribute特性选择XmlSerializer完成序列化和反序列化工作. 默认情况下,XmlSerializerFormatAttribute特性仅仅控制操作的参数和返回值的序列化行为,而不能控制错误细节对象的序列化行为. 可以通过SupportFaults=true来显示地选择XmlSerializer作为错误细节对象的序列化器
        [OperationContract]
    [FaultContract(typeof(CalcuatorError),Name="CalculationError")]
    [XmlSerializerFormat(SupportFaults=true)]
    int Divide(int x, int y);

错误消息与FaultException异常

  • 编程角度: 错误信息的载体是FaultException; 消息交换角度:错误信息则是通过错误消息来承载的.

FautlNode

由于在整个SOAP消息的路由过程中,错误可能发生在最终接收节点,也可能发生在中间节点,因此为了使SOAP错误消息的接收者能够判断导致错误的SOAP节点类型,在生成错误消息的时候,可以通过Node元素指定节点的类型.

唯一可被传播的异常: FaultException

WCF体系下,数据存在的形态大体分为两种:XML和托管对象.

FaultException异常和错误消息之间的转换

  • Message <=> MessageFault <=> FaultException

        //Message 转 MessageFault:
    public class MessageFault
    {
    public static MessageFault CreateFault(Message message, int maxBufferSize)
    } //MessageFault 转 Message :
    public class Message
    {
    public static Message CreateMessage(MessageVersion version, MessageFault fault, string action)
    }

    WCF中将实现MessageFault和FaultException之间的转换的应用编程接口在FaultException中,两个静态方法实现MessageFault向FaultException的转换,而实例方法CreateMessageFault则将FaultException转换为MessageFault对象.其中faultDetailTypes表示错误细节类型列表,这是为对FaultException对象的反序列化服务的.

        public class FaultException:CommunicationException
    {
    //Else
    public static FaultException CreateFault(MessageFault messageFault,params Type[] faultDetailTypes);
    public static FaultException CreateFault(MessageFault messageFault, string action, params Type[] faultDetailTypes); public virtual MessageFault CreateMessageFault();
    }
FaultException 和 MessageFault转换的核心 FaultFormatter

MessageFormater实现了在正常的服务调用过程中方法调用和消息之间的转换. 但是当异常(这里只FaultException)从服务端抛出后,WCF需要一个类似实现类似功能, 即在服务端对异常对象进行序列化并生成错误消息,在客户端对接收到的错误消息进行反序列化重建并抛出异常. 该使命由FaultFormatter担当
FaultFormatter在客户端和服务端所扮演的角色是不同的:

  • 客户端通过解析回复错误消息生成的MessageFault,该MessageFault最终被转换成FaultException异常并抛出
  • 服务端则将抛出的FaultException异常转换成MessageFault,以便后续的步骤生成响应的错误消息
        internal interface IClientFaultFormatter
    {
    FaultException Deserialize(MessageFault messageFault, string action);
    } internal interface IDispatchFaultFormatter
    {
    MessageFault Serialize(FaultException faultException, out string action);
    } internal class FaultFormatter:IClientFaultFormatter,IDispatchFaultFormatter
    {
    public FaultException Deserialize(MessageFault messageFault, string action);
    public MessageFault Serialize(FaultException faultException, out string action);
    }

WCF异常处理体系剖析

  • 消息交换是WCF进行通信的唯一手段,消息不仅仅是正常服务调用请求和服务的载体,服务端抛出的异常也是通过消息的形式传向客户端的.
  • FaultFormatter在服务端端创建于服务端寄宿之时. 在ServiceHost被初始化的过程中,WCF会为服务的每个终结点创建响应的终结点分发器(EndpointDispatcher). 而对于每一个被创建出来的终结点分发器都具有一个响应的分发运行时(DispatchRuntime). 分发运行时是整个WCF运行时框架的核心,一系列的对象和组件被它引用以实现对整个消息分发和操作执行行为的控制.

异常的抛出,序列化,反序列化和捕获

如果服务操作在执行过程中抛出FaultException异常,WCF会获取当前分发操作的FaultFormatter,调用Serialize方法对异常对象进行序列化. 序列化完成后得到相应的MessageFault对象和Action值,这两个值最终通过调用Message的CreateMessage静态方法生成一个错误消息对象

客户端的服务调用最终通过客户端操作对象完成.当调用服务获得回复消息后,如果回复消息是错误消息,WCF会调用MessageFault的CreateFault将消息转换成MessageFault对象,并获取Action值. 最终通过客户端操作的得到FaultFormatter,传入MessageFault对象和Action值调用Deserialize方法在客户端重建FaultException异常对象并将其抛出来

ExceptionDetial为何能被反序列化

对于应用了IncludeExceptionDetailInFaults属性的True的ServiceDebugBehavior服务行为,客户端是如何将错误消息中显示错误细节的XML反序列化成ExceptionDetail? 由于我们不曾通过FaultContractAttribute特性将ExceptionDetail类型应用在相应的操作方法中,因此FaultFormatter无法确定反序列化的对象类型。
原因是WCF在初始化FaultFormatter的时候会给予ExceptionDetail类型创建FaultContractInfo对象,并将其添加到属于自己的FaultContract列表中。

WCF异常处理扩展

错误处理器实现了System.ServiceModel.Dispatcher.IErrorHandler

    public interface IErrorHandler
{
bool HandlerException(Exception error);
     void ProvideFault(Exception error, MessageVersion version, ref Message fault);
} 
  • WCF运行时角度,错误处理器隶属于信道分发器,它维护着错误处理器列表。可以将多个错误处理器应用到相应的信道分发器上,这些分发器最终连成一个管道。每个错误处理器实现某个单一异常处理任务,如日志记录,敏感信息屏蔽等

        public class ChannelDispatcher:ChannelDispatcherBase
    {
    //else
    public Collection<IErrorHandler> ErrorHandlers {get;}
    }
  • 异常抛出后,WCF会遍历响应信道分发器的ErrorHandlers属性表示的错误处理集合,并调用错误处理器的ProvideFault方法.

对于ProvideFault方法的执行,有一些值得注意的地方。该方法的执行发生在会话终结之前,并且是在执行操作方法的线程中执行的。换句话说,ProviderFault方法是以与操作方法同步的方式执行的,所以在自定义ErrorHandler的时候,不应该将一些比较耗时的操作实现放在ProviderFault中。

  • 当与信道分发器关联的所有错误处理器的ProvideFault执行后,错误消息被传送给客户端.此后,错误处理器的HandleError方法被依次调用(异步方式执行),用于执行一些本地的异常处理操作(服务端本地行为,与客户端无关). 此时,可以实现一些相对耗时的操作.如 异常日志记录

WCF异常处理的更多相关文章

  1. WCF入门(十二)---WCF异常处理

    WCF服务开发者可能会遇到需要以适当的方式向客户端报告一些不可预见的错误.这样的错误,称为异常,通常是通过使用try/catch块来处理,但同样,这是非常具体的技术. 由于客户端的关注领域不是关于如何 ...

  2. WCF基础教程之异常处理:你的Try..Catch语句真的能捕获到异常吗?

    在上一篇WCF基础教程之开篇:创建.测试和调用WCF博客中,我们简单的介绍了如何创建一个WCF服务并调用这个服务.其实,上一篇博客主要是为了今天这篇博客做铺垫,考虑到网上大多数WCF教程都是从基础讲起 ...

  3. WCF入门(七)——异常处理1

    首先以一个简单的例子演示一下远程调用发生异常的结果: 服务器端代码如下: [ServiceContract] public interface IService1 { [OperationContra ...

  4. WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]

    原文:WCF技术剖析之二十一: WCF基本的异常处理模式[上篇] 由于WCF采用.NET托管语言(C#和NET)作为其主要的编程语言,注定以了基于WCF的编程方式不可能很复杂.同时,WCF设计的一个目 ...

  5. WCF分布式开发步步为赢(15):错误契约(FaultContract)与异常处理(ExceptionHandle)

    今天学习WCF分布式开发步步为赢系列的15节:错误契约(FaultContract)与异常处理(ExceptionHandle).本节内容作为WCF分布式开发的一个重要知识点,无论在学习还是项目中都应 ...

  6. 快速入门系列--WCF--04元数据和异常处理

    本章节将进行元数据和异常处理的介绍,这部分内容概念型比较强,可以快速浏览一下就好. 客户端和服务器借助于终结点进行通信,服务的提供者通过一个或者多个终结点将服务发布出来,服务的消费者则通过创建与之匹配 ...

  7. 《WCF技术剖析》博文系列汇总[持续更新中]

    原文:<WCF技术剖析>博文系列汇总[持续更新中] 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖 ...

  8. WCF学习之旅—基于Fault Contract 的异常处理(十八)

       WCF学习之旅—WCF中传统的异常处理(十六) WCF学习之旅—基于ServiceDebug的异常处理(十七) 三.基于Fault Contract 的异常处理 第二个示例是通过定制Servic ...

  9. WCF学习之旅—基于ServiceDebug的异常处理(十七)

    WCF学习之旅—WCF中传统的异常处理(十六) 二.基于ServiceDebug的异常处理 从前面的示例中,可以看到客户端捕获了异常,这是我们处理异常的前提.为了有利于我们进行有效的调试,WCF提供了 ...

随机推荐

  1. hdu1198Farm Irrigation(dfs找联通)

    题目链接: 啊哈哈,选我选我 思路是:首先依据图像抽象出联通关系.. 首先确定每一种图形的联通关系.用01值表示不连通与不连通... 然后从第1个图形进行dfs搜索.假设碰到两快田地能够联通的话那么标 ...

  2. C# MVC VS WebAPI

    获取路径: MVC:Server.MapPath("/Templates/vshop/default.json") WebAPI:System.Web.Hosting.Hostin ...

  3. GitHub上编程语言流行度分析

    GitHub已然是全球最流行的开源项目托管平台,项目数量眼下已经达到了千万级别.Adereth在Counting Stars on GitHub一文提供了一个很有意思的思路,那就是籍GitHub用户通 ...

  4. 基于JQuery实现表单元素值的回写

    form.jsp: <%@ page language="java" import="java.util.*" pageEncoding="GB ...

  5. 把握linux内核设计思想(二):硬中断及中断处理

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 操作系统负责管理硬件设备.为了使系统和硬件设备的协同工作不减少机器性能.系统和 ...

  6. c# emit 动态实现接口

    using System; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Te ...

  7. 去掉activity默认动画效果的方法

    非常多手机都会自带一些Activity切换动画,项目中假设我们须要禁用掉系统Activity切换的动画.能够使用例如以下方法: 一.重写Activity的Them中的windowAnimationSt ...

  8. 链接脚本使用一例2---将二进制文件 如图片、MP3音乐、词典一类的东西作为目标文件中的一个段

    参考文章: <程序员的自我修养——链接.转载与库> P68 这里介绍两种方法,实现将将一张图片作为二进制可执行程序的一个段,其中第一种方法在我之前的博客中已经有所介绍,不过,那是采用的是交 ...

  9. Linux进入单用户模式

    有时候配置linux的过程中,因为一些误操作导致系统初始化时堵塞或挂起而无法进入系统,原因往往是因为配置文件设置错误,部分文件被误删之类.遇到这种情况一般新手的做法就是重装(虚拟机不装白不装),但在实 ...

  10. Linux下Kafka单机安装配置方法

    Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语: •Kafka将消息以topi ...