无需写try/catch,也能正常处理异常

对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情。说其简单,是因为相关的编程无外乎try/catch/finally+throw而已;说其复杂,是因为我们往往很难按照我们真正需要的策略来处理异常。我一直有这样的想法,理想的企业应用开发中应该尽量让框架来完成对异常的处理,最终的开发人员在大部分的情况下无需编写异常处理相关的任何代码。在这篇文章中我们将提供一个解决方案来让ASP.NET应用利用EntLib的异常处理模块来实现自动化的异常处理。

源代码:
Sample1[通过重写Page的OnLoad和OnRaisePostBackEvent方法]
Sample2[通过自动封装注册的EventHandler]

一、EntLib的异常处理方式
二、实例演示
三、通过重写Page的OnLoad和RaisePostBackEvent方法实现自动异常处理
四、IPostBackDataHandler
五、EventHandlerWraper
六、对控件注册事件的自动封装
七、AlertHandler

一、EntLib的异常处理方式

所谓异常,其本意就是超出预期的错误。既然如此,异常处理的策略就不可能一成不变,我们不可能在开发阶段就制定一个完备的异常处理策略来处理未来发生的所有异常。异常处理策略应该是可配置的,能够随时进行动态改变的。就此而言,微软的企业库(以下简称EntLib)的异常处理应用块(Exception
Handling Application Block)是一个不错的异常处理框架,它运行我们通过配置文件来定义针对具体异常类型的处理策略。

针对EntLib的异常处理应用块采用非常简单的编程方式,我们只需要按照如下的方式捕捉抛出的异常,并通过调用ExceptionPolicy的HandleException根据指定的异常策略进行处理即可。对于ASP.NET应用来说,我们可以注册HttpApplication的Error事件的形式来进行统一的异常处理。但是在很多情况下,我们往往需要将异常控制在当前页面之内(比如当前页面被正常呈现,并通过执行一段JavaScript探出一个对话框显示错误消息),我们往往需要将下面这段相同的代码结构置于所有控件的注册事件之中。

   1: try
   2: {
   3:     //业务代码
   4: }
   5: catch(Exception ex)
   6: {
   7:     if(ExceptionPolicy.HandleException(ex,"exceptionPolcyName"))
   8:     {
   9:         throw;
  10:     }
  11: }

我个人不太能够容忍完全相同的代码到处出现,代码应该尽可能地重用,而不是重复。接下来我们就来讨论如何采用一些编程上的手段或者技巧来让开发人员无须编写任何的异常处理代码,而抛出的确却能按照我们预先指定的策略被处理。

二、实例演示

为了让读者对“自动化异常处理”有一个直观的认识,我们来做一个简单的实例演示。我们的异常处理策略很简单:如果后台代码抛出异常,异常的相关信息按照预定义的格式通过Alert的方式显示在当前页面中。如下所示的是异常处理策略在配置文件中的定义,该配置中定义了唯一个名为“default”的异常策略,该策略利用自定义的AlertHandler来显示异常信息。配置属性messageTemplate定义了一个模板用于控制显示消息的格式。

   1: <configuration>
   2:   ...
   3:   <exceptionHandling>
   4:     <exceptionPolicies>
   5:       <add name="default">
   6:         <exceptionTypes>
   7:           <add type="System.Exception, mscorlib" 
   8:                   postHandlingAction="None" name="Exception">
   9:             <exceptionHandlers>
  10:               <add name="Alert Handler" type="AutomaticExceptionHandling.AlertHandler, AutomaticExceptionHandling" 
  11:                    messageTemplate="[{ExceptionType}]{Message}"/>
  12:             </exceptionHandlers>
  13:           </add>          
  14:         </exceptionTypes>
  15:       </add>
  16:     </exceptionPolicies>
  17:   </exceptionHandling>     
  18: </configuration>

现在我们定义一个简单的页面来模式自动化异常处理,这个页面是一个用于进行除法预算的计算器。如下所示的该页面的后台代码,可以看出它没有直接继承自Page,而是继承自我们自定义的基类PageBase,所有异常处理的机制就实现在此。Page_Load方法收集以QueryString方式提供的操作数,并转化成整数进行除法预算,最后将运算结果显示在表示结果的文本框中。计算按钮的Click事件处理方法根据用户输入的操作数进行除法运算。两个方法中均没有一句与异常处理相关的代码。

   1: public partial class Default : 
   2: {
   3:     protected void Page_Load(object sender, EventArgs e)
   4:     {
   5:         if (!this.IsPostBack)
   6:         {
   7:             string op1 = Request.QueryString["op1"];
   8:             string op2 = Request.QueryString["op2"];
   9:             if (!string.IsNullOrEmpty(op1) && !string.IsNullOrEmpty(op2))
  10:             {
  11:                 this.txtResult.Text = (int.Parse(op1) / int.Parse(op2)).ToString();
  12:             }
  13:         }
  14:     }
  15:  
  16:     protected void btnCal_Click(object sender, EventArgs e)
  17:     {
  18:         int op1 = int.Parse(this.txtOp1.Text);
  19:         int op2 = int.Parse(this.txtOp2.Text);
  20:         this.txtResult.Text = (op1 / op2).ToString();
  21:     }
  22: }

现在运行我们程序,可以想象如果在表示操作数的文本框中输入一个非整数字符,调用Int32的Parse方法时将会抛出一个FormatException异常,或者将被除数设置为0,则会抛出一个DivideByZeroException异常。如下面的代码片断所示,在这两种情况下相应的错误信息按照我们预定义的格式以Alert的形式显示出来。

三、通过重写Page的OnLoad和RaisePostBackEvent方法实现自动异常处理

我们知道ASP.NET应用中某个页面的后台代码基本上都是注册到页面及其控件的事件处理方法,除了第一次呈现页面的Load事件,其他事件均是通过PostBack的方式出发的。所以我最初的解决方案很直接:就是提供一个PageBase,在重写的OnLoad和RaisePostBackEvent方法中进行异常处理。PageBase的整个定义如下所示:

   1: public abstract class PageBase: Page
   2: {
   3:     public virtual string ExceptionPolicyName { get; set; }
   4:     public PageBase()
   5:     {
   6:         this.ExceptionPolicyName = "default";
   7:     }
   8:  
   9:     protected virtual string GetExceptionPolicyName()
  10:     {
  11:         ExceptionPolicyAttribute attribute = this.GetType().GetCustomAttributes(true)
  12:             .OfType<ExceptionPolicyAttribute>().FirstOrDefault();
  13:         if (null != attribute)
  14:         {
  15:             return attribute.ExceptionPolicyName;
  16:         }
  17:         else
  18:         {
  19:             return this.ExceptionPolicyName;
  20:         }
  21:     }
  22:  
  23:     protected override void OnLoad(EventArgs e)
  24:     {
  25:         this.InvokeAndHandleException(() => base.OnLoad(e));
  26:     }
  27:  
  28:     protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
  29:     {
  30:         this.InvokeAndHandleException(()=>base.RaisePostBackEvent(sourceControl, eventArgument));
  31:     }
  32:  
  33:     private void InvokeAndHandleException(Action action)
  34:     {
  35:         try
  36:         {
  37:             action();
  38:         }
  39:         catch (Exception ex)
  40:         {
  41:             string exceptionPolicyName = this.GetExceptionPolicyName();
  42:             if (ExceptionPolicy.HandleException(ex, exceptionPolicyName))
  43:             {
  44:                 throw;
  45:             }
  46:         }
  47:     }
  48: }

如上面的代码片断所示,在重写的OnLoad和RaisePostBackEvent方法中,我们采用与EntLib异常处理应用块的编程方式调用基类的同名方法。我们通过属性ExceptionPolicyName

指定了一个默认的异常处理策略名称(“default”,也正是配置文件中定义个策略名称)。如果某个页面需要采用其他的异常处理策略,可以在类型上面应用ExceptionPolicyAttribute特性来制定,该特性定义如下:

   1: [AttributeUsage( AttributeTargets.Class, AllowMultiple = false)]
   2: public class ExceptionPolicyAttribute: Attribute
   3: {
   4:     public string ExceptionPolicyName { get; private set; }
   5:     public ExceptionPolicyAttribute(string exceptionPolicyName)
   6:     {
   7:         Guard.ArgumentNotNullOrEmpty(exceptionPolicyName, "exceptionPolicyName");
   8:         this.ExceptionPolicyName = exceptionPolicyName;
   9:     }
  10: }

四、IPostBackDataHandler

通过为具体Page定义基类并重写OnLoad和RaisePostBackEvent方法的方式貌似能够实现我们“自动化异常处理”的目标,而且针对我们提供的这个实例来说也是OK的。但是这却不是正确的解决方案,原因在于并非所有控件的事件都是在RaisePostBackEvent方法执行过程中触发的。ASP.NET提供了一组实现了IPostBackDataHandler接口的控件类型,它们会向PostBack的时候向服务端传递相应的数据,我们熟悉的ListControl(DropDownList、ListBox、RadioButtonList和CheckBoxList等)就属于此类。

   1: public interface IPostBackDataHandler
   2: {
   3:     bool LoadPostData(string postDataKey, NameValueCollection postCollection);
   4:     void RaisePostDataChangedEvent();
   5: }

当Page的ProcessRequest(这是对IHttpHandler方法的实现)被执行的的时候,会先于RaisePostBackEvent之前调用另一个方法RaiseChangedEvents。在RaiseChangedEvents方法执行过程中,如果目标类型实现了IPostBackDataHandler接口,会调用它们的RaisePostDataChangedEvent方法。很多表示输入数据改变的事件(比如ListControl的SelectedIndexChanged事件)就是被RaisePostDataChangedEvent方法触发的。如果可能,我们可以通过重写RaiseChangedEvents方法的方式来解决这个问题,不过很可惜,这个方法是一个内部方法。

五、EventHandlerWraper

要实现“自动化异常处理”的根本手段就是将页面和控件注册的事件处理方法置于一个try/catch块中执行,并采用EntLib的异常处理应用块的方式对抛出的异常进行处理。如果我们能够改变页面和控件注册的事件,使注册的事件处理器本身就具有异常处理的能力,我们“自动化异常处理”的目标也能够实现。为此我定义了如下一个用于封装EventHandler的EventHandlerWrapper,它将EventHandler的置于一个try/catch块中执行。对于EventHandlerWrapper的设计思想,在我两年前写的《如何编写没有Try/Catch的程序》一文中具有详细介绍。

   1: public class EventHandlerWrapper
   2: {
   3:     public object Target { get; private set; }
   4:     public MethodInfo Method { get; private set; }
   5:     public EventHandler Hander { get; private set; }
   6:     public string ExceptionPolicyName { get; private set; }
   7:  
   8:     public EventHandlerWrapper(EventHandler eventHandler, string exceptionPolicyName)
   9:     {
  10:         Guard.ArgumentNotNull(eventHandler, "eventHandler");
  11:         Guard.ArgumentNotNullOrEmpty(exceptionPolicyName, "exceptionPolicyName");
  12:  
  13:         this.Target = eventHandler.Target;
  14:         this.Method = eventHandler.Method;
  15:         this.ExceptionPolicyName = exceptionPolicyName;
  16:         this.Hander += Invoke;
  17:     }
  18:     public static implicit operator EventHandler(EventHandlerWrapper eventHandlerWrapper)
  19:     {
  20:         Guard.ArgumentNotNull(eventHandlerWrapper, "eventHandlerWrapper");
  21:         return eventHandlerWrapper.Hander;
  22:     }
  23:     private void Invoke(object sender, EventArgs args)
  24:     {
  25:         try
  26:         {
  27:             this.Method.Invoke(this.Target, new object[] { sender, args });
  28:         }
  29:         catch (TargetInvocationException ex)
  30:         {
  31:             if (ExceptionPolicy.HandleException(ex.InnerException, this.ExceptionPolicyName))
  32:             {
  33:                 throw;
  34:             }
  35:         }
  36:     }
  37: }

由于我们为EventHandlerWrapper定义了一个针对EventHandler的隐式转化符,一个EventHandlerWrapper对象能够自动被转化成EventHandler对象。我们现在的目标就是:将包括页面在内的所有控件注册的EventHandler替换成用于封装它们的EventHandlerWrapper。我们知道所有控件的基类Control具有如下一个受保护的只读属性Events,所有注册的EventHandler就包含在这里,而我们的目标就是要改变所有控件该属性中保存的EventHandler。

   1: public class Control
   2: {
   3:     protected EventHandlerList Events{get;}
   4: }

其实要改变Events属性中的EventHandler也并不是一件容易的事,因为其类型EventHandlerList
并不如它的名称表现出来的那样是一个可枚举的列表,而是一个通过私有类型ListEntry维护的链表。要改变这些注册的事件,我们不得不采用反射,而这会影响性能。不过对应并非访问量不高的企业应用来说,我觉得这点性能损失是可以接受的。整个操作被定义在如下所示的EventHandlerWrapperUtil的Wrap方法中。

   1: private static class EventHandlerWrapperUtil
   2: {
   3:     private static Type listEntryType;
   4:     private static FieldInfo handler;
   5:     private static FieldInfo key;
   6:     private static FieldInfo next;
   7:  
   8:     static EventHandlerWrapperUtil()
   9:     {
  10:         listEntryType = Type.GetType("System.ComponentModel.EventHandlerList+ListEntry, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
  11:         BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
  12:         handler = listEntryType.GetField("handler", bindingFlags);
  13:         key     = listEntryType.GetField("key", bindingFlags);
  14:         next    = listEntryType.GetField("next", bindingFlags);
  15:     }
  16:  
  17:     public static void Wrap(object listEntry, string exceptionPolicyName)
  18:     {
  19:         EventHandler eventHandler = handler.GetValue(listEntry) as EventHandler;
  20:         if (null != eventHandler)
  21:         {
  22:             EventHandlerWrapper eventHandlerWrapper = new EventHandlerWrapper(eventHandler, exceptionPolicyName);
  23:             handler.SetValue(listEntry, (EventHandler)eventHandlerWrapper);
  24:         }
  25:             object nextEntry = next.GetValue(listEntry);
  26:         if(null != nextEntry)
  27:         {
  28:             Wrap(nextEntry,exceptionPolicyName);
  29:         }
  30:     }            
  31: }

六、对控件注册事件的自动封装

对包括页面在内的所有控件注册时间的自动封装同样实现在作为具体页面积累的PageBase中。具体的实现定义在WrapEventHandlers方法中,由于Control的Events属性是受保护的,所以我们还得采用反射。该方法最终的重写的OnInit方法中执行。此外,由于EventHandlerWraper仅仅能够封装EventHandler,但是很多控件的事件却并非EventHandler类型,所以这是一个挺难解决的问题。

   1: public abstract class PageBase : Page
   2: {
   3:     private static PropertyInfo eventsProperty;
   4:     private static FieldInfo headField;
   5:  
   6:     public static string ExceptionPolicyName { get; set; }
   7:     static PageBase()
   8:     {
   9:         ExceptionPolicyName = "default";
  10:         eventsProperty = typeof(Control).GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
  11:         headField = typeof(EventHandlerList).GetField("head", BindingFlags.Instance | BindingFlags.NonPublic);
  12:     }
  13:  
  14:     protected override void OnInit(EventArgs e)
  15:     {
  16:         base.OnInit(e);
  17:         Trace.Write("Begin to wrap events!");
  18:         this.WrapEventHandlers(this);
  19:         Trace.Write("Wrapping events ends!");
  20:     }
  21:  
  22:     protected virtual void WrapEventHandlers(Control control)
  23:     {
  24:         string exceptionPolicyName = this.GetExceptionPolicyName();
  25:         EventHandlerList events = eventsProperty.GetValue(control, null) as EventHandlerList;
  26:         if (null != events)
  27:         {
  28:             object head = headField.GetValue(events);
  29:             if (null != head)
  30:             {
  31:                 EventHandlerWrapperUtil.Wrap(head, exceptionPolicyName);
  32:             }
  33:         }
  34:         foreach (Control subControl in control.Controls)
  35:         {
  36:             WrapEventHandlers(subControl);
  37:         }
  38:     }
  39:  
  40:     protected virtual string GetExceptionPolicyName()
  41:     {
  42:         ExceptionPolicyAttribute attribute = this.GetType().GetCustomAttributes(true)
  43:             .OfType<ExceptionPolicyAttribute>().FirstOrDefault();
  44:         if (null != attribute)
  45:         {
  46:             return attribute.ExceptionPolicyName;
  47:         }
  48:         else
  49:         {
  50:             return ExceptionPolicyName;
  51:         }
  52:     }
  53: }

七、AlertHandler

我想有人对用于显示错误消息对话框的AltertHandler的实现很感兴趣,下面给出了它和对应的AlertHandlerData的定义。从如下的代码可以看出,AltertHandler仅仅是调用Page的RaisePostBackEvent方法注册了一段显示错误消息的JavaScript脚本而已。

   1: [ConfigurationElementType(typeof(AlertHandlerData))]
   2: public class AlertHandler: IExceptionHandler
   3: {
   4:     public string MessageTemplate { get; private set; }
   5:     public AlertHandler(string messageTemplate)
   6:     {
   7:         this.MessageTemplate = messageTemplate;
   8:     }
   9:  
  10:     protected string FormatMessage(Exception exception)
  11:     {
  12:         Guard.ArgumentNotNull(exception, "exception");
  13:         string messageTemplate = string.IsNullOrEmpty(this.MessageTemplate) ? exception.Message : this.MessageTemplate;
  14:         return messageTemplate.Replace("{ExceptionType}", exception.GetType().Name)
  15:                                 .Replace("{HelpLink}", exception.HelpLink)
  16:                                 .Replace("{Message}", exception.Message)
  17:                                 .Replace("{Source}", exception.Source)
  18:                                 .Replace("{StackTrace}", exception.StackTrace);
  19:     }
  20:  
  21:     public Exception HandleException(Exception exception, Guid handlingInstanceId)
  22:     {
  23:         Page page = HttpContext.Current.Handler as Page;
  24:         if (null != page)
  25:         {
  26:  
  27:             string message = this.FormatMessage(exception);
  28:             string hiddenControl = "hiddenCurrentPageException";
  29:             page.ClientScript.RegisterHiddenField(hiddenControl, message);
  30:             string script = string.Format("<Script language=\"javascript\">var obj=document.forms[0].{0};alert(unescape(obj.value));</Script>", 
  31:                 new object[] { hiddenControl });
  32:             page.ClientScript.RegisterStartupScript(base.GetType(), "ExceptionHandling.AlertHandler", script);
  33:         }
  34:         return exception;
  35:     }
  36: }
  37:  
  38: public class AlertHandlerData : ExceptionHandlerData
  39: {
  40:     [ConfigurationProperty("messageTemplate", IsRequired = false, DefaultValue="")]
  41:     public string MessageTemplate
  42:     {
  43:         get { return (string)this["messageTemplate"]; }
  44:         set { this["messageTemplate"] = value; }
  45:     }
  46:  
  47:     public override IEnumerable<TypeRegistration> GetRegistrations(string namePrefix)
  48:     {
  49:         yield return new TypeRegistration<IExceptionHandler>(() => new AlertHandler(this.MessageTemplate))
  50:         {
  51:             Name = this.BuildName(namePrefix),
  52:             Lifetime = TypeRegistrationLifetime.Transient
  53:         };
  54:     }
  55: }

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-08-25 14:07:06

无需写try/catch,也能正常处理异常的相关文章

Sqlserver 8.00.2039 版本的存储过程怎么写try catch

问题描述 Sqlserver 8.00.2039 版本的存储过程怎么写try catch begindeclare @G_math ALTER PROCEDURE [dbo].[proc_test]ASbegindeclare @G_math varchar(30)set @G_math='2'_varchar(30)set @G_math='2'return '1'begin trybegin tran select 1commit tran end trybegin catchselect

java代码-我写了一个循环语句, 有异常出现,请问怎么解决?

问题描述 我写了一个循环语句, 有异常出现,请问怎么解决? class SimpleException extends Exception {}public class C {static int i=1; C(){ try{ int j=i++; if( j>6000) throw new SimpleException(); System.out.println(j); }catch(SimpleException e){} finally{ new C(); } }public stati

试写java的反序列化报了一个异常

问题描述 试写java的反序列化报了一个异常 异常信息如下: invalid type code: 11 求高手解释!跪求!趴求!!躺求!!! 解决方案 简单来说就是序列化的类型和你视图反序列化的类型不同,或者你的数据有问题 解决方案二: 异常信息太少了,无法判断,要么上源码,要么将异常信息贴完整.

我想写一个监控程序,如有一个程序a我让它一直开着,,写个程序b,当a出现异常时(有弹出框)把a重启,怎么实现

问题描述 我想写一个监控程序,如有一个程序a我让它一直开着,,写个程序b,当a出现异常时(有弹出框)把a重启,怎么实现大家帮帮忙 解决方案 解决方案二:大家给个建议行吗,,,谢谢了解决方案三:被控端时刻监听服务端的指令请求.控制端监听被控端的异常请求.如果被控端异常那么发送指令给控制端.由控制端决定是否让他重起.解决方案四:被控端不是自己写的,这个东西很棘手,,我在google上搜了老长时间都没有点儿方法解决方案五:a程序出现异常的时候,把异常代码存储到某个文件里面,b程序定时读取这个文件,发现

[Apache commons系列]DBUtils简介-1.总述

inkfish原创,请勿商业性质转载,转载请注明来源(http://blog.csdn.net/inkfish ). 一言蔽之,DbUtils就是让开发人员更方便的使用JDBC的工具类库,它不是任何一种框架或数据库.(来源:http://blog.csdn.net/inkfish) DbUtils作用有仨: 1.丢一个SQL,给一个结果: 2.把结果包装成Bean.Map.List或Object[]形式: 3.关闭Connection.Statement.ResultSet时候无需写try ca

重新想象 Windows 8.1 Store Apps (83) - 文件系统的新特性

原文:重新想象 Windows 8.1 Store Apps (83) - 文件系统的新特性 [源码下载] 重新想象 Windows 8.1 Store Apps (83) - 文件系统的新特性 作者:webabcd 介绍重新想象 Windows 8.1 Store Apps 之文件系统的新特性 简要说明 win8.1 中关于文件系统的增强 "库"管理 管理以及使用索引 示例1.简要说明 win8.1 中关于文件系统的增强Demo.xaml <Page x:Class="

一个不错的定时程序,写的很好的

 http://www.apkbus.com/forum.php?mod=viewthread&tid=159796 "本文参与趣米杯征文活动,如需转载请注明出处和作者". quitesleep是一款android手机的小软件.它可以设定在你睡觉的时候,当有电话打进来的时候.自动挂断或者设定为静音,并给打电话的人回复你之前设定好的邮件或者短信.保证你睡觉的时候不受打扰.       软件运行界面如下: Screenshot_2014-01-11-12-27-49.png (

Java基础-19总结IO流,异常try…catch,throws,File类

1:异常(理解) (1)程序出现的不正常的情况. (2)异常的体系 Throwable |--Error 严重问题,我们不处理. |--Exception |--RuntimeException 运行期异常,我们需要修正代码 |--非RuntimeException 编译期异常,必须处理的,否则程序编译不通过 package cn.itcast_01; /*  * 异常:程序出现了不正常的情况.  *   * 举例:今天天气很好,班长出去旅游.骑着自行车,去山里面呼吸新鲜空气.  *  问题1:

Java上的try catch并不影响性能(转)

转自:http://blog.csdn.net/tao_zi7890/article/details/17584813 最近因为担心自己开发的库不稳定,会影响到接入产品的正常运行,在代码的不少地方都加了一层try catch,主要是为了避免自己的一些异常抛出导致,应用crash,捕获的异常也并没有做什么特别的处理,只是在log中输出该异常的信息.try catch多了后,就开始担心会不会影响性能,以前学C或C++的时候好像曾经被警告过要尽量少使用try catch,好像是在压栈的时候会更消耗神马