• 【WCF】错误处理(四):一刀切——IErrorHandler


    前面几篇烂文中所介绍到的错误方式,都是在操作协定的实现代码中抛出 FaultException 或者带泛型参数的detail方案,有些时候,错误的处理方法比较相似,可是要每个操作协定去处理,似乎也太麻烦,此时就应当考虑统一处理了。

    在 System.ServiceModel.Dispatcher 命名空间下,有一个 IErrorHandler 接口,这个接口就是让我们统一处理错误的。

    先来认识一下这个接口。

        public interface IErrorHandler
        {
    
            bool HandleError(Exception error);
            
    
            void ProvideFault(Exception error, MessageVersion version, ref Message fault);
    
        }
    

    HandleError 方法是先判断一下,这个错误是否应该让会话中止,通常我们直接返回 true,表示会话不会中止,如果返回false,通信会话会被中止,这会使得服务实例被释放(单个实例模式除外,因为单例模式下如果把实例释放,就导致其他客户端无法调用服务了),从而使数据丢失,所以,还是返回true好一点,表示错误由你来处理。

    ProvideFault 方法就是用来产生 SOAP 错误消息的,可以在这里对异常进行统一封装。

    IErrorHandler 的实现类只能插入到 Channel Dispatcher中,这表明它是跟通道层相关的。最简单的方法就是把实现了 IErrorHandler 接口的类型实例直接通过 ServiceHost 来获取通道调度程序并加入到 ErrorHandlers 集合中。但,为了便于扩展和配置,老周还是建议宁可麻烦一点,扩展一个 behavior 来添加这个 handler。

    按照老周一向的风格,理论和原理部分已经说完了,下面就是上菜,哦不,是上示例时间。

    照旧,我们得先弄个示例服务。下面代码定义了一个计算加法运算和二次方运算的协定。

        [ServiceContract(Namespace = "sp-test", Name = "demo", ConfigurationName = "testContract")]
        public interface ITest
        {
            [OperationContract]
            int Add(int a, int b);
            [OperationContract]
            int SQR(int n);
        }
    

    然后,实现它。

        [ServiceBehavior(ConfigurationName = "sv")]
        class TestSvr : ITest
        {
            public int Add(int a, int b)
            {
                if (a < 0) throw new ArgumentException("值不能小于0", nameof(a));
                if (b < 0) throw new ArgumentException("值不能小于0", nameof(b));
                return a + b;
            }
    
            public int SQR(int n)
            {
                if (n == 0) throw new Exception("参数不能为0");
                return n * n;
            }
        }
    

    在实现代码中,都对传入参数进行验证,并抛出对应的异常,这个相信大伙能看懂。

    接下来才是主角出场。

    来,实现一个自定义的 Error Handler。

        public class CustErrorHandler : IErrorHandler
        {
            public bool HandleError(Exception error)
            {
                return true;
            }
    
            public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
            {
                // 1、封装异常
                string errText = error.Message;
                FaultException fex = new FaultException(errText);
                // 2、产生 MessageFault
                MessageFault msg_fault = fex.CreateMessageFault();
                // 3、产生新的消息
                fault = Message.CreateMessage(version, msg_fault, "cust-fault");
            }
        }
    

    用 FaultException 来封装错误信息是关键一步,如果必要可以用 FaultException<TDetail> ,因为这里没有定义错误协定类,故而用 FaultException 就OK了。

    随后,通过 FaultException ,可以生成一个 MessageFault 对象,最后就是产生新的 Message ,这条消息是要传回客户端的。此处咱们用的是以下CreateMessage 方法。

    public static Message CreateMessage(MessageVersion version, MessageFault fault, string action);
    

    version 参数好办,只直接引用 ProvideFault 方法中传入的值即可,第二个参数就是刚刚产生的 MessageFault 实例,最后还得指定一个 action,这个你可以自己取,反正一个字符串就行。

    下面实现 behavior,这里以实现End Point 层面的 behavior 为例。

        public class CustEndpointBehavior : IEndpointBehavior
        {
            public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
            {
                return;
            }
    
            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                return; //无需处理
            }
    
            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
                ChannelDispatcher cndisp = endpointDispatcher.ChannelDispatcher;
                // 加入自定义的错误处理程序
                CustErrorHandler cehdlr = null;
                cehdlr = (CustErrorHandler)cndisp.ErrorHandlers.FirstOrDefault();
                if (cehdlr == null)
                {
                    cehdlr = new CustErrorHandler();
                }
                cndisp.ErrorHandlers.Add(cehdlr);
            }
    
            public void Validate(ServiceEndpoint endpoint)
            {
                return;
            }
        }
    

    ApplyClientBehavior 方法是客户端上面调用的,此处我们不需要,只要实现 ApplyDispatchBehavior 方法即可,它是用在服务器上的。

    按理说,到这里就完事了,但是,如果用配置文件来配置的话,还是不够的,当然了,用代码来配置是没问题的。

    为了能让这个自定义的behavior能在配置文件中用,需要实现抽象类 BehaviorExtensionElement。

        public class CustBehaviorExtElement : BehaviorExtensionElement
        {
            public override Type BehaviorType => typeof(CustEndpointBehavior);
    
            protected override object CreateBehavior()
            {
                return new CustEndpointBehavior();
            }
        }
    

    这个我不解释了,简单。

    打开配置文件,我们需要在配置文件中注册这个自定义的元素。

      <system.serviceModel>
        <extensions>
          <behaviorExtensions>
            <add name="custerr" type="TestSvrSample.CustBehaviorExtElement, TestSvrSample"/>
          </behaviorExtensions>
        </extensions>
      </system.serviceModel>
    

    注意,type 属性指定的是我们刚刚实现抽象类 BehaviorExtensionElement 的那个类,不仅要写上完整类型路径,还要写上所在程序集的名字,不然会找不到。name 属性用来指定在配置中使用时,其元素的名称。

    比如,可以这样配置。

        <behaviors>
          <endpointBehaviors>
            <behavior name="bhv">
              <custerr />
            </behavior>
          </endpointBehaviors>
        </behaviors>
    

    其中的 custerr 元素就是刚才在 behaviorExtensions 中所指定的 name 属性。

    最后可以在终结点配置中引用这个 behavior。

          <service name="sv">
            <endpoint address="http://127.0.0.1:3655/test" binding="basicHttpBinding" contract="testContract" behaviorConfiguration="bhv"/>
          </service>
    

    好了,大功告成,现在,可以测试一下调用,调用中,我故意传个不合适的参数值。

                        ITest ch = factory.CreateChannel();
                        try
                        {
                            int r = ch.Add(-1, 6);
                            Console.WriteLine(r);
                        }
                        catch(FaultException faultex)
                        {
                            FaultReason reason = faultex.R
    eason; string text = reason.GetMatchingTranslation().Text; Console.WriteLine($"错误:{text}"); }

    运行后,客户端捕捉到的错误信息如下图所示。

    OK,本文到此结束,开饭。

    示例代码下载: http://files.cnblogs.com/files/tcjiaan/IErrorHandlerSample.zip

  • 相关阅读:
    js炫酷效果
    程序员的执着
    [心得]docker学习笔记
    [心得笔记]多线程之间的内存可见性问题
    Docker入门
    [心得体会]jvm
    redis学习总结
    [心得]redis集群环境搭建的错误
    Linux安装mysql5.7版本
    Cent OS下安装JDK11
  • 原文地址:https://www.cnblogs.com/sjqq/p/6918462.html
Copyright © 2020-2023  润新知