Happy weekend.
今天第20集了。这个视频系列里面有6集和异常相关,这集是最后一集。前面几集讲了服务端遇到普通的 .net exception时候,要转换城Soap Fault,用fault Exception 或 FaultException<T>来处理。 上一集的例子中用了在主方法体上加大的try catch块来捕获异常,然后throw成FaultExcepiton,这个有个坏处,我们不可能在所有的方法上都加上这么一段,因为不仅代码上显得臃肿,而且加起来麻烦,到处的try-catch。这集就通过IErrorHandler接口来提供一种相对优雅很多的方法。
在ASP.net 的web程序中,我们可以用Global.asax中的Application_Error()事件来记录异常日志,然后处理掉比如redirect到其他自定义错误页什么的。
WCF中,我们可以用IErrorHandler 接口来实现类似的功能。
总共有3步:
1. 创建一个实现了IErrorHandler 接口的类。
public class GlobalErrorHandler : IErrorHandler { public bool HandleError(Exception error) { //can do something like log or what. //true means this error was handled. return true; } public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault) { if(error is FaultException) return; var faultException = new FaultException("A general service error occured!"); var msgFault = faultException.CreateMessageFault(); fault = Message.CreateMessage(version, msgFault, null); } }
定义一个类,GlobalErrorHandler,实现IErrorHandler接口。 这个接口里面有两个方法,分个介绍:
HandleError: 这个返回一个true or false,表示这个Exception是否已经被处理。通常,我们也可以在里面做些日志什么的。
ProvideFault: 这个用来构造一个我们需要的FaultException,来避免channel的失效。大致意思是如果error已经是FaultException了,就直接return。 否则,通过传进来的参数,构造一个新的Message,然后赋值给这个ref修饰fault,回传回去。
当Exception 发生时,先进入ProvideFault方法,然后直接return 出这个FaultException给客户端,避免客户端的等待。同时,异步调用HandleError方法。这就是为什么log 要写在HandleError里面而不是写在ProvideFault里面的原因。
2. 创建一个ServiceBehaviour 特性类来告诉WCF服务端当发生异常时,我们要使用上一步创建的GlobalErrorHandler 类。
代码如下:
public class GlobalErrorHandlerBehaviourAttribute : Attribute, IServiceBehavior { private readonly Type errorHandlerType; public GlobalErrorHandlerBehaviourAttribute(Type errorHandlerType) { this.errorHandlerType = errorHandlerType; } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { IErrorHandler handler = (IErrorHandler)Activator.CreateInstance(this.errorHandlerType); foreach(ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) { var channelDispatcher = channelDispatcherBase as ChannelDispatcher; if(channelDispatcher != null) channelDispatcher.ErrorHandlers.Add(handler); } } public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { //throw new NotImplementedException(); } public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { //throw new NotImplementedException(); } }
定义了一个GlobalErrorHandlerBehaviourAttribute类,这是一个特性类,同时,实现了IServiceBehavior接口。定一这个类的构造函数,传入一个类型,确切的说是实现了第一步的IErrorHandler接口类型的类的类型。(或许可以把这个类定义成一个泛型类)
msdn看了一下IServiceBehavior 接口,
提供一种在整个服务内修改或插入自定义扩展的机制,包括 ServiceHostBase。
这个接口定义了3个方法,这里需要关注的是ApplyDispatchBehavior方法。首先通过Activator构造出一个我们需要的ErrorHandler实例(确切的说就是那个GlobalErrorHandler),然后获取当前ServiceHostBase里面的所有的ChannelDispatcher,然后给每个ChannelDispatcher的ErrorHandler添加我们自定义的GlobalErrorHandler。
关于这个ChannelDispatcher,msdn上是这么说的
接受通道以及将通道与服务相关联的组件。
然后msdn上这个ChannelDispacher.ErrorHandlers的解释:
获取 IErrorHandler 对象的集合,这些对象可用于插入终结点的自定义错误处理功能。
3. 把我们定义的CalculatorService 用这个GlobalErrorHandlerBehavior修饰。
[GlobalErrorHandlerBehaviour(typeof(GlobalErrorHandler))] public class CalculatorService : ICalculatorService { public double Divide(int numerator, int denominator) { //try { return numerator / denominator; //} catch(DivideByZeroException ex) { // throw new FaultException<DivideByZeroFault>(new DivideByZeroFault() // { // Error = ex.Message, // Details = "Denominator can't be zero" // }); //} catch(Exception ex) { // throw new FaultException(ex.Message, new FaultCode("Unknow Code")); //} } }
同时,我们移除所有这个大的try-catch块,因为他本来就不应该出现这里。如果有需要,可以把这个移到第一步的ProvideFault方法里面,在里面判断出现的Exception是不是DivideByZeroException(common .net Exception,不是FaultException,如果是的话可以抛出一个我们定义的DivideByZeroFault)。
下面来测试一下
host起服务,然后更新一下客户端的服务引用。然后输入除数和被除数:
如图我们得到了A general serice error occurred! 的错误消息,并且,再次输入非0的除数也可以得到正确的结果。
这集就是这样,讲的是IErrorHandler接口的使用。如果是在WCF的实际项目中应该还是比较好用的吧。
Thank you。