代码改变世界

WCF 第十章 异常处理 创建并使用强类型错误

2011-03-04 17:34  DanielWise  阅读(827)  评论(0编辑  收藏  举报

正如我们刚才描述的,一个基本的FaultException不提供一个在客户端创建鲁棒性try/catch/finally错误处理逻辑的特殊类型方式。为了在客户端开启这种类型的处理方式,考虑使用通用型的FaultException<>类。

  FaultException<>接收一个被序列化的异常数据结构的类型的定义。这可以是任何可以被序列化然后传输的类型,但是对那个有强类型化访问的类型的客户端来说,它必须能访问那个类型的定义。

  例如,我们可以使用列表10.6 的例子中的ArgumentException冰抛出一个FaultException<ArgumentException>. 在这个情况下当客户端是一个.NET 应用程序时,它肯定可以工作,因为每一边都有强类型化的消息来访问ArgumentException的细节。然而,当一个基于非.NET 技术的客户端,比如Java 尝试使用服务时会发生什么? Java 没有关于.NET 的ArgumentException的继承知识,所以Java代理将不会提供强类型化能力来访问我们服务返回的错误细节。

  为了保证相互影响的能力,服务的WSDL应该描述用来创建FaultException<> 实例的类型结构。后者通过提供一个错误契约实现。

使用FaultContract定义错误契约

记住数据契约用来定义由一个服务的WSDL定义表示的数据结构,一个客户端与服务端内部交互时可以精确地知道要提供的类型和数据结构。现在我们将使用与数据契约相同的概念来描述用来从一个服务端向一个调用方传递信息的结构。

  可以使用一个或者多个FaultContract属性来修饰一个服务操作。对WCF来说这意味着一个服务的WSDL应该包含在由操作潜在地抛出去的错误相关信息的细节中。这意味着代理生成工具将能够创建你用来表达错误信息的类的强类型表达。使用定义好的代理处理错误信息将帮助使用你的服务的开发人员创建鲁棒性和可信赖的客户端应用程序。注意因为细节包含在标准WSDL格式中,任何类型的工具可以为你的错误生成特定平台的代理,不仅仅是.NET 应用程序。

  让我们创建一个数据结构来表示一个返回给调用应用程序的错误条件的细节。创建一个类似列表10.9的TrackedFault类。

列表10.9 创建一个从FaultContract使用的DataContract

    [DataContract]
    public class TrackedFault
    {
        Guid mTrackingId;
        string mDetails;
        DateTime mDateTime;

        [DataMember]
        public Guid TrackingId
        {
            get { return mTrackingId; }
            set { mTrackingId = value; }
        }

        [DataMember]
        public string Details
        {
            get { return mDetails; }
            set { mDetails = value; }
        }

        [DataMember]
        public DateTime DateAndTime
        {
            get { return mDateTime; }
            set { mDateTime = value; }
        }

        public TrackedFault(Guid id, string details, DateTime dateTime)
        {
            mTrackingId = id;
            mDetails = details;
            mDateTime = dateTime;
        }
    }

定义一个FaultContract

在你已经有一个或多个你想用来向你的调用者表示异常数据的数据契约以后,向你的操作中添加FaultContract属性来确定关联数据契约的名字。

  例如,列表10.10 中的ApproveInvoice操作已经扩展来潜在地引发一个我们之前定义的基于TrackedFault数据契约的FaultException.

列表10.10 使用一个FaultContract扩展一个操作定义

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        [FaultContract(typeof(TrackedFault))]
        double Divide(double numerator, double denominator);
    }
注意 单向操作和FaultContracts
正如之前提到的,单向操作不向调用者访问消息,所以没有直接的返回错误的结构。因为错误没有返回,如果你使用一个FaultContract修饰任何单向操作,那么在服务载入时会抛出一个InvalidOperationException.

使用一个定义好的FaultContract来抛出一个FaultException<>

当在TrackedFault 数据契约内序列化数据时,WCF知道ApproveInvoice操作可能抛出一个异常,我们仅需要添加逻辑让那个数据契约变得可知并抛出异常。列表10.11通过从列表10.7的代码的扩展描述这个问题。

列表10.11 使用一个FaultContract抛出一个FaultException<>

    [ServiceBehavior(IncludeExceptionDetailInFaults=true)]
    public class Service1 : IService
    {
        public double Divide(double numerator, double denominator)
        {
            try
            {
                if (denominator == 0)
                {
                    throw new ArgumentOutOfRangeException("denominator", "Must be a numeric value less than or greator than zero");
                }
                return numerator / denominator;
            }
            catch (ArgumentOutOfRangeException exp)
            {
                TrackedFault tf = new TrackedFault(Guid.NewGuid(),
                    exp.Message, DateTime.Now);
                throw new FaultException<TrackedFault>(tf,
                    new FaultReason("ArgumentOutOfRangeException"),
                    FaultCode.CreateReceiverFaultCode(new FaultCode("Divide")));
            }
            catch (Exception exp)
            {
                List<FaultReasonText> frts = new List<FaultReasonText>();
                frts.Add(new FaultReasonText("Error processing divide"));
                frts.Add(new FaultReasonText("<French translation>", new CultureInfo("fr-FR")));
                frts.Add(new FaultReasonText("<Czech translation>", new CultureInfo("cs-CZ")));
                throw new FaultException(new FaultReason(frts), FaultCode.CreateReceiverFaultCode(new FaultCode("Divide")));
            }
        }
    }

错误契约策略

有很多已有的策略来使用错误契约描述你的服务。你可以定义一个被所有拟公司服务使用的共享契约代码库。错误七月可能对一个单独的服务或者应用是特殊的,包括系统特定的特殊细节。你可能决定为可能由你的服务引发的潜在服务创建一个特殊契约。

  我们建议在你每次创建WCF服务工程时,你要考虑为你的异常策略使用一个综合的基于FaultContract的方案。小心地使用FaultContracts为你的操作修饰预期的异常将允许客户端应用程序使用强类型化表示的可能由你的服务抛出的异常来创建客户端。

  无论你适合使用哪种策略,确保你持续地使用那个策略以便于通过你的服务开发出的客户端将可以有持续的可维护性和可用性。