WCF 入门(20)

前言

Happy weekend.

第20集 通过实现IErrorHandler接口来统一处理WCF里的异常 Centralized exception handling in WCF by implementing IErrorHandler interface

今天第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起服务,然后更新一下客户端的服务引用。然后输入除数和被除数:

WCF 入门(20)_第1张图片

如图我们得到了A general serice error occurred! 的错误消息,并且,再次输入非0的除数也可以得到正确的结果。

 

这集就是这样,讲的是IErrorHandler接口的使用。如果是在WCF的实际项目中应该还是比较好用的吧。

Thank you。

你可能感兴趣的:(WCF 入门(20))