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

    QQ截图20151107185840

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

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

    Thank you。

  • 相关阅读:
    【9018:2221】[伪模板]可持久化线段树
    【9018:2208】可持久化线段树2
    【9018:2207】可持久化线段树1
    【POJ2187】Beauty Contest
    2017/11/22模拟赛
    2017/11/3模拟赛
    [AtCoder 2702]Fountain Walk
    [AtCoder3856]Ice Rink Game
    20170910模拟赛
    20170906模拟赛
  • 原文地址:https://www.cnblogs.com/sheldon-lou/p/4945891.html
Copyright © 2020-2023  润新知