WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]
原文:WCF技术剖析之二十一: 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<ICalculator> channelFactory = new ChannelFactory<ICalculator>(
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 或从 <serviceDebug> 配置行为)以便将异常信息发送回客户端,或在打开每个 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<TDetail>异常。FaultException<TDetail>继承自FaultException,这两种典型的异常类型在WCF异常处理中具有重要的地位,在本章后续章节中还会重点讲述,在这里先做一点简单的介绍。
对于所有从服务端抛出的异常,只有FaultException和直接或间接继承自FaultException的异常才能被序列化,并最终通过消息返回给服务的调用端。FaultException可以通过文本的形式保存相应的错误信息。FaultException<TDetail>在FaultException现有的基础上,增加了一个额外的特性:将错误信息通过一个具体的对象表示,其类型便是范型类型TDetail,该对象可以通过属性Detail设置或者获取。
1: [Serializable]
2: public class FaultException<TDetail> : 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 set; }
9: [DataMember]
10: public ExceptionDetail InnerException { get; private set; }
11: [DataMember]
12: public string Message { get; private set; }
13: [DataMember]
14: public string StackTrace { get; private set; }
15: [DataMember]
16: public string Type { get; private set; }
17: }
ExceptionDetail是一个数据契约(Data Contract),也就意味ExceptionDetail是一个可以被DataContractSerializer进行序列化的对象。再仔细察看具体的属性成员列表,我想很多读者肯定有一种是曾相识的感觉:是不是和System.Exception的属性成员定义很相似。实际上,ExceptionDetail是WCF专门设计出来用于封装服务端抛出的异常信息的,其个属性HelpLink、InnerException和StackTrace各自和System.Exception的同名属性向对应,而属性Type表示异常的类型。
也就是说,对于应用了开启IncludeExceptionDetailInFaults的ServiceDebug服务行为的WCF服务,在执行服务操作抛出的异常信息,可以通过包含在客户端捕获的FaultException<ExceptionDetail>异常中的ExceptionDetail对象获取。比如,在下面的代码中,我修改了客户端的代码,将具体的错误信息输出到控制台上:
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<ICalculator> channelFactory = new ChannelFactory<ICalculator>(
11: "calculatorservice"))
12: {
13: ICalculator calculator = channelFactory.CreateChannel();
14: using (calculator as IDisposable)
15: {
16: try
17: {
18: int result = calculator.Divide(1, 0);
19: }
20: catch (FaultException<ExceptionDetail> ex)
21: {
22: Console.WriteLine("Message:{0}", ex.Detail.Message);
23: Console.WriteLine("Type:{0}", ex.Detail.Type);
24: Console.WriteLine("StackTrace:{0}", ex.Detail.StackTrace);
25: Console.WriteLine("HelpLink:{0}", ex.Detail.HelpLink);
26: (calculator as ICommunicationObject).Abort();
27: }
28: }
29: }
30: }
31: }
32: }
输出结果:
1: Message:试图除以零。
2: Type:System.DivideByZeroException
3: StackTrace: 在 Artech.WcfServices.Services.CalculatorService.Divide(Int32 x, Int32 y) 位置 D:\Demos\Artech.WcfServices\Services\CalculatorService.cs:行号 13
4: 在 SyncInvokeDivide(Object , Object[] , Object[] )
5: 在 System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&; outputs)
6: 在System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&; rpc)
7: 在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&; rpc)
8: 在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&; rpc)
9: 在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&; rpc)
10: 在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&; rpc)
11: 在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&; rpc)
12: 在 System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
13: HelpLink:
注:在catch程序块中,我们通过代码((calculator as ICommunicationObject).Abort();)将会话信道强行中断。原因在于,对于基于会话信道(Sessionful Channel)的服务调用,服务端抛出的异常会将该信道的状态转变为出错状态(Faulted),处于Faulted状态的会话信道将不能再用于后续的通信,即使你调用Close方法将其关闭。在这种情况下,需要调用Abort方法对其进行强行中止。具体的原理,在《WCF技术剖析(卷1)》的第9章有详细的介绍。
对于服务行为SerivceDebug的IncludeExceptionDetailInFaults属性,我需要再次重申一遍:由于会导致敏感信息泄露的潜在危险,一般地我们仅仅在调试的时候才会开启该属性。对于已经发布、付诸使用的服务,这个开关一般是关闭的。实际上,我们从这个服务行为的命名也可以看出,SerivceDebug,也是用于调试服务的服务行为罢了。
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
WCF技术剖析之二十一: WCF基本的异常处理模式[上篇]的更多相关文章
- WCF技术剖析之二十一:WCF基本异常处理模式[下篇]
原文:WCF技术剖析之二十一:WCF基本异常处理模式[下篇] 从FaultContractAttribute的定义我们可以看出,该特性可以在同一个目标对象上面多次应用(AllowMultiple = ...
- WCF技术剖析之二十一:WCF基本异常处理模式[中篇]
原文:WCF技术剖析之二十一:WCF基本异常处理模式[中篇] 通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultExcept ...
- WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载]
原文:WCF技术剖析之二十九:换种不同的方式调用WCF服务[提供源代码下载] 我们有两种典型的WCF调用方式:通过SvcUtil.exe(或者添加Web引用)导入发布的服务元数据生成服务代理相关的代码 ...
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序)
原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于HTTP-GET的实现](提供模拟程序) 基于HTTP-GET的元数据发布方式与基于WS-MEX原理类似,但是ServiceMetad ...
- WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]
原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ...
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序)
原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[基于WS-MEX的实现](提供模拟程序) 通过<如何将一个服务发布成WSDL[编程篇]>的介绍我们知道了如何可以通过编程或者配 ...
- WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇]
原文:WCF技术剖析之二十七: 如何将一个服务发布成WSDL[编程篇] 对于WCF服务端元数据架构体系来说,通过MetadataExporter将服务的终结点导出成MetadataSet(参考< ...
- WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[扩展篇]
原文:WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[扩展篇] 通过<实现篇>对WSDL元素和终结点三要素的之间的匹配关系的介绍,我们知道了WSDL的Binding ...
- WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇]
原文:WCF技术剖析之二十六:如何导出WCF服务的元数据(Metadata)[实现篇] 元数据的导出就是实现从ServiceEndpoint对象向MetadataSet对象转换的过程,在WCF元数据框 ...
随机推荐
- clear伪类使用
都知道float会脱离文档流 用什么办法撑开父元素呢? 手动在本区块的所有float元素之后加上一个块元素并对其添加clear:both 可以 但是这样还要再去修改html页面 而且多了一个仅仅是 ...
- float与position
使用float会使块级元素的宽高表现为包裹内容(在不设定宽高的情况下) 这是当然的 我们使用float就是使几个div排在一行 当然不可能在宽度上撑满父元素啦 至于高度 不论有没有float 高 ...
- js 几个特殊情况
alert(033-15);//12,前缀0用在直接量中,表示八进制 alert('033'-15);//18,前缀0用在字符串中,在(隐式)转换将忽略 alert(parseInt('033')-1 ...
- Week15(12月16日):授课综述1
Part I:提问 =========================== 1.( )类提供了一个对Entity Framework的抽象,能够进行数据持久化并接受数据. A.Layout ...
- hibernate Criteria查询 2.3
Criteria对象提供了一种面向对象的方式查询数据库.Criteria对象需要使用Session对象来获得一个Criteria对象表示对一个持久化类的查询 查询所有 Session session ...
- Xamarin.Android开发实践(三)
原文:Xamarin.Android开发实践(三) 一.前言 用过Android手机的人一定会发现一种现象,当你把一个应用置于后台后,一段时间之后在打开就会发现应用重新打开了,但是之前的相关的数据却没 ...
- 55. 略谈Lotus Notes的与众不同及系列文章至此的总结
在二十多年的悠久历史里,Lotus Notes发展出一整套独特的概念.技术和思维.由于它早期惊人的领先时代和后续发展中同样惊人的忠于传统,这位软件领域的寿星在如今发展更新速度远超往日和技术愈趋公开互通 ...
- apache2.4.x三种MPM介绍
三种MPM介绍 Apache 2.X 支持 ...
- 树莓派常用Linux命令
转自小五义 1.ls命令:列出文件目录的常用命令,主要参数见下表. -a 列出目录下的所有文件,包括以.开头的隐含文件. -b 把文件名中不可输出的字符用反斜杠加字符编号(就象在C语言里一样)的形式列 ...
- 我的Python成长之路---第三天---Python基础(12)---2016年1月16日(雾霾)
四.函数 日常生活中,要完成一件复杂的功能,我们总是习惯把“大功能”分解为多个“小功能”以实现.在编程的世界里,“功能”可称呼为“函数”,因此“函数”其实就是一段实现了某种功能的代码,并且可以供其它代 ...