zoukankan      html  css  js  c++  java
  • 艾伟_转载:WCF基本异常处理模式[下篇] 狼人:

      从FaultContractAttribute的定义我们可以看出,该特性可以在同一个目标对象上面多次应用(AllowMultiple = true)。这也很好理解:对于同一个服务操作,可能具有不同的异常场景,在不同的情况下,需要抛出不同的异常。

       1: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
       2: public sealed class FaultContractAttribute : Attribute
       3: {    
       4:     //省略成员
       5: }

      但是,如果你在同一个操作方法上面应用了多了FaultContractAttribute特性的时候,需要遵循一系列的规则,我们现在就来逐条介绍它们。

      一、多次声明相同的错误明细类型

      比如在下面的代码中,对于操作Divide,通过FaultContractAttribute特性对同一个错误明细类型CalculationError进行了两次设置。

       1: using System.ServiceModel;
       2: namespace Artech.WcfServices.Contracts
       3: {    
       4:     [ServiceContract(Namespace = "http://www.artech.com/")]
       5:     public interface ICalculator
       6:     {
       7:         [OperationContract]
       8:         [FaultContract(typeof(CalculationError))]
       9:         [FaultContract(typeof(CalculationError))]
      10:         int Divide(int x, int y);
      11:     }   
      12: }

      WCF服务端框架在初始化ServiceHost,并创建服务表述的时候(关于服务描述,以及在服务寄宿过程中对服务描述的创建,《WCF技术剖析(卷1)》的第7章有详细的介绍),会抛出如图1所示的InvalidOperationException异常。

    clip_image002

    图1 多次声明相同的错误明细类型导致的异常

      但是,如果你在应用FaultContractAttribute特性指定相同错误明细类型的同时,指定不同的Name或者Namespace,这是允许的。比如下面的代码中,在两个FaultContractAttribute特性中,同样是指定的相同的错误明细类型CalculationError,由于我们为之指定了不同的Name,在寄宿服务的时候将不会有上述异常的发生。

       1: using System.ServiceModel;
       2: namespace Artech.WcfServices.Contracts
       3: {    
       4:     [ServiceContract(Namespace = "http://www.artech.com/")]
       5:     public interface ICalculator
       6:     {
       7:         [OperationContract]
       8:         [FaultContract(typeof(CalculationError), Name = "CalculationError")]
       9:         [FaultContract(typeof(CalculationError), Name = "CalculationException")]
      10:         int Divide(int x, int y);
      11:     }   
      12: }

      二、多次声明不同的具有相同有效名称错误明细类型

      多次声明的错误类型的类型虽然不同,但是如果我们为其指定相同的Name和Namespace我们可以将Name和Namespace的组合称为有效名称QN:Qualified Name),这依然是不允许的。比如下面的代码中,通过FaultContractAttribute特性为Divide操作指定了两个不同的错误明细类型(CalculationError和CalculationException),但是设置的名称却是相同的(CalculationError)。

       1: using System.ServiceModel;
       2: namespace Artech.WcfServices.Contracts
       3: {    
       4:     [ServiceContract(Namespace = "http://www.artech.com/")]
       5:     public interface ICalculator
       6:     {
       7:         [OperationContract]
       8:         [FaultContract(typeof(CalculationError), 
       9: Name = "CalculationError", Namespace = "http://www.artech.com/")]
      10:         [FaultContract(typeof(CalculationException), 
      11: Name = "CalculationError", Namespace = "http://www.artech.com/")]
      12:         int Divide(int x, int y);
      13:     }   
      14: }

      对于这种情况,在服务寄宿的时候,依然会和上面一样抛出一个InvalidOperationExcepiton异常,如图2所示:

    clip_image004

    图2 多次申明具有相同有效名称导致的异常

      三、多次声明不同的具有相同数据契约有效名称的错误明细类型

      还有另一种情况:虽然是多次申明的是不同的错误明细类型,但是通过DataContractAttribute特性定义它们的时候,指定了相同的名称和命名空间。如果我们将它们通过FaultContractAttribute特性应用到同一个操作上面,又会出现怎样的问题了。比如,在下面的代码中,我们定义了两个不同错误明细类型(CalculationError和CalculationFault),它们具有相同的数据契约名称(CalculationError)和命名空间(http://www.artech.com/)。

       1: using System;
       2: using System.Runtime.Serialization;
       3: namespace Artech.WcfServices.Contracts
       4: {
       5:     [DataContractAttribute(Namespace="http://www.artech.com/")]
       6:     public class CalculationError
       7:     {       
       8:         [DataMember]
       9:         public string Operation
      10:         { get; set; }
      11:         [DataMember]
      12:         public string Message
      13:         { get; set; }
      14:     }
      15:  
      16: [DataContractAttribute(Namespace = "http://www.artech.com/", Name = "CalculationError")]
      17:     public class CalculationFault
      18:     {
      19:         [DataMember]
      20:         public string Fault
      21:         { get; set; }
      22:     }
      23: }

      如果我们通过下面的方式通过FaultContractAttribute特性将这两个类型应用到同一个服务操作上面,服务寄宿不会出什么问题,客户端的方法调用也能正常运行。

       1: using System.ServiceModel;
       2: namespace Artech.WcfServices.Contracts
       3: {    
       4:     [ServiceContract(Namespace = "http://www.artech.com/")]
       5:     public interface ICalculator
       6:     {
       7:         [OperationContract]
       8:         [FaultContract(typeof(CalculationError))]
       9:         [FaultContract(typeof(CalculationFault))]
      10:         int Divide(int x, int y);
      11:     }   
      12: }

      但是,当我们试图通过HTTP-GET或者标准的MEX终结点获取以WSDL表示的服务元数据(Metadata)的时候就会出现问题。至于为什么会导致这样的问题,你大体可以这样来理解:当WCF为某个操作的错误描述(Fault Description)的时候,会创建一个字典来存储通过FaultContractAttribute特性指定的基于错误明细类型的数据契约。对于这个字典来说,它的Key为数据契约的有效名称(QN:Qualified Name),即名称和命名空间组合。由于CalculationError和CalculationFault具有相同的名称和命名空间,这无疑会造成Key的冲突。

      由于数据契约是使对数据结构的一种描述,如果两个数据契约时等效的,不管其具体的托管类型是什么,WCF在遇到上述情况的时候,会自动识别并忽略其中一个,从而保证元数据能够正确产生。比如说,如果我们将CalculationFault进行如下的改写,服务的元数据就能够被正常地获得了。

       1: [DataContractAttribute(Namespace = "http://www.artech.com/", Name = "CalculationError")]
       2: public class CalculationFault
       3: {
       4:     [DataMember(Name = "Operation")]
       5:     public string OperationName
       6:     { get; set; }
       7:     [DataMember(Name = "Message")]
       8:     public string Fault
       9:     { get; set; }
      10: }

      四、通过XmlSerializer对错误明细对象进行序列化

      对于任何分别是框架来说,序列化和反序列化都是其功能体系中重要的一环。WCF通过一个重要的对象实现对托管对象的序列化和反序列化:序列化器(Serializer)。具体来说,所有序列化和反序列化的功能又最终落实到两个具体的序列化器上:DataContractSerializer和XmlSerializer。关于这两种序列化器,在《WCF技术剖析(卷1)》第5章中已经有过深入的探讨,在这里就需要在画蛇添足了。

      WCF采用的默认序列化器是DataContractSerializer,但是有的时候,我们需要显示地控制某个服务或者服务的某个操作的序列化行为,通过XmlSerializer来序列化和反序列化操作的参数对象和返回值。举个例子,一个服务的绝大部分操作的参数类型都是通过数据契约的方式定义,但是对于个别的操作参数类型依然沿用的是传统XML的定义方式。在这种情况下,我们希望的是专门对这几个操作进行定制,让它们采用XmlSerializer作为它们的序列化器。

      我们可以通过对自定义特性System.ServiceModel.XmlSerializerFormatAttribute的应用帮助我们是相上面的功能。从先面对XmlSerializerFormatAttribute的定义我们可以看出:应用特性的目标元素的类型包括接口、类和方法。也就是说,XmlSerializerFormatAttribute既可以应用于服务契约接口上,也可以应用于服务类型上,甚至可以应用于服务接口和服务类型的方法上。

       1: [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
       2: public sealed class XmlSerializerFormatAttribute : Attribute
       3: {   
       4:     public XmlSerializerFormatAttribute();  
       5:     public OperationFormatStyle Style { get; set; }
       6:     public bool SupportFaults { get; set; }
       7:     public OperationFormatUse Use { get; set; }
       8: }

      在默认的情况下,XmlSerializerFormatAttribute特性仅仅控制操作的参数和返回值的序列化行为,而不能控制错误明细对象的序列化行为。也就是说,基于在某个操作方法上应用了XmlSerializerFormatAttribute特性,WCF会采用XmlSerializer作为所有参数和返回值的序列化器,对于出现异常指定的错误明细对象,依然采用默认的DataContractSerializer进行序列化和反序列化。我们可以通过SupportFaults属性来显式地选择XmlSerializer作为错误明细对象的序列化器。在下面的代码中,我们将XmlSerializerFormatAttribute特性应用在服务契约的Divide操作上面,并将SupportFaults属性设为true。

       1: using System.ServiceModel;
       2: namespace Artech.WcfServices.Contracts
       3: {    
       4:     [ServiceContract(Namespace = "http://www.artech.com/")]
       5:     public interface ICalculator
       6:     {
       7:         [OperationContract]
       8:         [FaultContract(typeof(CalculationError), Name = "CalculationError")]
       9:         [XmlSerializerFormat(SupportFaults = true)]
      10:         int Divide(int x, int y);
      11:     }
      12: }

      那么对于Divide操作,WCF将会采用XmlSerializer同时作为参数、返回值和错误明细对象的序列化器。比如在这个时候,我们采用下面的形式对CalculationError进行重新定义:

       1: using System;
       2: using System.Runtime.Serialization;
       3: using System.Xml;
       4: using System.Xml.Serialization;
       5: namespace Artech.WcfServices.Contracts
       6: {
       7:     [Serializable]
       8:     public class CalculationError
       9:     {       
      10:         [XmlAttributeAttribute("op")]
      11:         public string Operation
      12:         { get; set; }
      13:         [XmlElement("Error")]
      14:         public string Message
      15:         { get; set; }
      16:     }
      17: }

      在被零除而抛出异常的情况下,WCF将会生成如下一个Fault SOAP,其中s:Body>// 节点中的XML为CalculationError对象序列化所的。

       1: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
       2:   <s:Header>
       3:     <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/DivideCalculationErrora:Action>
       4:     <a:RelatesTo>urn:uuid:7b01995b-9f81-4a08-9fa2-c5ef8c7cacca:RelatesTo>
       5:   s:Header>
       6:   <s:Body>
       7:     <s:Fault>
       8:       <s:Code>
       9:         <s:Value>s:Senders:Value>
      10:       s:Code>
      11:       <s:Reason>
      12:         <s:Text xml:lang="zh-CN">被除数y不能为零!!s:Text>
      13:       s:Reason>
      14:       <s:Detail>
      15:         <CalculationError op="Divide" xmlns="http://www.artech.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      16:           <Error>被除数y不能为零!!Error>
      17:         CalculationError>
      18:       s:Detail>
      19:     s:Fault>
      20:   s:Body>
      21: s:Envelope>   
  • 相关阅读:
    MySQL:逻辑库与表管理
    MySQL:初识数据库
    MySQL:安装与配置
    C语言之指针
    C语言之二维数组
    C语言之冒泡排序
    C语言之数组
    C语言之函数的声明
    C语言之带有返回值的函数
    C语言之全局变量和局部变量
  • 原文地址:https://www.cnblogs.com/waw/p/2157072.html
Copyright © 2011-2022 走看看