WCF基本异常处理模式[上篇]

  由于WCF采用.NET托管语言(C#和NET)作为其主要的编程语言,注定以了基于WCF的编程方式不可能很复杂。同时,WCF设计的一个目的就是提供基于非业务逻辑的通信实现,为编程人员提供一套简单易用的应用编程接口(API)。WCF编程模式的简单性同样体现在异常处理上面,本篇文章的主要目的就是对WCF基于异常处理的编程模式做一个简单的介绍。

  一、当异常从服务端抛出

  对于一个典型的WCF服务调用,我个人倾向于将潜在抛出的异常费为两种类型:应用异常(Application Exception)和基础结构(Infrastructure Exception)。前者为应用级别,主要体现为执行某个服务操作的业务逻辑抛出的异常;而后者则是业务无关的,通过WCF本身的基础架构抛出,主要体现在对象的序列化、消息的处理、消息传输和消息的分发等等。在这里我们更多地关注与应用异常。

  首先,我们在不做任何异常处理相关操作的情况下,看看如果在服务端执行某个服务操作的过程中抛出异常后,客户端会得到怎样的结果。我们通过实例的形式来演示这中场景。处于简单和易于理解考虑,我们照例沿用计算服务的例子。

  我们照例采用典型的四层结构(Contract、Service、Hosting和Client),具体的层次在VS解决方案的划分如图1所示:

图1 异常抛出实例解决方案结构

  下面代码片断表示服务契约(ICalculator)和服务类型(CalculatorService)的定义。为了简洁,在服务契约接口中,我们仅仅定义了唯一一个用于进行两个整数触发预算的方法Divide。服务契约和服务类型类型分别定义在项目Contracts和Services中。

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: int Divide(int x, int y); 9: } 10: } 1: using Artech.WcfServices.Contracts; 2: namespace Artech.WcfServices.Services 3: { 4: public class CalculatorService : ICalculator 5: { 6: public int Divide(int x, int y) 7: { 8: return x / y; 9: } 10: } 11: }

  接下来是通过Console应用程序(Hosting项目)对上面定义的WCF服务(CalculatorService)进行寄宿(Host)的代码和相关配置。

1: using System; 2: using System.ServiceModel; 3: using Artech.WcfServices.Services; 4: namespace Artech.WcfServices.Hosting 5: { 6: public class Program 7: { 8: static void Main(string[] args) 9: { 10: using (ServiceHost host = new ServiceHost(typeof(CalculatorService))) 11: { 12: 13: host.Open(); 14: Console.Read(); 15: } 16: } 17: } 18: } 1: xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <system.serviceModel> 4: <services> 5: <service name="Artech.WcfServices.Services.CalculatorService"> 6: <endpoint address="http://127.0.0.1:3721/calculatorservice" binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" /> 7: service> 8: services> 9: system.serviceModel> 10: configuration>

  最后在代表客户端的Console应用程序(Client项目)中对计算服务CalculatorService进行调用。相关的服务调用代码和配置如下所示,为了让服务端在执行Divide操作的时候抛出异常,特意将第二个参数设置为0,以便服务在进行除法运算的时候抛出System.DivideByZeroException异常。

1: using System; 2: using System.ServiceModel; 3: using Artech.WcfServices.Contracts; 4: namespace Artech.WcfServices.Clients 5: { 6: class Program 7: { 8: static void Main(string[] args) 9: { 10: using (ChannelFactory channelFactory = new ChannelFactory( 11: "calculatorservice")) 12: { 13: ICalculator calculator = channelFactory.CreateChannel(); 14: using (calculator as IDisposable) 15: { 16: int result = calculator.Divide(1, 0); 17: } 18: } 19: } 20: } 21: } 1: xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <system.serviceModel> 4: <client> 5: <endpoint address="http://127.0.0.1:3721/calculatorservice" 6: binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" name="calculatorservice" /> 7: client> 8: system.serviceModel> 9: configuration>

  在启动服务寄宿程序(Hosting)后执行客户端服务调用程序,在客户端将会跑出如图2所示的类型为  System.ServiceModel.FaultException的异常,其错误消息为:

  “由于内部错误,服务器无法处理该请求。有关该错误的详细信息,请打开服务器上的 IncludeExceptionDetailInFaults (从 ServiceBehaviorAttribute 或从 配置行为)以便将异常信息发送回客户端,或在打开每个 Microsoft .NET Framework 3.0 SDK 文档的跟踪的同时检查服务器跟踪日志。”

图2 客户端捕获从服务端抛出的异常

  从上面的实例演示中,我们可以获知WCF在默认情况下的异常处理行为:对于服务端抛出的异常(这里主要指应用异常),客户端捕获到的总一个具有相同异常消息的System.ServiceModel.FaultException异常。由于异常类型和消息固定不变,对于服务的客户端来说,直接通过捕获到的异常相关的信息是无法确定服务端在执行服务操作的时候遇到的具体的错误是什么。

  WCF如此设计的一个主要的目的为了安全。原因很简单,由于我们不能保证服务端直接抛出的异常不包含任何敏感信息,所以直接将服务端原始的异常信息暴露给客户端(对于服务提供者来说,该客户端可能使一个不受信任或者部完全受信任的第三方)。

  二、 异常细节的传输

  通过上面的介绍,我们已经意识到了:在默认的情况下,如果异常(主要指应用异常)在执行服务操作的过程中抛出,其真正的异常信息并不能被客户端捕获。实际上,服务端具体的异常细节信息仅限于服务端可见,并不会传递到客户端。

  然后,不论对于开发阶段的调试,还是维护阶段的纠错、排错,如果在客户端调用某个服务操作后能够很直接地获取到从服务端抛出异常的所有细节,这无疑是一件很有价值的事情。那么,WCF能够做到这一点呢?答案是肯定的。

  实际上,对于细心的读者,看到客户端捕获的FaultException异常的消息,就能从中找到解决方案。消息中指出,如果试图得到服务端具体的错误信息,需要开启IncludeExceptionDetailInFaults这么一个开关。具体来讲,又具有两种等效的方式:配置的方式和应用自定义特性(Custom Attribute)的方式。

  通过在服务端的配置中,为寄宿的服务定义相应的服务行为(Service Behavior),并把serviceDebug配置项的includeExceptionDetailInFaults属性设为True。具体配置如下所示:

1: xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <system.serviceModel> 4: <behaviors> 5: <serviceBehaviors> 6: <behavior name="serviceDebuBehavior"> 7: <serviceDebug includeExceptionDetailInFaults="true" /> 8: behavior> 9: serviceBehaviors> 10: behaviors> 11: <services> 12: <service behaviorConfiguration="serviceDebuBehavior" name="Artech.WcfServices.Services.CalculatorService"> 13: <endpoint address="http://127.0.0.1:3721/calculatorservice" binding="wsHttpBinding" contract="Artech.WcfServices.Contracts.ICalculator" /> 14: service> 15: services> 16: system.serviceModel> 17: configuration>

  大部分系统自定义服务行为都可以直接通过在服务类型上应用System.ServiceModel.ServiceBehaviorAttribute这么一个自定义特性一样,includeExceptionDetailInFaults服务调试(ServiceDebug)行为也不另外。在ServiceBehaviorAttribute中定义了一个IncludeExceptionDetailInFaults属性,当我们将ServiceBehaviorAttribute特性应用到具体的服务类型上的时候,只需将此属性设为true即可。

1: [AttributeUsage(AttributeTargets.Class)] 2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior 3: { 4: //其他成员 5: public bool IncludeExceptionDetailInFaults { get; set; } 6: }

  所以如果不采用上面的配置,在服务类型CalculatorService上面应用ServiceBehaviorAttribute特性,并进行如下的设置,也可以到达相同的效果。

1: using Artech.WcfServices.Contracts; 2: using System.ServiceModel; 3: namespace Artech.WcfServices.Services 4: { 5: [ServiceBehavior(IncludeExceptionDetailInFaults = true)] 6: public class CalculatorService : ICalculator 7: { 8: //省略服务成员 9: } 10: }

  当IncludeExceptionDetailInFaults被开启的ServiceDebug服务属性通过上述两种方式应用到我们例子中的服务CalculatorService的情况下,运行客户端应用程序,将会捕获包含有错误明细信息的异常,运行的结果如图3所示:

图3 客户端捕获到具有明细信息的异常

  从图3中,我们可以看出客户端捕获到的实际上是一个泛型的System.ServiceModel.FaultException异常。FaultException继承自FaultException,这两种典型的异常类型在WCF异常处理中具有重要的地位,在本章后续章节中还会重点讲述,在这里先做一点简单的介绍。

  对于所有从服务端抛出的异常,只有FaultException和直接或间接继承自FaultException的异常才能被序列化,并最终通过消息返回给服务的调用端。FaultException可以通过文本的形式保存相应的错误信息。FaultException在FaultException现有的基础上,增加了一个额外的特性:将错误信息通过一个具体的对象表示,其类型便是范型类型TDetail,该对象可以通过属性Detail设置或者获取。

1: [Serializable] 2: public class FaultException : FaultException 3: { 4: // 其他成员 5: public FaultException(TDetail detail); 6: public TDetail Detail { get; } 7: }

  对于上面例子对应的场景,客户端捕获的异常类型实际上是FaultException< System.ServiceModel.ExceptionDetail>,也就是说其具体的泛型类型参数为System.ServiceModel.ExceptionDetail。ExceptionDetail的定义如下:

1: [DataContract] 2: public class ExceptionDetail 3: { 4: // 其他成员 5: public ExceptionDetail(Exception exception); 6:  7: [DataMember] 8: public string HelpLink { get; private

时间: 2024-08-04 20:38:52

WCF基本异常处理模式[上篇]的相关文章

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

通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的错误信息仅仅限于服务端可见,并不会被WCF传递到客户端:如果将开启了IncludeExceptionDetailInFaults的ServiceDebug服务行为通过声明(通过在服务类型上应用ServiceBehaviorAttrite特性)或者配置的方式应用到相应的服务上,异常相关的所有细节信息将会原封不动地向客户端传送. 这两种方式体

WCF基本异常处理模式[中篇]

通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的错误信息仅仅限于服务端可见,并不会被WCF传递到客户端:如果将开启了IncludeExceptionDetailInFaults的ServiceDebug服务行为通过声明(通过在服务类型上应用ServiceBehaviorAttrite特性)或者配置的方式应用到相应的服务上,异常相关的所有细节信息将会原封不动地向客户端传送. 这两种方式体

艾伟_转载:WCF基本异常处理模式[中篇]

通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的错误信息仅仅限于服务端可见,并不会被WCF传递到客户端:如果将开启了IncludeExceptionDetailInFaults的ServiceDebug服务行为通过声明(通过在服务类型上应用ServiceBehaviorAttrite特性)或者配置的方式应用到相应的服务上,异常相关的所有细节信息将会原封不动地向客户端传送. 这两种方式体

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

由于WCF采用.NET托管语言(C#和NET)作为其主要的编程语言,注定以了基于WCF的编程方式不可能很复杂.同时,WCF设计的一个目的就是提供基于非业务逻辑的通信实现,为编程人员提供一套简单易用的应用编程接口(API).WCF编程模式的简单性同样体现在异常处理上面,本篇文章的主要目的就是对WCF基于异常处理的编程模式做一个简单的介绍. 一.当异常从服务端抛出 对于一个典型的WCF服务调用,我个人倾向于将潜在抛出的异常费为两种类型:应用异常(Application Exception)和基础结构

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

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

WCF基本异常处理模式[下篇]

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

艾伟_转载:WCF基本异常处理模式[下篇]

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

来源于WCF的设计模式:可扩展对象模式[上篇]

我一直很喜欢剖析微软一些产品.框架的底层实现.在我看来,这不但让我们可以更加深入地了解其运作的原理,同时也能提高设计/架构的技能.因为对于这些框架或者产品来说,高质量的设计是它们能够成功的一个最基本的因素.比如说比如ASP.NET,不但能够支持传统的Web Form应用,MVC同样建立在它基础之上.比如说WCF,从其诞生的那一天开始,真个架构体系就从未改变.这些应用在这些产品和框架上的设计其实是最值得我们学习的设计案例.比如说,今天我们介绍的"可扩展对象模式(Extensible Object

WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]

WCF客户端和服务端的框架体系相互协作,使得开发人员可以按照我们熟悉的方式进行异常的处理:在服务操作执行过程中抛出异常(FaultException),在调用服务时捕获异常,完全感觉不到"分布式"的存在,如同典型的"本地"操作一般.为了实现这样的效果,WCF在内部为我们作了很多. 消息交换是WCF进行通信的唯一手段,消息不仅仅是正常服务调用请求和回复的载体,服务端抛出的异常,甚至是服务的元数据都是通过消息的形式传向客户端的.所以,实现异常与消息之间的转换是整个异常处