在哪些网站做收录比较快,新媒体营销岗位有哪些,微信公众平台开发者文档,旅游网站设计asp对于企业应用的开发者来说#xff0c;异常处理是一件既简单又复杂的事情。说其简单#xff0c;是因为相关的编程无外乎try/catch/finallythrow而已#xff1b;说其复杂#xff0c;是因为我们往往很难按照我们真正需要的策略来处理异常。我一直有这样的想法#xff0c;理想… 对于企业应用的开发者来说异常处理是一件既简单又复杂的事情。说其简单是因为相关的编程无外乎try/catch/finallythrow而已说其复杂是因为我们往往很难按照我们真正需要的策略来处理异常。我一直有这样的想法理想的企业应用开发中应该尽量让框架来完成对异常的处理最终的开发人员在大部分的情况下无需编写异常处理相关的任何代码。在这篇文章中我们将提供一个解决方案来让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 namedefault 6: exceptionTypes 7: add typeSystem.Exception, mscorlib 8: postHandlingActionNone nameException 9: exceptionHandlers 10: add nameAlert Handler typeAutomaticExceptionHandling.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: .OfTypeExceptionPolicyAttribute().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的时候向服务端传递相应的数据我们熟悉的ListControlDropDownList、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.EventHandlerListListEntry, System, Version4.0.0.0, Cultureneutral, PublicKeyTokenb77a5c561934e089); 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: .OfTypeExceptionPolicyAttribute().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 objdocument.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 IEnumerableTypeRegistration GetRegistrations(string namePrefix) 48: { 49: yield return new TypeRegistrationIExceptionHandler(() 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 程序员的基础教程菜鸟程序员