西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁(yè)編程開(kāi)發(fā)ASP.NET → ASP.NET中不用try/catch進(jìn)行正常處理異?偨Y(jié)

ASP.NET中不用try/catch進(jìn)行正常處理異常總結(jié)

相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:西西整理時(shí)間:2012/10/28 20:26:16字體大。A-A+

作者:西西點(diǎn)擊:214次評(píng)論:0次標(biāo)簽: 異常

對(duì)于企業(yè)應(yīng)用的開(kāi)發(fā)者來(lái)說(shuō),異常處理是一件既簡(jiǎn)單又復(fù)雜的事情。說(shuō)其簡(jiǎn)單,是因?yàn)橄嚓P(guān)的編程無(wú)外乎try/catch/finally+throw而已;說(shuō)其復(fù)雜,是因?yàn)槲覀兺茈y按照我們真正需要的策略來(lái)處理異常。我一直有這樣的想法,理想的企業(yè)應(yīng)用開(kāi)發(fā)中應(yīng)該盡量讓框架來(lái)完成對(duì)異常的處理,最終的開(kāi)發(fā)人員在大部分的情況下無(wú)需編寫(xiě)異常處理相關(guān)的任何代碼。在這篇文章中我們將提供一個(gè)解決方案來(lái)讓ASP.NET應(yīng)用利用EntLib的異常處理模塊來(lái)實(shí)現(xiàn)自動(dòng)化的異常處理。

源代碼:
Sample1[通過(guò)重寫(xiě)Page的OnLoad和OnRaisePostBackEvent方法]
Sample2[通過(guò)自動(dòng)封裝注冊(cè)的EventHandler]

一、EntLib的異常處理方式
二、實(shí)例演示
三、通過(guò)重寫(xiě)Page的OnLoad和RaisePostBackEvent方法實(shí)現(xiàn)自動(dòng)異常處理
四、IPostBackDataHandler
五、EventHandlerWraper
六、對(duì)控件注冊(cè)事件的自動(dòng)封裝
七、AlertHandler

一、EntLib的異常處理方式

所謂異常,其本意就是超出預(yù)期的錯(cuò)誤。既然如此,異常處理的策略就不可能一成不變,我們不可能在開(kāi)發(fā)階段就制定一個(gè)完備的異常處理策略來(lái)處理未來(lái)發(fā)生的所有異常。異常處理策略應(yīng)該是可配置的,能夠隨時(shí)進(jìn)行動(dòng)態(tài)改變的。就此而言,微軟的企業(yè)庫(kù)(以下簡(jiǎn)稱EntLib)的異常處理應(yīng)用塊(Exception Handling Application Block)是一個(gè)不錯(cuò)的異常處理框架,它運(yùn)行我們通過(guò)配置文件來(lái)定義針對(duì)具體異常類型的處理策略。

針對(duì)EntLib的異常處理應(yīng)用塊采用非常簡(jiǎn)單的編程方式,我們只需要按照如下的方式捕捉拋出的一場(chǎng),并通過(guò)調(diào)用ExceptionPolicy的HandleException根據(jù)指定的異常策略進(jìn)行處理即可。對(duì)于ASP.NET應(yīng)用來(lái)說(shuō),我們可以注冊(cè)HttpApplication的Error事件的形式來(lái)進(jìn)行統(tǒng)一的異常處理。但是在很多情況下,我們往往需要將異?刂圃诋(dāng)前頁(yè)面之內(nèi)(比如當(dāng)前頁(yè)面被正常呈現(xiàn),并通過(guò)執(zhí)行一段JavaScript探出一個(gè)對(duì)話框顯示錯(cuò)誤消息),我們往往需要將下面這段相同的代碼結(jié)構(gòu)置于所有控件的注冊(cè)事件之中。

   1: try
   2: {
   3:     //業(yè)務(wù)代碼
   4: }
   5: catch(Exception ex)
   6: {
   7:     if(ExceptionPolicy.HandleException(ex,"exceptionPolcyName"))
   8:     {
   9:         throw;
  10:     }
  11: }

我個(gè)人不太能夠容忍完全相同的代碼到處出現(xiàn),代碼應(yīng)該盡可能地重用,而不是重復(fù)。接下來(lái)我們就來(lái)討論如何采用一些編程上的手段或者技巧來(lái)讓開(kāi)發(fā)人員無(wú)須編寫(xiě)任何的異常處理代碼,而拋出的確卻能按照我們預(yù)先指定的策略被處理。

二、實(shí)例演示

為了讓讀者對(duì)“自動(dòng)化異常處理”有一個(gè)直觀的認(rèn)識(shí),我們來(lái)做一個(gè)簡(jiǎn)單的實(shí)例演示。我們的異常處理策略很簡(jiǎn)單:如果后臺(tái)代碼拋出異常,異常的相關(guān)信息按照預(yù)定義的格式通過(guò)Alert的方式顯示在當(dāng)前頁(yè)面中。如下所示的是異常處理策略在配置文件中的定義,該配置中定義了唯一個(gè)名為“default”的異常策略,該策略利用自定義的AlertHandler來(lái)顯示一場(chǎng)信息。配置屬性messageTemplate定義了一個(gè)模板用于控制顯示消息的格式。

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

現(xiàn)在我們定義一個(gè)簡(jiǎn)單的頁(yè)面來(lái)模式自動(dòng)化異常處理,這個(gè)頁(yè)面是一個(gè)用于進(jìn)行除法預(yù)算的計(jì)算器。如下所示的該頁(yè)面的后臺(tái)代碼,可以看出它沒(méi)有直接繼承自Page,而是繼承自我們自定義的基類PageBase,所有異常處理的機(jī)制就實(shí)現(xiàn)在此。Page_Load方法收集以QueryString方式提供的操作數(shù),并轉(zhuǎn)化成整數(shù)進(jìn)行除法預(yù)算,最后將運(yùn)算結(jié)果顯示在表示結(jié)果的文本框中。計(jì)算按鈕的Click事件處理方法根據(jù)用戶輸入的操作數(shù)進(jìn)行除法運(yùn)算。兩個(gè)方法中均沒(méi)有一句與異常處理相關(guān)的代碼。

   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: }

現(xiàn)在運(yùn)行我們程序,可以想象如果在表示操作數(shù)的文本框中輸入一個(gè)非整數(shù)字符,調(diào)用Int32的Parse方法時(shí)將會(huì)拋出一個(gè)FormatException異常,或者將被除數(shù)設(shè)置為0,則會(huì)拋出一個(gè)DivideByZeroException異常。如下面的代碼片斷所示,在這兩種情況下相應(yīng)的錯(cuò)誤信息按照我們預(yù)定義的格式以Alert的形式顯示出來(lái)。

三、通過(guò)重寫(xiě)Page的OnLoad和RaisePostBackEvent方法實(shí)現(xiàn)自動(dòng)異常處理

我們知道ASP.NET應(yīng)用中某個(gè)頁(yè)面的后臺(tái)代碼基本上都是注冊(cè)到頁(yè)面及其控件的事件處理方法,除了第一次呈現(xiàn)頁(yè)面的Load事件,其他事件均是通過(guò)PostBack的方式出發(fā)的。所以我最初的解決方案很直接:就是提供一個(gè)PageBase,在重寫(xiě)的OnLoad和RaisePostBackEvent方法中進(jìn)行異常處理。PageBase的整個(gè)定義如下所示:

   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().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: }

如上面的代碼片斷所示,在重寫(xiě)的OnLoad和RaisePostBackEvent方法中,我們采用與EntLib異常處理應(yīng)用塊的編程方式調(diào)用基類的同名方法。我們通過(guò)屬性ExceptionPolicyName 指定了一個(gè)默認(rèn)的異常處理策略名稱(“default”,也正是配置文件中定義個(gè)策略名稱)。如果某個(gè)頁(yè)面需要采用其他的異常處理策略,可以在類型上面應(yīng)用ExceptionPolicyAttribute特性來(lái)制定,該特性定義如下:

   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

通過(guò)為具體Page定義基類并重寫(xiě)OnLoad和RaisePostBackEvent方法的方式貌似能夠?qū)崿F(xiàn)我們“自動(dòng)化異常處理”的目標(biāo),而且針對(duì)我們提供的這個(gè)實(shí)例來(lái)說(shuō)也是OK的。但是這卻不是正確的解決方案,原因在于并非所有控件的事件都是在RaisePostBackEvent方法執(zhí)行過(guò)程中觸發(fā)的。ASP.NET提供了一組實(shí)現(xiàn)了IPostBackDataHandler接口的控件類型,它們會(huì)向PostBack的時(shí)候向服務(wù)端傳遞相應(yīng)的數(shù)據(jù),我們熟悉的ListControl(DropDownList、ListBox、RadioButtonList和CheckBoxList等)就屬于此類。

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

當(dāng)Page的ProcessRequest(這是對(duì)IHttpHandler方法的實(shí)現(xiàn))的時(shí)候,會(huì)先于RaisePostBackEvent之前調(diào)用另一個(gè)方法RaiseChangedEvents。在RaisePostBackEvent方法執(zhí)行過(guò)程中,如果目標(biāo)類型實(shí)現(xiàn)了IPostBackDataHandler接口,會(huì)調(diào)用它們的RaisePostDataChangedEvent方法。很多表示輸入數(shù)據(jù)改變的事件(比如ListControl的SelectedIndexChanged事件)就是被RaisePostDataChangedEvent方法觸發(fā)的。如果可能,我們可以通過(guò)重寫(xiě)RaiseChangedEvents方法的方式來(lái)解決這個(gè)問(wèn)題,不過(guò)很可惜,這個(gè)方法是一個(gè)內(nèi)部方法。

五、EventHandlerWraper

要實(shí)現(xiàn)“自動(dòng)化異常處理”的根本手段就是將頁(yè)面和控件注冊(cè)的事件處理方法置于一個(gè)try/catch塊中執(zhí)行,并采用EntLib的異常處理應(yīng)用塊的方式對(duì)拋出的異常進(jìn)行處理。如果我們能夠改變頁(yè)面和控件注冊(cè)的事件,使注冊(cè)的事件處理器本身就具有異常處理的能力,我們“自動(dòng)化異常處理”的目標(biāo)也能夠?qū)崿F(xiàn)。為此我定義了如下一個(gè)用于封裝EventHandler的EventHandlerWrapper,它將EventHandler的置于一個(gè)try/catch塊中執(zhí)行。對(duì)于EventHandlerWrapper的設(shè)計(jì)思想,在我兩年前寫(xiě)的《如何編寫(xiě)沒(méi)有Try/Catch的程序》一文中具有詳細(xì)介紹。

   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: }

由于我們?yōu)镋ventHandlerWrapper定義了一個(gè)針對(duì)EventHandler的隱式轉(zhuǎn)化符,一個(gè)EventHandlerWrapper對(duì)象能夠自動(dòng)被轉(zhuǎn)化成EventHandler對(duì)象。我們現(xiàn)在的目標(biāo)就是:將包括頁(yè)面在內(nèi)的所有控件注冊(cè)的EventHandler替換成用于封裝它們的EventHandlerWrapper。我們知道所有控件的基類Control具有如下一個(gè)受保護(hù)的只讀屬性Events,所有注冊(cè)的EventHandler就包含在這里,而我們的目標(biāo)就是要改變所有控件該屬性中保存的EventHandler。

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

其實(shí)要改變Events屬性中的EventHandler也并不是一件容易的事,因?yàn)槠漕愋虴ventHandlerList 并不如它的名稱表現(xiàn)出來(lái)的那樣是一個(gè)可枚舉的列表,而是一個(gè)通過(guò)私有類型ListEntry維護(hù)的鏈表。要改變這些注冊(cè)的事件,我們不得不采用反射,而這會(huì)影響性能。不過(guò)對(duì)應(yīng)并非訪問(wèn)量不高的企業(yè)應(yīng)用來(lái)說(shuō),我覺(jué)得這點(diǎn)性能損失是可以接受的。整個(gè)操作被定義在如下所示的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: }


六、對(duì)控件注冊(cè)事件的自動(dòng)封裝

對(duì)包括頁(yè)面在內(nèi)的所有控件注冊(cè)時(shí)間的自動(dòng)封裝同樣實(shí)現(xiàn)在作為具體頁(yè)面積累的PageBase中。具體的實(shí)現(xiàn)定義在WrapEventHandlers方法中,由于Control的Events屬性是受保護(hù)的,所以我們還得采用反射。該方法最終的重寫(xiě)的OnInit方法中執(zhí)行。

   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().FirstOrDefault();
  44:         if (null != attribute)
  45:         {
  46:             return attribute.ExceptionPolicyName;
  47:         }
  48:         else
  49:         {
  50:             return ExceptionPolicyName;
  51:         }
  52:     }
  53: }


七、AlertHandler

我想有人對(duì)用于顯示錯(cuò)誤消息對(duì)話框的AltertHandler的實(shí)現(xiàn)很感興趣,下面給出了它和對(duì)應(yīng)的AlertHandlerData的定義。從如下的代碼可以看出,AltertHandler僅僅是調(diào)用Page的RaisePostBackEvent方法注冊(cè)了一段顯示錯(cuò)誤消息的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("", 
  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 GetRegistrations(string namePrefix)
  48:     {
  49:         yield return new TypeRegistration(() => new AlertHandler(this.MessageTemplate))
  50:         {
  51:             Name = this.BuildName(namePrefix),
  52:             Lifetime = TypeRegistrationLifetime.Transient
  53:         };
  54:     }
  55: }

    相關(guān)評(píng)論

    閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過(guò)難過(guò)
    • 5 囧
    • 3 圍觀圍觀
    • 2 無(wú)聊無(wú)聊

    熱門(mén)評(píng)論

    最新評(píng)論

    發(fā)表評(píng)論 查看所有評(píng)論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)