在使用ASP.NET時,常常使用Page的錯誤事件Error進行錯誤捕捉和處理。這種方式可以集中處理所有異常,這種方式有利有弊。集中處理的好處就不用啰嗦了,這裡只說明一下這種方式的局限,就是當頁面中的某個控件發生異常之後,整個頁面執行都會中斷,然後處理異常,這樣一來,頁面就無法顯示。
在實際開發中,常常有這樣的需求,即頁面是由多個相對獨立的控件組成,其中一個控件的錯誤不能影響到其它控件的正常顯示。這就需要在控件內部捕捉錯誤,並自行處理錯誤,然而控件基類並沒有提供這樣的錯誤捕捉功能。如何用簡單有效方法來實現呢?
其實我們可以實現一個基類,並把所有在控件生命期中會調用到的方法都封裝起來,這樣只要繼承這個控件,就可以方便地實現在控件內部自行捕捉錯誤的功能。請看下面的代碼:
1public abstract class AbstractControl: Control 2{ 3 /**//// <summary> 4 /// 異常棧 5 /// </summary> 6 public Stack Exceptions 7 { 8 get 9 { 10 if (exceptions == null) 11 { 12 exceptions = new Stack(); 13 } 14 return exceptions; 15 } 16 } 17 18 protected override void CreateChildControls() 19 { 20 try 21 { 22 CreateChildControlsByCatchedException(); 23 } 24 catch (HttpUnhandledException) 25 { 26 throw; 27 } 28 catch (Exception ex) 29 { 30 Exceptions.Push(ex); 31 } 32 } 33 34 /**//// <summary> 35 /// 創建子控件(已進行異常捕捉處理) 36 /// </summary> 37 protected virtual void CreateChildControlsByCatchedException() 38 { 39 } 40 41 /**//// <summary> 42 /// 43 /// </summary> 44 /// <param name="e"></param> 45 protected override void OnPreRender(EventArgs e) 46 { 47 try 48 { 49 OnPreRenderByCatchedException(e); 50 } 51 catch (HttpUnhandledException) 52 { 53 throw; 54 } 55 catch (Exception ex) 56 { 57 Exceptions.Push(ex); 58 } 59 } 60 61 /**//// <summary> 62 /// 呈現前事件(已進行錯誤捕捉處理) 63 /// </summary> 64 /// <param name="e"></param> 65 protected virtual void OnPreRenderByCatchedException(EventArgs e) 66 { 67 base.OnPreRender (e); 68 } 69 70 /**//// <summary> 71 /// 設計時的呈現前事件 72 /// </summary> 73 /// <param name="e"></param> 74 protected virtual void DesigningOnPreRenderByCatchedException(EventArgs e) 75 { 76 } 77 78 /**//// <summary> 79 /// 呈現 80 /// </summary> 81 /// <param name="writer"></param> 82 protected override void Render(HtmlTextWriter writer) 83 { 84 if (Exceptions.Count > 0) 85 { 86 while (Exceptions.Count > 0 ) 87 { 88 Exception ex = (Exception) Exceptions.Pop(); 89 RenderException(writer, ex); 90 } 91 return; 92 } 93 94 try 95 { 96 RenderByCatchedException(writer); 97 } 98 catch (HttpUnhandledException) 99 { 100 throw; 101 } 102 catch (Exception ex) 103 { 104 RenderException(writer, ex); 105 } 106 } 107 108 /**//// <summary> 109 /// 呈現(已進行錯誤捕捉處理) 110 /// </summary> 111 /// <param name="writer"></param> 112 protected virtual void RenderByCatchedException(HtmlTextWriter writer) 113 { 114 base.Render (writer); 115 } 116 117 /**//// <summary> 118 /// 呈現異常 119 /// </summary> 120 /// <param name="writer"></param> 121 /// <param name="ex"></param> 122 private void RenderException(HtmlTextWriter writer, Exception ex) 123 { 124 writer.AddAttribute(HtmlTextWriterAttribute.Title, BuildExceptionInfomation(ex)); 125 writer.AddStyleAttribute("font-weight", "700"); 126 writer.AddStyleAttribute("color", "#f00"); 127 writer.AddStyleAttribute("border", "1px solid #ddd"); 128 writer.AddStyleAttribute("cursor", "pointer"); 129 writer.AddStyleAttribute("padding", "0px 3px 0px 3px"); 130 writer.AddStyleAttribute("background-color", "#ffe"); 131 writer.RenderBeginTag(HtmlTextWriterTag.Span); 132 writer.Write("!"); 133 writer.RenderEndTag(); 134 } 135 136 /**//// <summary> 137 /// 生成異常信息 138 /// </summary> 139 /// <param name="ex"></param> 140 /// <returns></returns> 141 private string BuildExceptionInfomation(Exception ex) 142 { 143 StringBuilder sb = new StringBuilder(); 144 sb.Append(ex.Message); 145 sb.Append(Environment.NewLine); 146 sb.Append(ex.GetType().FullName); 147 sb.Append(Environment.NewLine); 148 sb.Append(ex.StackTrace); 149 return sb.ToString(); 150 } 151 152 /**//// <summary> 153 /// 中斷程序的執行 154 /// </summary> 155 /// <param name="ex"></param> 156 protected virtual void Interrupt(Exception ex) 157 { 158 throw new HttpUnhandledException(ex.Message, ex); 159 } 160} 161
上面的代碼只重載了OnPreRender、Render和CreateChildControls三個方法,實際上還有OnInit、OnLoad等,可以視實際需要而重載,這樣重載之後,所有錯誤都被捕捉,並存放在錯誤棧中,並在呈現時將錯誤以某種格式呈現在界面上。注意,繼承AbstractControl基類的控件應重載如RenderByCatchedException之類的方法。
如果某些錯誤不希望被捕捉,而是直接拋出到頁面上,這時候還可以調用Interrupt方法來將錯誤直接拋出到頁面上,並中斷整個頁面的執行。