1:WCF中的异常处理分析
WCF中的异常继承层次结构,如下图:
System.Exception
System.SystemException
System.ServiceModel.CommunicationException
System.ServiceModel.FaultException
System.ServiceModel.FaultException<TDetail>
System.ServiceModel.Web.WebFaultException
在整个WCF体系下,数据存在的形态大体可以分为两种:XML和托管对象(Managed Object)。WCF建立在.NET平台下,利用托管语言(C#和VB.NET)开发人员提供了一个面向对象的编程模型,所以,在WCF体系最顶层的数据形态表现为.NET托管对象。而最终服务调用体现在消息的交换上,消息时基于XML的(除了少部分非XML的消息,比如JSON)。从数据转化的角度上讲,WCF起到了一个将数据从这两种形态数据进行转化和适配的作用。
在WCF异常处理体系中,对于异常或者错误,在XML的世界里最终通过soap Fault消息体现;而在托管对象的世界中,即使相应的Exception对象。
WCF包括三种常见类型的异常:
1) 通讯异常,这通常是因为链路的原因,比如服务没有启动,网络阻塞等。这类异常是CommunicationException或者其派生类
2) 状态异常,这类异常通常是与上文提到的实例模式相关的,当访问了一个已经销毁的服务器对象时便会引发此类型的异常,它们通常是ObjectDisposedException
3) 服务异常,由服务端根据具体的业务逻辑触发,通常是FaultException 值得注意的是当抛出服务异常的时候,不同的实例模式的处理方式有所不同:
PerSession:这种模式下,抛出异常,服务实例将销毁,客户端抛出FaultException,客户端代理对象无法继续使用
PerCall:这种模式下,抛出异常,服务实例也将销毁。客户端代理对象无法继续使用
Single:这种模式下,抛出异常,服务实例会照旧运行。客户端代理无法继续使用
所以:WCF的异常处理框架的核心功能就是实现FaultException异常和Fault消息之间的转换
可以这样来简单地描述WCF异常处理框架的功能实现:WCF服务端将抛出的FaultException异常进行序列化,并根绝消息的SOAP规范(SOAP 1.1或SOAP 1.2)和WS-Addressing规范(WS-Addressing 2004和WS-Addressing 1.0)生成Fault消息。被传入信道层,经过一系列的信道后,该Fault消息最终借助于传输层返回到客户端;客户端信道层接收到该Fault消息并经过相应的处理后,被反序列化。反序列化的结果即实现对FaultException的重建,WCF最终将重建的FaultException异常抛出,对于最终的开发者而言,感觉就像服务端抛出的FaultException直接被客户端捕获了一样。在上面的内容中我们说过:WCF并不直接进行FaultException和Fault消息之间的转换,而是借助于MessageFault这一中间对象
2:错误契约
以服务端的一个服务方法(被除数为零):
public int devide(int a, int b)
{
return a / b;
}
测试WCF异常处理机制
1》如果IncludeExceptionDetailInFaults为关闭状态,而且服务端对服务异常没有做任何的处理,那么客户端只能看到:“
System.ServiceModel.FaultException: 由于内部错误,服务器无法处理该请求。有关该错误的详细信息,请打开服务器上的 IncludeExceptionDetailInFaults (从 ServiceBehaviorAttribute 或从 <serviceDebug> 配置行为)以便将异常信息发送回客户端,或在打开每个 Microsoft .NET Framework 3.0 SDK 文档的跟踪的同时检查服务器跟踪日志。
Server stack trace:
在 System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)
在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc)
在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
在 Contact.Calc.devide(Int32 a, Int32 b)
在 Client.Program.Main(String[] args) 位置 D:\WCF_study\Contact\Client\Program.cs:行号 20}
。”;
2》如果IncludeExceptionDetailInFaults为开启状态(通常开发状态才可以开启),而且服务端对服务异常没有做任何的处理,那么客户端可以看到:“System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: 尝试
除以零。 (错误详细信息等于 很可能由 IncludeExceptionDetailInFaults=true 创建的 E
xceptionDetail,其值为:
System.DivideByZeroException: 尝试除以零。
在 Service.CalcService.devide(Int32 a, Int32 b) 位置 D:\WCF_study\Contact\Ser
vice\CalcService.cs:行号 18
在 SyncInvokedevide(Object , Object[] , Object[] )
在 System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, O
bject[] inputs, Object[]& outputs)
在 System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(Messag
eRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(Me
ssageRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(M
essageRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(Me
ssageRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(M
essageRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(Me
ssageRpc& rpc)
在 System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(..
.)。”
3》如果IncludeExceptionDetailInFaults为关闭状态,而且服务端[FaultContract(typeof(DivideByZeroException))],服务方法针对除数为零抛出了throw new FaultException<DivideByZeroException>(new DivideByZeroException(), "除数为零"),那么客户端可以看到:“System.ServiceModel.FaultException`1[System.DivideByZeroException]: 除数为零 (错
误详细信息等于 System.DivideByZeroException: 尝试除以零。)。”
4》如果IncludeExceptionDetailInFaults为关闭状态,而且服务端去掉[FaultContract(typeof(DivideByZeroException))],服务方法针对除数为零抛出了throw new FaultException<DivideByZeroException>(new DivideByZeroException(), "除数为零"),那么客户端可以看到:”
System.ServiceModel.FaultException: 除数为零
Server stack trace:
在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRunt
ime operation, ProxyRpc& rpc)
在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean on
eway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan tim
eout)
在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCall
Message methodCall, ProxyOperationRuntime operation)
在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]:
在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req
Msg, IMessage retMsg)
在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa
ta, Int32 type)
在 Contact.Calc.devide(Int32 a, Int32 b)
在 Client.Program.Main(String[] args) 位置 D:\WCF_study\Contact\Client\Progra
m.cs:行号 20
“
3:通过错误处理扩展来异常提升和记录日志
WCF允许开发者定制默认的异常报告和异常传递,甚至为定制日志提供了一个钩子.需要实现IErroHandler接口,实现HandleError和ProvideFault方法。
ProvideFault方法:如果出现应用异常,返回客户端被阻止,立即调用次方法,可以在此方法进行异常提升,不能在此方法中做些耗时的操作
HandleError方法:不会阻止返回客户端,调用次方法的是后台使用的一个单独线程,而不是用来处理服务请求的线程,可以在此方法中做一些比如记录日志到数据库中和错误跟踪的耗时操作。
什么异常提升:首先它是一种解藕技巧。一般服务可以使用下游对象,这些对象也可以被各种服务调用,所以考虑到系统的松散耦合,服务的下游对象不应该依赖于调用它们的服务的特定错误契约,当下游对象出现错误时,只需抛出常规的CLR异常。服务要做的就是使用错误处理扩展检查抛出的异常,如果异常属于faultException<T>中的T类型,同时又属于错误契约操作的一部分,那么服务就能够将该异常提升为完整的faultException类型。