今天学习WCF分布式开发步步为赢系列的15节:错误契约(FaultContract)与异常处理(ExceptionHandle)。本节内容作为WCF分布式开发的一个重要知识点,无论在学习还是项目中都应该有所了解。此前也和多位学习爱好者讨论过WCF异常处理的相关知识。这里就系统整理一下,共大家参考。同时也是对《WCF分布式开发步步为赢》系列文章的完善和补充。
   本节主要涉及的知识点就是:【1】.NET异常处理【2】WCF异常处理【3】错误契约【4】WCF异常处理扩展【5】示例代码分析,最后是【6】总结部分。
   首先我们来回忆一下.NET里一个重要的概念异常处理ExceptionHandle。异常处理在JAVA平台也有自己的机制,这个不是一个WCF特有的概念,同样要了解WCF的异常处理,我们有必要先来了解其前身.NET的异常处理相关的概念。
【1】.NET异常处理:
     .NET Framework 中的托管异常是凭借 Win32 结构化异常处理机制实现的。公共语言运行库提供了一个模型,以统一的方式通知程序发生的错误,这样为设计容错软件提供了极大的帮助。所有的.NET Framework操作都通过引发异常来指示出现错误。 传统上,语言的错误处理模型依赖于语言检测错误和查找错误处理程序的独特方法,或者依赖于操作系统提供的错误处理机制。运行库实现的异常处理具有下列特点:     
        处理异常时不用考虑生成异常的语言或处理异常的语言。     
        异常处理时不要求任何特定的语言语法,而是允许每种语言定义自己的语法。     
        允许跨进程甚至跨计算机边界引发异常。     
        与其他错误通知方法(如返回代码)相比,异常具有若干优点。不再有出现错误而不被人注意的情况。无效值不会继续在系统中传播。不必检查返回代码。可以轻松添加异常处理代码,以增加程序的可靠性。最后,运行库的异常处理比基于Windows 的C++错误处理更快。   
    由于执行线程例行地遍历托管代码块和非托管代码块,因此运行库可以在托管代码或非托管代码中引发或捕捉异常。非托管代码可以同时包含C++样式的   SEH   异常和基于COM 的HRESULT。 
     异常处理使用 trycatch 和 finally 关键字尝试某些操作,以处理失败情况,尽管这些操作有可能失败,但如果您确定需要这样做,且希望在事后清理资源,就可以尝试这样做。公共语言运行时 (CLR)、.NET Framework 或任何第三方库或者应用程序代码都可以生成异常。异常是使用 throw 关键字创建的。(MSDN)
【2】WCF异常处理:
   现在我们来了解一下WCF的异常处理机制。 前面我们介绍了.NET的异常处理机制。WCF也是.NET框架的一部分,很多一场处理方式基本相同。但是由于其跨服务平台的目标要求,导致了WCF并不支持传统的异常处理方式。传统方式上.NET抛出的未经处理的异常会立即终止主进程,而WCF则不会。
【2.1】WCF错误类型:
   在进行WCF分布式应用开发的过程中,我们客户端经常会遇到一下三种常见的错误。
(1):通信错误,可能和网络、通道等相关的异常,客户端表现为Communication Exception;
(2):代理和通道的State,代理已经关闭,或者通道Fault,等问题,这个比较常见。一般通道闲置时间过久,通道会出现这个状态错误的问题。一般我们可以通过代理的State来判断。安全验证失败也会导致这个错误。
(3):服务调用错误,服务调用时抛出的异常,这个服务内部异常会序列化传递给客户端,被客户端捕获。
    第三种是我们本节要详细讲述的类型。
【2.2】FaultException:
   这里最关键的问题就是,WCF服务抛出的异常信息往往是基于.NET的内部异常信息,这些信息不能被序列化,也就不能在客户端和服务端实现共享错误信息。
    因此如果在WCF服务中采用传统的方式处理异常,由于异常消息不能被序列化,因而客户端无法捕获和处理服务抛出的异常。为了解决这个问题,WCF提供了FaultException。这个是一个基于行业标准的SOAP异常类,WCF会将无法识别的异常处理为统一的FaultException异常对象,因此,可以把错误信息传递到客户端,客户端可以捕获FaultException或者Exception。你可以通过.NET Reflector查看此类的定义,FaultException的部分代码如下:

[Serializable, KnownType(typeof(FaultReasonData[])), KnownType(typeof(FaultCodeData)), KnownType(typeof(FaultCodeData[])), KnownType(typeof(FaultReasonData))]
public class FaultException : CommunicationException
{
    // Fields
    private string action;
    private FaultCode code;
    private MessageFault fault;
    internal const string Namespace = "http://schemas.xmlsoap.org/Microsoft/WindowsCommunicationFoundation/2005/08/Faults/";
    private FaultReason reason;     // Methods
    public FaultException();
}

我们可以注意到FaultException具有序列化的标记。声明了Serializable.
FaultException的另外一个重要的泛型定义就是FaultException<T>,这里我们可以使用任何系统类型或者自定义类型来传递错误信息,T代表要传递的错误细节。此类也可以使用反射器查看代码:

Code

【3】错误契约FaultContract:
    说道WCF的异常处理,这里就必须介绍一下一个重要的概念:错误契约(FaultContract)。
   前面已经介绍了WCF的FaultException,那为什么我们还要FaultContract。
   默认情况,WCF会把服务抛出的异常以FaultException类型传递到客户端。
  但是WCF规定,任何客户端和服务共享的异常,必须属于服务行为的特性。也就是我们必须给特性的服务操作定义了服务契约才能够提供错误类型。WCF为异常处理专门提供了FaultContract特性,它可以被应用到服务操作上,指明操作可能会抛出的异常类型。代码如下:

[AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=false)]
public sealed class FaultContractAttribute : Attribute
{
    // Fields
    private string action;
    private bool hasProtectionLevel;
    private string name;
.
}

错误契约FaultContract其实是WCF4大契约中的一种(Service Contract,DataContract,MessageContract,FaultContract ).声明一个服务操作契约方法为FaultContract并指定了响应的类型参数,这个参数要和FaultException<T>的类型一致。
【4】WCF异常处理扩展:
WCF允许开发者定制异常报告和异常传递的过程。但是要实现System.ServiceModel.Dispatcher.IErrorHandler接口:
IErrorHandler的定义如下:

public interface IErrorHandler
{
    // Methods
    bool HandleError(Exception error);
    void ProvideFault(Exception error, MessageVersion version, ref Message fault);
}

服务端抛出的异常会再调用ProvideFault()方法后再返回给客户端。ProvideFault不考虑异常的类型。
    另外ProvideFault一个重要的作用,异常提升(Exception Promotion)。它可以将非FaultContract异常提升为FaultContract<T>异常,例如将OverflowException异常提升为FaultExceptino< OverflowException>异常。而HandleError()方法,我们可以重新实现代码,如log日志。
  此外,如果要安装自己定制的IErrorHandle,需要将它添加到对应的分发器里即可。
   我们在服务类实现System.ServiceModel.Description.IServiceBehavior接口ApplyDispatchBehavior()方法中来实现安装错误处理扩展。IServiceBehavior的主要方法ApplyDispatchBehavior:

public interface IServiceBehavior
{
    // Methods
    void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters);
    void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
    void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
}

【5】示例代码分析:
    下面我们给出本节文章的简单示例代码。
【5.1】服务端:
    这里我们定义了两个服务操作方法,主要是为了测试WCF的异常处理,SayHello和SayGoodBye。参数是个string 类型的name,长度大于10的时候就会抛出一个OverflowException内存溢出的异常,然后我们通过FaultException来包装,传递到客户端。这里我们也对操作契约应用了FaultContract特性。定义代码如下:

 //1.服务契约
    [ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
    public interface IWCFService
    {
        //操作契约
        [OperationContract]
        [FaultContract(typeof(OverflowException))]//标注以后,WCF客户端才能区分错误契约,否则会作为通信异常抛出
        string SayHello(string name);
        //操作契约
        [OperationContract]
        [FaultContract(typeof(OverflowException))]
        string SayGoodBye(string name);
    }
    //2.服务类,继承接口。实现服务契约定义的操作
    public class WCFService : IWCFService
    {
        //实现接口定义的方法
        public string SayHello(string name)
        {
            if (name.Length < 10)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Hello! {0},", name);
                return "Hello! " + name;
            }
            else
            {//泛型类FaultException,可以包含不同的异常,可以序列化。基于SOAP的错误消息
                OverflowException oe = new OverflowException();
                throw new FaultException<OverflowException>(oe, "name Length is more than 10");
            }
        }
        //实现接口定义的方法
        public string SayGoodBye(string name)
        {
            if (name.Length < 10)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("GoodBye! {0},", name);
                return "Hello! " + name;
            }
            else
            {
                OverflowException oe = new OverflowException();
                throw new FaultException<OverflowException>(oe,"name Length is more than 10");
            }
        }
    }

【5.2】宿主:
    这里宿主依然是Console 程序,自托管WCF服务。会打印每次的调用信息。比较简单,代码如下:

 static void Main(string[] args)
        {
            //反射方式创建服务实例,
            //Using方式生命实例,可以在对象生命周期结束时候,释放非托管资源
            using (ServiceHost host = new ServiceHost(typeof(WCFService.WCFService)))
            {
                ////判断是否以及打开连接,如果尚未打开,就打开侦听端口
                if (host.State != CommunicationState.Opening)
                    host.Open();
                //显示运行状态
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("Host is runing! and state is {0}", host.State);
                Console.ForegroundColor = ConsoleColor.Red;
                //print endpoint information
                foreach (ServiceEndpoint se in host.Description.Endpoints)
                {
                    Console.WriteLine("Host is listening at {0}", se.Address.Uri.ToString());                 }
                //等待输入即停止服务
                Console.Read();
            }
        }

【5.3】客户端:
   客户端分别传递两次参数,第一次正常调用,第二次参数长度大于10,引起服务端异常,然后跑出到客户端,客户端根据定义的FaultException<T>来处理异常。代码如下:

//WSHttpBinding_IWCFService
            Test.WCFServiceClient wcfServiceProxyHttp = new Test.WCFServiceClient("WSHttpBinding_IWCFService");
            //通过代理调用SayHello服务
            try
            {
                Console.WriteLine(wcfServiceProxyHttp.SayHello("Frank Xu"));
                Console.WriteLine(wcfServiceProxyHttp.SayHello("Frank Xu Lei WSHttpBinding"));
            }
            catch (FaultException<OverflowException> oe)
            {
                Console.WriteLine(oe.Message);
            }
            catch (CommunicationException ce)
            {
                Console.WriteLine(ce.Message);
            }
            finally 
            { 
                wcfServiceProxyHttp.Close(); 
            }
           
            //For Debug
            Console.WriteLine("Press any key to exit");
            Console.Read();

这里运行程序,测试结果如下:

【6】总结:
    以上就是WCF异常处理的全部内容。一下几点我们要注意:
    (1)  即使我们不加FaultContract特性,或者抛出非FaultException异常,客户端也可以获得异常消息,该异常会导致通道出现错误。
    (2)此外我们也可以通过添加ServiceBehavior特性,将服务的IncludeExceptionDetailInFaults设置为true(默认为 false),客户端也可以捕获抛出的非FaultException异常信息,但该异常仍然会导致通道出现错误。
    (3)异常对WCF服务实例的影响和服务的激活类型有关。通常单调PerCall和会话模式,WCF服务异常会导致服务释放服务实例,而Single单例模式不会,WCF服务会继续运行。
    (4)在发布服务与部署服务时,我们应避免将服务的IncludeExceptionDetailInFaults设置为true。这回带来性能问题。
    最后给出本文的参考代码:/Files/frank_xl/WCFServiceExceptionHandleFrankXuLei.rar
参考文章:
1.http://msdn.microsoft.com/zh-cn/library/ms173160.aspx
2.http://msdn.microsoft.com/zh-cn/library/ms954830.aspx
3.WCF Services Programming

WCF分布式开发步步为赢(15):错误契约(FaultContract)与异常处理(ExceptionHandle)的更多相关文章

  1. WCF分布式开发步步为赢(5)服务契约与操作重载

    继上一节WCF分布式开发步步为赢系列的(4):WCF服务可靠性传输配置与编程开发,本节我们继续学习WCF分布式开发步步为赢的第(5)节:服务契约与操作重载.这里我们首先讲解OOP面向对象的编程中方法重 ...

  2. WCF分布式开发步步为赢(6):WCF服务契约继承与分解设计

    上一节我们学习了WCF分布式开发步步为赢(5)服务契约与操作重载部分.今天我们来继续学习WCF服务契约继承和服务分解设计相关的知识点.WCF服务契约继承有何优势和缺点?实际项目里契约设计有什么原则和依 ...

  3. WCF分布式开发步步为赢(7):WCF数据契约与序列化

    本节继续学习WCF分布式开发步步为赢(7):WCF数据契约与序列化.数据契约是WCF应用程序开发中一个重要的概念,毫无疑问实现客户端与服务端数据契约的传递中序列化是非常重要的步骤.那么序列化是什么?为 ...

  4. WCF分布式开发步步为赢(11):WCF流处理(Streaming)机制

    WSE3.0框架提供了数据优化传输机制,WSE3.0构建Web服务安全(4):MTOM消息传输优化和文件上传.下载 疑问里进行了介绍.WCF同样也提供了流操作来支持大数据对象的传输和处理优化机制,今天 ...

  5. WCF分布式开发步步为赢(4):WCF服务可靠性传输配置与编程开发

    今天继续WCF分布式开发步步为赢系列的第4节:WCF服务可靠性传输配置与编程开发.这个章节,我们要介绍什么是WCF服务的可靠性传输,随便介绍网络协议的概念,Web Service为什么不支持可靠性传出 ...

  6. WCF分布式开发步步为赢(3)WCF服务元数据交换、配置及编程开发

    今天我们继续WCF分布式开发步步为赢(3)WCF服务元数据交换.配置及编程开发的学习.经过前面两节的学习,我们了解WCF分布式开发的相关的基本的概念和自定义宿主托管服务的完整的开发和配置过程.今天我们 ...

  7. WCF分布式开发步步为赢(1):WCF分布式框架基础概念

    众所周知,系统间的低耦合一直是大型企业应用系统集成追寻的目标,SOA面向服务架构的出现为我们的如何利用现有企业系统资源进行企业ERP系统设计和实现提供了重要的参考原则.SOA如此炙手可热,各大厂商都推 ...

  8. WCF分布式开发步步为赢(13):WCF服务离线操作与消息队列MSMQ

    之前曾经写过一个关于MSMQ消息队列的文章:WCF分布式开发必备知识(1):MSMQ消息队列 ,当时的目的也是用它来作为学习WCF 消息队列MSMQ编程的基础文章.在那篇文章里,我们详细介绍了MSMQ ...

  9. WCF分布式开发步步为赢(12):WCF事务机制(Transaction)和分布式事务编程

    今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程.众所周知,应用系统开发过程中,事务是一个重要的概念.它是保证数据与服务可靠性的重要机制. ...

随机推荐

  1. Django的aggregate()和annotate()函数的区别

    aggregate() aggregate()为所有的QuerySet生成一个汇总值,相当于Count().返回结果类型为Dict. annotate() annotate()为每一个QuerySet ...

  2. 332. Reconstruct Itinerary

    class Solution { public: vector<string> path; unordered_map<string, multiset<string>& ...

  3. 如何打war包

    1. 利用jdk里的工具 例如我们要打包的文件在D:\myHome\dist: 运行 cmd: cd D:\myHome\dist 进入D:\myHome\dist 然后输入 D:\myHome\di ...

  4. 机器学习之-sklearn

    https://www.cnblogs.com/lianyingteng/p/7811126.html sklearn官方文档: http://scikit-learn.org/stable/

  5. CSS流布局权威指南

    http://www.cnblogs.com/qieguo/p/5421252.html

  6. jmeter上传视频图片附件

    http上传附件一般用的Content-Type: multipart/form-data;文中是先通过fiddler抓取手机端的请求,然后通过jmeter模拟该请求,如果有接口文档,则可以跳过抓包这 ...

  7. C++学习004-Go To 语句使用

    C++中,goto语句主要负责语句的跳转,可以用在循环中跳出循环 注意gotu语句是无条件跳转,用的时候一定要谨慎,一定要少 编写环境 Qt 5.7 for(int i = 0;i<100;i+ ...

  8. 【LoadRunner】解决LR11无法录制Chrome浏览器脚本问题

    LoadRunner录制脚本时,遇到高版本的IE.FireFox,或者Chrome浏览器,会出现无法录制脚本的问题,下面就来讲一下如何利用LR自带的wplus_init_wsock.exe插件进行脚本 ...

  9. thinkPHP form表单提交参数无法获取

    后台打印获取的数据为empty, 找了半天,是因为 input标签没有写name, 真是醉了!记一下,免得以后再犯错了.

  10. 福大软工1816:Alpha(3/10)

    Alpha 冲刺 (3/10) 队名:第三视角 组长博客链接 本次作业链接 团队部分 团队燃尽图 工作情况汇报 张扬(组长) 过去两天完成了哪些任务: 文字/口头描述: 1.学习qqbot库: 2.实 ...