无需写try/catch,也能正常处理异常 (转)
原文地址: http://www.cnblogs.com/artech/archive/2012/10/28/automatic-exception-handling-aspnet.html
对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情。说其简单,是因为相关的编程无外乎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: }
无需写try/catch,也能正常处理异常 (转)的更多相关文章
- 无需写try/catch,也能正常处理异常
对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情.说其简单,是因为相关的编程无外乎try/catch/finally+throw而已:说其复杂,是因为我们往往很难按照我们真正需要的策略来处理 ...
- 【QT】无需写connect代码关联信号和槽函数
对于一些简单的事件判别,如点击按钮. 无需写代码关联信号和槽函数. connect(ui->Btnshowhello,SIGNAL(clicked(bool)),this,SLOT(Btnsho ...
- 如何用Windbg找到被catch住的C++的异常
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:如何用Windbg找到被catch住的C++的异常.
- Online Coding开发模式 (通过在线配置实现一个表模型的增删改查功能,无需写任何代码)
JEECG 智能开发平台. 开发模式由代码生成器转变为Online Coding模式 (通过在线配置实现一个表模型的增删改查功能,无需一行代码,支持用户自定义 ...
- c#winform将全局异常抛出,不用大量写try()catch()
一.在program.cs处完善成如下,但是这样后只能抛出主线程(UI)的错误,所以请看第二步 /// 应用程序的主入口点. /// </summary> [STAThread] stat ...
- 厚溥教育1718部数据库连接作业答案,分装一个操作数据库而无需写SQL语句的函数
<?php header("Content-type:text/html;charset=utf8"); //PHP操作数据库的函数 function phpsql($dbc ...
- [Java 开源项目]一款无需写任何代码,即可一键生成前后端代码的工具
作者:HelloGitHub-小鱼干 JeecgBoot 是一款基于代码生成器的低代码开发平台,零代码开发.JeecgBoot 采用开发模式:Online Coding 模式-> 代码生成器模式 ...
- myeclipse与数据库进行连接(无需写代码进行验证)
首先对SqlServer配置管理器进行设置. 1.打开SqlServer配置管理器 2.进入SQL配置管理器后,选中左侧“SQL Server网络配置”>>再选中“MSSQLSERVER的 ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
随机推荐
- uva133 救济金发放
#include<stdio.h> #define maxn 20 ], n; int go( int p,int d,int t ) {// printf("a[%d]=%d\ ...
- Delphi常用关键字用法详解
本文详细介绍了Delphi中常用的各个关键字名称及用法,供大家在编程过程中借鉴参考之用.详情如下: absolute: ? 1 2 3 4 5 6 7 8 9 10 //它使得你能够创建一个新变量, ...
- Kali Linux 2.0配置并安装常用的工具
Kali Linux 前身是著名渗透测试系统BackTrack ,是一个基于 Debian 的 Linux 发行版,包含很多安全和取证方面的相关工具. Kali Linux 2.0是基于Debian ...
- php获取文件名称和扩展名
1.basename()-返回路径的文件名请看下面php代码: <?php $path = "/usr/www/html/index.php"; echo basename( ...
- python之购物车的编写(熬夜撸代码中。。。)
购物车的编写对于我这种不是很精通函数的小白来说,简直太难了.各种坑,各种无奈啊!不过总算也是写出来了! 不多说,直接上代码! #!/usr/bin/env python#用户名 sanjiang#密码 ...
- malloc原理和内存碎片[转]
当一个进程发生缺页中断的时候,进程会陷入内核态,执行以下操作: 1.检查要访问的虚拟地址是否合法 2.查找/分配一个物理页 3.填充物理页内容(读取磁盘,或者直接置0,或者啥也不干) 4.建立映射关系 ...
- 【转】excel 末尾是0 恢复数据方法
今天从数据库里面查了点数据,用plsql导出为csv数据后用excel打开后就出问题了,显示的问题,excel中会遇到一列中因为数字太长,结果变成了用科学计数法来表示,而这种损失不可逆的,及时改变其格 ...
- Yii2安装搭建和将入口文件移到根目录
用Composer下载Yii2速度太慢了,所以我还是喜欢下载打包好的框架文件. 在https://github.com/yiisoft/yii2/releases 下载最新的的Yii2,advance ...
- 深入浅出SQL Server中的死锁
简介 死锁的本质是一种僵持状态,是多个主体对于资源的争用而导致的.理解死锁首先需要对死锁所涉及的相关观念有一个理解. 一些基础知识 要理解SQL Server中的死锁,更好的方式是通过类比从更大的面理 ...
- STM32 硬件I2C 到底是不是个坑?
/** ****************************************************************************** * @author Maox ...