加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

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

发布时间:2020-12-16 09:05:32 所属栏目:asp.Net 来源:网络整理
导读:对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情。说其简单,是因为相关的编程无外乎try/catch/finally+throw而已;说其复杂,是因为我们往往很难按照我们真正需要的策略来处理异常。我一直有这样的想法,理想的企业应用开发中应该尽量让框架来

对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情。说其简单,是因为相关的编程无外乎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
   3:     //业务代码
   5: catch(Exception ex)
   7:     if(ExceptionPolicy.HandleException(ex,"exceptionPolcyName"))
   9:         throw;
  11: }

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

二、实例演示

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

   2:   ...
   5:       add name="default"   6:         exceptionTypes   7:           type="System.Exception,mscorlib" 
  11:                    messageTemplate="[{ExceptionType}]{Message}"/>
  14:           15:         16:       17:   >     
public partial class Default : PageBase
   4:     {
   6:         {
   8:             string op2 = Request.QueryString["op2"];
  10:             {
  12:             }
  14:     }
void btnCal_Click(  17:     {
  19:         int op2 = this.txtOp2.Text);
  21:     }
abstract class PageBase: Page
public PageBase()
this.ExceptionPolicyName = "default";
   8:? 
  10:     {
  12:             .OfType<ExceptionPolicyAttribute>().FirstOrDefault();
  14:         {
  16:         }
  18:         {
  20:         }
  23:     override void OnLoad(EventArgs e)
  25:         this.InvokeAndHandleException(() => base.OnLoad(e));
  27:? 
  29:     {
  31:     }
  33:     private void InvokeAndHandleException(Action action)
  35:           36:         {
  38:         }
  40:         {
  42:             if (ExceptionPolicy.HandleException(ex,exceptionPolicyName))
  44:                 throw;
  46:         }
  48: }

如上面的代码片断所示,在重写的OnLoad和RaisePostBackEvent方法中,我们采用与EntLib异常处理应用块的编程方式调用基类的同名方法。我们通过属性ExceptionPolicyName 指定了一个默认的异常处理策略名称(“default”,也正是配置文件中定义个策略名称)。如果某个页面需要采用其他的异常处理策略,可以在类型上面应用ExceptionPolicyAttribute特性来制定,该特性定义如下:

2: class ExceptionPolicyAttribute: Attribute
string ExceptionPolicyName { get; private set; }
   6:     {
   8:         this.ExceptionPolicyName = exceptionPolicyName;
  10: }

四、IPostBackDataHandler

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

bool LoadPostData(string postDataKey,NameValueCollection postCollection);
   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的程序》一文中具有详细介绍。

object Target { get; private set; }
   8:     public EventHandlerWrapper(EventHandler eventHandler,1)">string exceptionPolicyName)
  10:         Guard.ArgumentNotNull(eventHandler,1)">"eventHandler");
this.Target = eventHandler.Target;
  15:         this.ExceptionPolicyName = exceptionPolicyName;
  17:     }
  19:     {
  21:         return eventHandlerWrapper.Hander;
void Invoke(  26:         {
  28:         }
  30:         {
  32:             {
  35:         }
  37: }

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

protected EventHandlerList Events{get;}
class EventHandlerWrapperUtil
static FieldInfo handler;
static FieldInfo next;
  10:         listEntryType = Type.GetType("System.ComponentModel.EventHandlerList+ListEntry,System,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");
  12:         handler = listEntryType.GetField("handler",bindingFlags);
  14:         next    = listEntryType.GetField("next",1)" id="lnum15">  15:     }
  17:     void Wrap(object listEntry,1)" id="lnum18">  18:     {
null != eventHandler)
  22:             EventHandlerWrapper eventHandlerWrapper = new EventHandlerWrapper(eventHandler,exceptionPolicyName);
  24:         }
  26:         if(null != nextEntry)
  28:             Wrap(nextEntry,1)" id="lnum29">  29:         }
  31: }

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

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

static PropertyInfo eventsProperty;
   5:? 
static PageBase()
  10:         eventsProperty = typeof(Control).GetProperty("Events",BindingFlags.Instance | BindingFlags.NonPublic);
  12:     }
  14:     void OnInit(EventArgs e)
base.OnInit(e);
this.WrapEventHandlers(this);
  20:     }
  22:     void WrapEventHandlers(Control control)
  24:         this.GetExceptionPolicyName();
null != events)
  29:             null != head)
  31:                 EventHandlerWrapperUtil.Wrap(head,exceptionPolicyName);
  33:         }
  35:         {
  37:         }
  39:? 
  41:     {
  43:             .OfType<ExceptionPolicyAttribute>().FirstOrDefault();
  45:         {
  47:         }
  49:         {
  51:         }
  53: }

七、AlertHandler

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

class AlertHandler: IExceptionHandler
   7:         this.MessageTemplate = messageTemplate;
   9:? 
  11:     {
string messageTemplate = string.IsNullOrEmpty(this.MessageTemplate) ? exception.Message : this.MessageTemplate;
  15:                                 .Replace("{HelpLink}",exception.HelpLink)
  17:                                 .Replace("{Source}",exception.Source)
  19:     }
  21:     public Exception HandleException(Exception exception,Guid handlingInstanceId)
  23:         Page page = HttpContext.Current.Handler as Page;
  25:         {
string message = this.FormatMessage(exception);
  29:             page.ClientScript.RegisterHiddenField(hiddenControl,message);
  31:                 object[] { hiddenControl });
return exception;
  36: }
  38: class AlertHandlerData : ExceptionHandlerData
  40:     [ConfigurationProperty("messageTemplate",IsRequired = false,DefaultValue="")]
  42:     {
  44:         set { "messageTemplate"] = value; }
  46:? 
  48:     {
  50:         {
  52:             Lifetime = TypeRegistrationLifetime.Transient
  54:     }