【读书笔记】

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

  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. MySQL经常使用命令--create命令使用

    切换数据库的字符集 在mysql中的配置文件里: 使用vim /etc/mysql/my.cnf [client] default-character-set = utf8 [mysqld] char ...

  2. eletron 播放rtmp flash 播放器问题

    1 安装 flash https://www.flash.cn/ 2 man.js 配置 参考 https://newsn.net/say/electron-flash-win.html 3 播放器 ...

  3. Oracle TNS路径

    修改tnsnames.oRA,监听文件   Oracle TNS路径 G:\Oracle\product\11.2.0\client_1\network\admin\tnsnames.oRA

  4. .NET实现爬虫

    前几天看到一个.NET Core写成的爬虫,有些莫名的小兴奋,之前一直用集搜客去爬拉勾网的招聘信息,这个傻瓜化工具相当于用HTML模板页去标记DOM节点,然后在浏览器窗口上模拟人的浏览行为同时跟踪节点 ...

  5. Android加载网络图片学习过程

    好多应用,像我们公司的<乘友>还有其他的<飞鸽><陌陌><啪啪>这些,几乎每一款应用都需要加载网络图片,那ToYueXinShangWan,这是比须熟练 ...

  6. Django之信息聚合

    feeds.py #coding:utf-8 __author__ = 'similarface' from django.contrib.syndication.views import Feed ...

  7. Hadoop关于Wrong FS错误

    关于使用java api上传文件. 在定义一个FileSystem变量的时候伪分布式和单机版的方法是不一样的,单机版使用的是FileSystem类的静态函数 FileSystem hdfs = Fil ...

  8. Office 365 开发入门

    <Office 365 开发入门指南>公开邀请试读,欢迎反馈 终于等来了这一天,可以为我的这本新书画上一个句号.我记得是在今年的2月份从西雅图回来之后,就萌发了要为中国的Office 36 ...

  9. iOS开发入门

    https://github.com/qinjx/30min_guides/blob/master/ios.md 任何C源程序,不经修改,即可通过Objective-C编译器成功编译 Objectiv ...

  10. 禁用android studio自身的ndk编译disable automatic ndk-build call

    1,让studio不自动编译jni文件,而是我们手动通过ndk-build编译    打开工程下面的app文件夹, 找到build.gradle   添加如下:  defaultConfig {   ...