閱讀493 返回首頁    go 技術社區[雲棲]


ASP.NET MVC集成EntLib實現“自動化”異常處理[實例篇]

個人覺得異常處理對於程序員來說是最為熟悉的同時也是最難掌握的。說它熟悉,因為僅僅就是try/catch/finally而已。說它難以掌握,則是因為很多開發人員卻說不清楚try/catch/finally應該置於何處?什麼情況下需要對異常進行日誌記錄?什麼情況下需要對異常進行封裝?什麼情況下需要對異常進行替換?對於捕獲的異常,在什麼情況下需要將其再次拋出?什麼情況下則不需要?

合理的異常處理應該是場景驅動的,在不同的場景下,采用的異常處理策略往往是不同的。異常處理的策略應該是可配置的,因為應用程序出現怎樣的異常往往是不可預測的,現有異常策略的不足往往需要在真正出現某種異常的時候才會體現出來,所以我們需要一種動態可配置的異常處理策略維護方式。目前有一些開源的異常處理框架提供了這種可配置的、場景驅動的異常處理方式,EntLib的Exception Handling Application Block(以下簡稱EHAB)就是一個不錯的選擇。[源代碼從這裏下載][本文已經同步到《How ASP.NET MVC Works?》中]

目錄
一、通過指定Handle-Error-Action響應請求
二、通過Error View顯示錯誤消息
三、自動創建JsonResult響應Ajax請求

在正式介紹如何通過擴展實現與EntLib以實現自動化異常處理之前,我們不妨先來體驗一下異常處理具有怎樣的“自動化”特性。以用戶登錄場景為例,我們在通過Visual Studio的ASP.NET MVC項目模板創建的Web應用中定義了如下一個簡單的數據類型LoginInfo封裝用戶登錄需要輸入的用戶名和密碼。

   1: public class LoginInfo
   2: {
   3:     [DisplayName("用戶名")]
   4:     [Required(ErrorMessage="請輸入{0}")]
   5:     public string UserName { get; set; }
   6:  
   7:     [DisplayName("密碼")]
   8:     [Required(ErrorMessage = "請輸入{0}")]
   9:     [DataType(DataType.Password)]
  10:     public string Password { get; set; }
  11: }

然後我們定義了如下一個HomeController。基於HTTP-GET的Action方法Index將會呈現一個用戶登錄View,該View使用創建的LoginInfo對象作為其Model。真正的用戶驗證邏輯定義在另一個應用了HttpPostAttrubute特性的Index方法中:如果用戶名不為Foo,拋出InvalidUserNameException異常;如果密碼不是“password”,則拋出InvalidPasswordException異常。InvalidUserNameException和InvalidPasswordException是我們自定義的兩種異常類型。

   1: [ExceptionPolicy("defaultPolicy")]
   2: public class HomeController : ExtendedController
   3: {
   4:     public ActionResult Index()
   5:     {
   6:         return View(new LoginInfo());
   7:     }
   8:  
   9:     [HttpPost]
  10:     [HandleErrorAction("OnIndexError")]
  11:     public ActionResult Index(LoginInfo loginInfo)
  12:     {
  13:         if (string.Compare(loginInfo.UserName, "foo", true) != 0)
  14:         {
  15:             throw new InvalidUserNameException();
  16:         }
  17:  
  18:         if (loginInfo.Password != "password")
  19:         {
  20:             throw new InvalidPasswordException();
  21:         }
  22:         return View(loginInfo);
  23:     }
  24:  
  25:     [HttpPost]
  26:     public ActionResult OnIndexError(LoginInfo loginInfo)
  27:     {
  28:         return View(loginInfo);
  29:     }
  30: }

上麵定義的HomeController具有三點與自動化異常處理相關的地方:

  • HomeController繼承自自定義的基類ExtendedController,後者完成了對異常的自動化處理。
  • HomeController類型上應用了自定義的ExceptionPolicyAttribute特性用於指定默認采用的異常處理策略名稱(“defaultPolicy”)。
  • 基於HTTP-POST的Index方法上應用了HandleErrorActionAttribute特性用於指定一個Handle-Error-Action名稱,當異常在目標Action執行過程中拋出並通過EHAB處理後,指定的Action會被執行以實現對請求的響應。對於我們的例子來說,從Index方法拋出的異常被處理後會調用OnIndexError方法作為對當前請求的響應。

下麵是代表登錄頁麵的View的定義,這是一個Model類型為LoginInfo的強類型View。在該View中,作為Model的LoginInfo對象以編輯默認呈現在一個表單中,表單中提供了一個“登錄”提交表單。除此之外,View中還具有個ValidationSummary。

   1: @model LoginInfo
   2: <html>
   3:     <head>
   4:         <title>用戶登錄</title>
   5:         <style type="text/css">
   6:             .validation-summary-errors{color:Red}
   7:         </style>
   8:     </head>
   9:     <body>
  10:         @using (Html.BeginForm())
  11:         { 
  12:             @Html.ValidationSummary(true)
  13:             @Html.EditorForModel()
  14:             <input type="submit" value="登錄" />
  15:         }
  16:     </body>
  17: </html>

通過HomeController的定義我們知道兩種不同類型的異常(InvalidUserNameException和InvalidPasswordException)分別在輸入無效用戶名和密碼是被拋出來,而我們需要處理的就是這兩種類型的異常。正對它們的異常處理策略定義在如下的配置中,策略名稱就是通過應用在HomeController上的ExceptionPolicyAttribute特性指定的“defaultPolicy”。

   1: <configuration>
   2:   <configSections>
   3:       <section name="exceptionHandling" 
   4:          type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" />
   5:    </configSections>
   6:   <exceptionHandling>
   7:     <exceptionPolicies>
   8:       <add name="defaultPolicy">
   9:         <exceptionTypes>
  10:           <add type="MvcApp.InvalidUserNameException, MvcApp" postHandlingAction="ThrowNewException" name="InvalidUserNameException">
  11:             <exceptionHandlers>
  12:               <add name ="ErrorMessageHandler" type="MvcApp.ErrorMessageHandler, MvcApp" errorMessage="用戶名不存在"/>
  13:             </exceptionHandlers>
  14:           </add>
  15:  
  16:           <add type="MvcApp.InvalidPasswordException, MvcApp" postHandlingAction="ThrowNewException" name="InvalidPasswordException">
  17:             <exceptionHandlers>
  18:               <add name ="ErrorMessageHandler" type="MvcApp.ErrorMessageHandler, MvcApp" errorMessage="密碼與用戶名不匹配"/>
  19:             </exceptionHandlers>
  20:           </add>
  21:         </exceptionTypes>
  22:       </add>
  23:     </exceptionPolicies>
  24:   </exceptionHandling>
  25:   ...
  26: </configuration>

通過上麵的這樣異常策略配置可以看到:我們使用一個自定義的名為ErrorMessageHandler的ExceptionHandler來處理拋出來的InvalidUserNameException和InvalidPasswordException異常,而ErrorMessageHandler僅僅是指定一個友好的錯誤消息,該消息一般會呈現給最終的用戶。運行該程序後一個用於登錄頁麵會呈現出來,當我們輸入錯誤的用戶名和密碼的時候,相應的錯誤消息(在配置中通過ErrorMessageHandler設置的錯誤消息)會以如圖7-16所示的效果顯示出來,其實整個View是通過執行Action方法OnIndexError返回的ViewResult呈現出來的。

image

除了通過執行對應的Handle-Error-Action來呈現異常處理後的最終結果之外,還支持錯誤頁麵的錯誤呈現方法。簡單起見,我們隻是用名稱為Error的View來作為最終的錯誤頁麵。為了演示基於錯誤頁麵的呈現方式,我們按照如下的方式重新定義了\Views\Shared\目錄下的Error.cshtml。

   1: @model ExtendedHandleErrorInfo
   2: @{
   3:     Layout = null;
   4: }
   5: <!DOCTYPE html>
   6: <html>
   7: <head>
   8:     <meta name="viewport" content="width=device-width" />
   9:     <title>Error</title>
  10:     <style type="text/css">
  11:         h3 {color:Red;}
  12:     </style>
  13: </head>
  14: <body>
  15:     <h3>
  16:         @Html.DisplayFor(m=>m.ErrorMessage)
  17:     </h3>
  18:     <ul>
  19:         <li>Controller: @Html.DisplayFor(m => m.ControllerName)</li>
  20:         <li>Action: @Html.DisplayFor(m => m.ActionName)</li>
  21:         <li>Exception: 
  22:             <ul>
  23:                 <li>Message: @Html.DisplayFor(m => m.Exception.Message)</li>
  24:                 <li>Type: @Model.Exception.GetType().FullName</li>
  25:                 <li>StackTrace: @Html.DisplayFor(m => m.Exception.StackTrace)</li>
  26:             </ul>
  27:         </li>
  28:     </ul>
  29: </body>
  30: </html>

上麵這個View的Model類型是具有如下定義的ExtendedHandleErrorInfo。它繼承自HandleErrorInfo,隻額外定義了一個表示錯誤消息的ErrorMessage屬性。在上麵的這個View中,我們將錯誤消息、異常類型和StackTrace和當前Controller/Action的名稱呈現出來。

   1: public class ExtendedHandleErrorInfo : HandleErrorInfo
   2: {
   3:     public string ErrorMessage { get; private set; }
   4:     public ExtendedHandleErrorInfo(Exception exception, string controllerName, string actionName, string errorMessage)
   5:         : base(exception, controllerName, actionName)
   6:     {
   7:         this.ErrorMessage = errorMessage;
   8:     }
   9: }

當利用EntLib的EHAB對從Index方法中拋出的異常進行處理後采用錯誤View的方式來響應請求,我們需要按照如下的方式將應用在該方法上的HandleErrorActionAttribute特性注釋掉。

   1: [ExceptionPolicy("defaultPolicy")]
   2: public class HomeController : ExtendedController
   3: {    
   4:     //其他成員
   5:     [HttpPost]
   6:     //[HandleErrorAction("OnIndexError")]
   7:     public ActionResult Index(LoginInfo loginInfo)
   8:     {
   9:         //省略實現
  10:     }
  11: }

再次運行該程序並分別輸入錯誤的用戶名和密碼後,默認的錯誤View(Error.cshtml)將會以如下圖所示地效果把處理後的異常結果呈現出來。

image

用於實施認證的Action方法Index可以通過普通的HTTP-POST的形式來調用,同樣也可以通過Ajax請求的方式來調用。對於Ajax請求來說,我們最終會將通過EntLib處理後的異常封裝成如下一個類型為ExceptionDetail的對象。如下麵的代碼片斷所示,ExceptionDetail具有與Exception對應的屬性設置。最終根據拋出異常對象創建的ExceptionDetail對象會被用於創建一個JsonResult對象對當前Ajax請求予以響應。

   1: public class ExceptionDetail
   2: {
   3:     public ExceptionDetail(Exception exception,string errorMessage=null)
   4:     {
   5:         this.HelpLink = exception.HelpLink;
   6:         this.Message = string.IsNullOrEmpty(errorMessage) ? exception.Message : errorMessage;
   7:         this.StackTrace = exception.StackTrace;
   8:         this.Type = exception.GetType().ToString();
   9:         if (exception.InnerException != null)
  10:         {
  11:             this.InnerException = new ExceptionDetail(exception.InnerException);
  12:         }
  13:     }
  14:  
  15:     public string HelpLink { get; set; }
  16:     public ExceptionDetail InnerException { get; set; }
  17:     public string Message { get; set; }
  18:     public string StackTrace { get; set; }
  19:     public string Type { get; set; }
  20: }

當客戶端接收到回複的Json對象後,可以通過檢測其是否具有一個ExceptionType屬性(對於一個ExceptionDetail對象來說,該屬性不可能為Null)來判斷是否發生異常。作為演示我們對Action方法Index對應的View進行了如下的改動。

   1: @model LoginInfo
   2: <html>
   3:     <head>
   4:         <title>用戶登錄</title>
   5:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.6.2.js")"></script>
   1:  
   2:         <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")">
   1: </script>
   2:         <script type="text/javascript">
   3:             function login(data) {
   4:                 if (data.ExceptionType) {
   5:                     alert(data.Message);
   6:                 }
   7:                 else {
   8:                     alert("認證成功");
   9:                 }
  10:             }
  11:         
</script>
   6:     </head>
   7:     <body>
   8:         @{
   9:             AjaxOptions options = new  AjaxOptions{OnSuccess = "login"};
  10:          }
  11:         @using (Ajax.BeginForm(options))
  12:         { 
  13:             @Html.EditorForModel()
  14:             <input type="submit" value="登錄" />
  15:         }
  16:     </body>
  17: </html>

如上麵的代碼片斷所示,我們通過調用AjaxHelper的BuginForm生成了一個以Ajax形式提交的表單。表單成功提交(服務端因對拋出的異常進行處理而返回一個封裝異常的Json對象,對於提交表單的Ajax請求來說依然屬於成功提交)後會調用我們定義的回調函數login。在該JavaScript函數中,我們通過得到的對象是否具有一個ExceptionType屬性來判斷服務端是否拋出異常。如果拋出異常,在通過調用alert方法將錯誤消息顯示出來,否則顯示“認證成功”。我們再次運行我們的程序並分別輸入不合法的用戶名和密碼,相應的錯誤消息會以對話框的形式顯示出來,具體的顯示效果如下圖所示。

image

ASP.NET MVC集成EntLib實現“自動化”異常處理[實例篇]
ASP.NET MVC集成EntLib實現“自動化”異常處理[實現篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 17:04:04

  上一篇:go  ASP.NET MVC中的ActionFilter是如何執行的?
  下一篇:go  ASP.NET MVC集成EntLib實現“自動化”異常處理[實現篇]