WCF异常处理

【读书笔记】

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

  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, 而是未被处理的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方法.

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

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

WCF异常处理的相关文章

收藏2篇关于WCF异常处理的文章

WCF Extensibility – IErrorHandler Exception Handling in WCF Web Service收藏2篇关于WCF异常处理的文章,码迷,mamicode.com

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

今天学习WCF分布式开发步步为赢系列的15节:错误契约(FaultContract)与异常处理(ExceptionHandle).本节内容作为WCF分布式开发的一个重要知识点,无论在学习还是项目中都应该有所了解.此前也和多位学习爱好者讨论过WCF异常处理的相关知识.这里就系统整理一下,共大家参考.同时也是对<WCF分布式开发步步为赢>系列文章的完善和补充.   本节主要涉及的知识点就是:[1].NET异常处理[2]WCF异常处理[3]错误契约[4]WCF异常处理扩展[5]示例代码分析,最后是[

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

http://www.cnblogs.com/artech/archive/2009/11/21/1607686.html 近半年以来,一直忙于我的第一本WCF专著<WCF技术剖析(卷1)>的写作,一直无暇管理自己的Blog.在<WCF技术剖析(卷1)>写作期间,对WCF又有了新的感悟,为此以书名开始本人的第三个WCF系列.本系列的目的在于对<WCF技术剖析>的补充,会对书中的一些内容进行展开讲述,同时会囊括很多由于篇幅的原因忍痛割弃的内容. [第1篇] 通过一个ASP

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

本章节将进行元数据和异常处理的介绍,这部分内容概念型比较强,可以快速浏览一下就好. 客户端和服务器借助于终结点进行通信,服务的提供者通过一个或者多个终结点将服务发布出来,服务的消费者则通过创建与之匹配的终结点进行服务的调用.可以将服务的元数据看做是它所有终结点的描述,它以一种易于交换的数据格式(WSDL, XSD, WS-POLICY)描述该服务的所有终结点信息.WCF提供了一个完整的元数据架构体系,易于元数据的导出.发布.获取和导入. 服务的元数据实际上是对其所具有的终结点的描述,终结点由地址

WCF服务全局统一异常处理机制

转载:http://www.csframework.com/archive/1/arc-1-20150109-2193.htm 服务端增加WCF服务全局异常处理机制,任一WCF服务或接口方式出现异常,将统一调用WCF_ExceptionHandler.ProvideFault方法,因此不需要每个方法使用try catch写法. C# Code: /// <summary> /// WCF服务端异常处理器 /// </summary> public class WCF_Excepti

服务端增加WCF服务全局异常处理机制

转自:http://www.csframework.com/archive/1/arc-1-20150109-2193.htm 服务端增加WCF服务全局异常处理机制,任一WCF服务或接口方式出现异常,将统一调用WCF_ExceptionHandler.ProvideFault方法,因此不需要每个方法使用try catch写法. 1 /// <summary> 2 /// WCF服务端异常处理器 3 /// </summary> 4 public class WCF_Exceptio

WCF初探-12:WCF客户端异常处理

前言: 当我们打开WCF基础客户端通道(无论是通过显式打开还是通过调用操作自动打开).使用客户端或通道对象调用操作,或关闭基础客户端通道时,都会在客户端应用程序中出现异常.而我们知道WCF是基于网络的通讯服务,错误异常也是要基于消息传递的,在WCF中提供了一个错误消息处理的类FaultException.接下来,我们看一下如何使用它在客户端处理异常. WCF异常类型: 意外异常:意外异常包括灾难性故障(如 OutOfMemoryException)和编程错误(如 ArgumentNullExce

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

WCF学习之旅—WCF中传统的异常处理(十六) 二.基于ServiceDebug的异常处理 从前面的示例中,可以看到客户端捕获了异常,这是我们处理异常的前提.为了有利于我们进行有效的调试,WCF提供了ServiceDebug Service Behavior.我们可以通过设置<serviceDebug includeExceptionDetailInFaults="True" />属性设为true,那么如果服务端抛出异常,WCF会简单得包装这个异常并把它置于Soap中Res

WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境下的重要意义,并阐明了其根本原因.但是,是否直接调用ICommunicationObject的Close方法将服务代理关闭就万事大吉了呢?事情远不会这么简单,这其中还会涉及关于异常处理的一些操作,这就是本篇文章需要讨论的话题. 一.异常的抛出与Close的失败 一般情况下,当服务端抛出异常,客户客户