无需写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 : PageBase
   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: }

转载自:http://www.cnblogs.com/artech/archive/2012/10/28/automatic-exception-handling-aspnet.html

时间: 2024-10-22 14:24:32

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

如何用Windbg找到被catch住的C++的异常

博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:如何用Windbg找到被catch住的C++的异常.

finally中使用return会吃掉catch中抛出的异常

今天学习大神的文章:深入理解java异常处理机制 学到一个有意思的知识点.如果在finally中使用return会吃掉catch中抛出的异常. 看例子: [java] view plaincopy public class TestException { public TestException() { } boolean testEx() throws Exception { boolean ret = true; try { ret = testEx1(); } catch (Excepti

厚溥教育1718部数据库连接作业答案,分装一个操作数据库而无需写SQL语句的函数

<?php header("Content-type:text/html;charset=utf8"); //PHP操作数据库的函数 function phpsql($dbconfig,$type,$field='',$data=array(),$condition=array(),$join=array()){ //判断存不存在数据库连接主机 if(!isset($dbconfig['host'])){ return "数据库连接主机不存在"; } //判断

js之try catch | js中也可以写try catch

myeclipse与数据库进行连接(无需写代码进行验证)

首先对SqlServer配置管理器进行设置. 1.打开SqlServer配置管理器 2.进入SQL配置管理器后,选中左侧“SQL Server网络配置”>>再选中“MSSQLSERVER的协议”双击后,右侧窗口会有“TCP/IP”选项.双击进入 3.在TCP/IP属性中对IP地址进行设置,IP地址设为“127.0.0.1”,即本地ip.端口值在后面会用到,先记住.如图示.注意,TCP/IP若不是处于启动状态,要右击后选择“启动” 4.进行完上述设置后,然后打开MyEclipse进入数据库操作界

如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习<深入理解java虚拟机-JVM高级特性与最佳实践>这本书,终于初步了解了一下java虚拟机的内存模型.本文通过写出使jvm发生内存溢出异常的代码来对自己的学习结果进行总结,同时也提醒自己以后写代码时候不要再跳进这个坑啦. java的内存管理是由java虚拟机自动进行管理的,并不需要程序员过多的手动干预

以写代学:python 错误、异常、断言

1.错误 (1)语法错误 语法错误指示软件的结构上有错误,导致不能被解释器解释或编译器无法编译.这些错误必须在程序执行前纠正. (2)逻辑错误 逻辑错误可能是由于不完整或是不合法的输入所致.还可能是逻辑无法生成,计算,或是输出结果需要的过程无法执行 2.异常 (1)当python检测到一个错误时,解释器就会指出当前流已经无法继续执行下去,这时候就出现了异常 (2)异常是因为程序出现了错误而在正常控制流以外采取的行为.这个行为又分为两个阶段首先是引起异常发生的错误,然后是检测(和采取可能的措施)阶

写if-else不外乎两种场景:异常逻辑处理和不同状态处理。

异常逻辑处理说明只能一个分支是正常流程,而不同状态处理都所有分支都是正常流程. 第一个例子`if (obj != null)`是异常处理,是代码健壮性判断,只有if里面才是正常的处理流程,`else`分支是出错处理流程 //举例一:异常逻辑处理例子 Object obj = getObj(); if (obj != null) { //do something }else{ //do something 第二个例子不管type等于1,2还是其他情况,都属于业务的正常流程.对于这两种情况重构的方法

Spring JMSTemplate 与 JMS 原生API比较

博客分类: JMS Spring 2.x JMSUtil与Spring JmsTemplate的对比 Author:信仰 Date:2012-4-20 未完待续,截止日期2012-4-20 从以下几方面比较JMSUtil和Spring JmsTemplate l  对JNDI的支持 l  对ConnectionFactory.Connection.Destination.Session.MessageProducer.MessageConsumer对象的处理 l  对事务的处理 l  不同类型的