原文:WCF技术剖析之二十一:WCF基本异常处理模式[下篇]

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

  1. 1: [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]

  1. 2: public sealed class FaultContractAttribute : Attribute

  1. 3: {

  1. 4: //省略成员

  1. 5: }

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

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

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

  1. 1: using System.ServiceModel;

  1. 2: namespace Artech.WcfServices.Contracts

  1. 3: {

  1. 4: [ServiceContract(Namespace = "http://www.artech.com/")]

  1. 5: public interface ICalculator

  1. 6: {

  1. 7: [OperationContract]

  1. 8: [FaultContract(typeof(CalculationError))]

  1. 9: [FaultContract(typeof(CalculationError))]

  1. 10: int Divide(int x, int y);

  1. 11: }

  1. 12: }

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

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

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

  1. 1: using System.ServiceModel;

  1. 2: namespace Artech.WcfServices.Contracts

  1. 3: {

  1. 4: [ServiceContract(Namespace = "http://www.artech.com/")]

  1. 5: public interface ICalculator

  1. 6: {

  1. 7: [OperationContract]

  1. 8: [FaultContract(typeof(CalculationError), Name = "CalculationError")]

  1. 9: [FaultContract(typeof(CalculationError), Name = "CalculationException")]

  1. 10: int Divide(int x, int y);

  1. 11: }

  1. 12: }

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

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

  1. 1: using System.ServiceModel;

  1. 2: namespace Artech.WcfServices.Contracts

  1. 3: {

  1. 4: [ServiceContract(Namespace = "http://www.artech.com/")]

  1. 5: public interface ICalculator

  1. 6: {

  1. 7: [OperationContract]

  1. 8: [FaultContract(typeof(CalculationError),

  1. 9: Name = "CalculationError", Namespace = "http://www.artech.com/")]

  1. 10: [FaultContract(typeof(CalculationException),

  1. 11: Name = "CalculationError", Namespace = "http://www.artech.com/")]

  1. 12: int Divide(int x, int y);

  1. 13: }

  1. 14: }

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

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

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

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

  1. 1: using System;

  1. 2: using System.Runtime.Serialization;

  1. 3: namespace Artech.WcfServices.Contracts

  1. 4: {

  1. 5: [DataContractAttribute(Namespace="http://www.artech.com/")]

  1. 6: public class CalculationError

  1. 7: {

  1. 8: [DataMember]

  1. 9: public string Operation

  1. 10: { get; set; }

  1. 11: [DataMember]

  1. 12: public string Message

  1. 13: { get; set; }

  1. 14: }

  1. 15: 

  1. 16: [DataContractAttribute(Namespace = "http://www.artech.com/", Name = "CalculationError")]

  1. 17: public class CalculationFault

  1. 18: {

  1. 19: [DataMember]

  1. 20: public string Fault

  1. 21: { get; set; }

  1. 22: }

  1. 23: }

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

  1. 1: using System.ServiceModel;

  1. 2: namespace Artech.WcfServices.Contracts

  1. 3: {

  1. 4: [ServiceContract(Namespace = "http://www.artech.com/")]

  1. 5: public interface ICalculator

  1. 6: {

  1. 7: [OperationContract]

  1. 8: [FaultContract(typeof(CalculationError))]

  1. 9: [FaultContract(typeof(CalculationFault))]

  1. 10: int Divide(int x, int y);

  1. 11: }

  1. 12: }

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

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

  1. 1: [DataContractAttribute(Namespace = "http://www.artech.com/", Name = "CalculationError")]

  1. 2: public class CalculationFault

  1. 3: {

  1. 4: [DataMember(Name = "Operation")]

  1. 5: public string OperationName

  1. 6: { get; set; }

  1. 7: [DataMember(Name = "Message")]

  1. 8: public string Fault

  1. 9: { get; set; }

  1. 10: }

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

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

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

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

  1. 1: [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]

  1. 2: public sealed class XmlSerializerFormatAttribute : Attribute

  1. 3: {

  1. 4: public XmlSerializerFormatAttribute();

  1. 5: public OperationFormatStyle Style { get; set; }

  1. 6: public bool SupportFaults { get; set; }

  1. 7: public OperationFormatUse Use { get; set; }

  1. 8: }

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

  1. 1: using System.ServiceModel;

  1. 2: namespace Artech.WcfServices.Contracts

  1. 3: {

  1. 4: [ServiceContract(Namespace = "http://www.artech.com/")]

  1. 5: public interface ICalculator

  1. 6: {

  1. 7: [OperationContract]

  1. 8: [FaultContract(typeof(CalculationError), Name = "CalculationError")]

  1. 9: [XmlSerializerFormat(SupportFaults = true)]

  1. 10: int Divide(int x, int y);

  1. 11: }

  1. 12: }

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

  1. 1: using System;

  1. 2: using System.Runtime.Serialization;

  1. 3: using System.Xml;

  1. 4: using System.Xml.Serialization;

  1. 5: namespace Artech.WcfServices.Contracts

  1. 6: {

  1. 7: [Serializable]

  1. 8: public class CalculationError

  1. 9: {

  1. 10: [XmlAttributeAttribute("op")]

  1. 11: public string Operation

  1. 12: { get; set; }

  1. 13: [XmlElement("Error")]

  1. 14: public string Message

  1. 15: { get; set; }

  1. 16: }

  1. 17: }

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

  1. 1: <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

  1. 2: <s:Header>

  1. 3: <a:Action s:mustUnderstand="1">http://www.artech.com/ICalculator/DivideCalculationError</a:Action>

  1. 4: <a:RelatesTo>urn:uuid:7b01995b-9f81-4a08-9fa2-c5ef8c7cacc</a:RelatesTo>

  1. 5: </s:Header>

  1. 6: <s:Body>

  1. 7: <s:Fault>

  1. 8: <s:Code>

  1. 9: <s:Value>s:Sender</s:Value>

  1. 10: </s:Code>

  1. 11: <s:Reason>

  1. 12: <s:Text xml:lang="zh-CN">被除数y不能为零!!</s:Text>

  1. 13: </s:Reason>

  1. 14: <s:Detail>

  1. 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">

  1. 16: <Error>被除数y不能为零!!</Error>

  1. 17: </CalculationError>

  1. 18: </s:Detail>

  1. 19: </s:Fault>

  1. 20: </s:Body>

  1. 21: </s:Envelope>

作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

WCF技术剖析之二十一:WCF基本异常处理模式[下篇]的更多相关文章

  1. WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]

    原文:WCF技术剖析之二十一: WCF基本的异常处理模式[上篇] 由于WCF采用.NET托管语言(C#和NET)作为其主要的编程语言,注定以了基于WCF的编程方式不可能很复杂.同时,WCF设计的一个目 ...

  2. WCF技术剖析之二十一:WCF基本异常处理模式[中篇]

    原文:WCF技术剖析之二十一:WCF基本异常处理模式[中篇] 通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultExcept ...

  3. WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]

    原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码 ...

  4. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序) 基于HTTP-GET的元数据发布方式与基于WS-MEX原理类似,但是ServiceMetad ...

  5. WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]

    原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ...

  6. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序) 通过<如何将一个服务发布成WSDL[编程篇]>的介绍我们知道了如何可以通过编程或者配 ...

  7. WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇]

    原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇] 对于WCF服务端元数据架构体系来说,通过MetadataExporter将服务的终结点导出成MetadataSet(参考< ...

  8. WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[扩展篇]

    原文:WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[扩展篇] 通过<实现篇>对WSDL元素和终结点三要素的之间的匹配关系的介绍,我们知道了WSDL的Binding ...

  9. WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇]

    原文:WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇] 元数据的导出就是实现从ServiceEndpoint对象向MetadataSet对象转换的过程,在WCF元数据框 ...

随机推荐

  1. (Problem 29)Distinct powers

    Consider all integer combinations ofabfor 2a5 and 2b5: 22=4, 23=8, 24=16, 25=32 32=9, 33=27, 34=81, ...

  2. C陷阱与缺陷(四)

    第四章 连接 4.1 什么是连接器 C语言中的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合在一起.典型的连接器把由编译器或汇编器生成的若干个目标模块,整合 ...

  3. JS使用合并数组

    var arr= [4,5,6]; var arr1 = [7,8,9]; var arr2=[1,2,3]; arr.concat(arr1,arr2); //或者使用Arry.prototype. ...

  4. 函数重载不仅仅是看其参数,还要看是否有const修饰

    比如QString有两个函数,可以堂而皇之的存在,原因就在于有了const修饰以后,编译器不把两个函数当作同一个函数名了: QChar * data() const QChar * data() co ...

  5. spring mvc 分页

    spring mvc 分页

  6. 解题报告 HDU1789 Doing Homework again

    Doing Homework again Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64 ...

  7. 数据结构——栈(Stacks)

    栈遵循LIFO ( last in first out) 即后入先出原则 栈结构类似于叠盘子 后叠上去的要先拿走 才能拿到下面的盘子 因此stack是一种访问受限的线性存储结构 用单向链表的结构来存储 ...

  8. CSS实现背景图尺寸不随浏览器大小而变化的两种方法

    一些网站的首页背景图尺寸不随浏览器缩放而变化,本例使用CSS 实现背景图尺寸不随浏览器缩放而变化,方法一. 把图片作为background,方法二使用img标签.喜欢的朋友可以看看   一些网站的首页 ...

  9. [置顶] ios 360度旋转效果demo

    demo功能:用UIimageView实现360度旋转效果. demo说明:iPhone6.1 测试成功.主要代码在:FVImageSequence.m中.在touchesMoved事件中,通过替换U ...

  10. ios8 swift开发:显示变量的类名称

    var ivar = [:] ivar.className // __NSDictionaryI var i = 1 i.className // error: 'Int' does not have ...