今天学习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。 

     异常处理使用 trycatchfinally 关键字尝试某些操作,以处理失败情况,尽管这些操作有可能失败,但如果您确定需要这样做,且希望在事后清理资源,就可以尝试这样做。公共语言运行时 (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代表要传递的错误细节。此类也可以使用反射器查看代码:

错误契约(FaultContract)与异常处理(转)_客户端

[Serializable]

public class FaultException<TDetail> : FaultException

{

    // Fields

    private TDetail detail;


    // Methods

    public FaultException(TDetail detail);

    public FaultException(TDetail detail, FaultReason reason);

    public FaultException(TDetail detail, string reason);

    protected FaultException(SerializationInfo info, StreamingContext context);

    public FaultException(TDetail detail, FaultReason reason, FaultCode code);

    public FaultException(TDetail detail, string reason, FaultCode code);

    public FaultException(TDetail detail, FaultReason reason, FaultCode code, string action);

    public FaultException(TDetail detail, string reason, FaultCode code, string action);

    public override MessageFault CreateMessageFault();

    [SecurityCritical, SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter=true)]

    public override void GetObjectData(SerializationInfo info, StreamingContext context);

    public override string ToString();

    // Properties

    public TDetail Detail { get; }

}

【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)与异常处理(转)_序列化_02.

}

   错误契约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},错误契约(FaultContract)与异常处理(转)_序列化_02", 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},错误契约(FaultContract)与异常处理(转)_序列化_02", 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错误契约(FaultContract)与异常处理(转)_序列化_02");

            Console.Read();

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

错误契约(FaultContract)与异常处理(转)_异常处理_06

【6】总结:

    以上就是WCF异常处理的全部内容。一下几点我们要注意:

    (1)  即使我们不加FaultContract特性,或者抛出非FaultException异常,客户端也可以获得异常消息,该异常会导致通道出现错误。

    (2)此外我们也可以通过添加ServiceBehavior特性,将服务的IncludeExceptionDetailInFaults设置为 true(默认为 false),客户端也可以捕获抛出的非FaultException异常信息,但该异常仍然会导致通道出现错误。

    (3)异常对WCF服务实例的影响和服务的激活类型有关。通常单调PerCall和会话模式,WCF服务异常会导致服务释放服务实例,而Single单例模式不会,WCF服务会继续运行。

    (4)在发布服务与部署服务时,我们应避免将服务的IncludeExceptionDetailInFaults设置为true。这回带来性能问题。