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


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

通過《實例篇》的實演示可以看出我們通過擴展實現的自動異常處理機製能夠利用EntLib的EHAB根據執行的一場處理策略對某個Action方法執行過程中拋出的異常進行處理。對於處理後的結果,則按照如下的機製對請求進行響應。[源代碼從這裏下載][本文已經同步到《How ASP.NET MVC Works?》中]

  • 對於Ajax請求,直接創建一個用於封裝被處理後異常的數據對象,並據此創建一個JsonResult將異常信息回複給客戶端。
  • 對於非Ajax請求,如果當前Action方法上應用HandleErrorActionAttribute特性設置了匹配的Action方法用於處理該方法拋出的異常,那麼執行該方法並用返回的ActionResult對象響應當前請求。
  • 如果HandleErrorActionAttribute特性不曾應用在當前Action方法上,或者通過該特性指定的Action不存在,則將默認的錯誤View呈現出來作為多請求的響應。

目錄
一、ExceptionPolicyAttribute & HandleErrorActionAttribute
二、實現在OnException方法中的異常處理邏輯
三、將處理後的錯誤消息存放在HttpContext的Items中
四、用於設置錯誤消息的ErrorMessageHandler

所有的這些都是通過一個自定義的ExceptionFilter來實現的。不過我們並沒有定義任何的ExceptionFilter特性,而是將異常處理實現在一個自定義的ExtendedController基類中,對異常的自動處理實現在重寫的OnException方法中,不過在介紹該方法的邏輯之前我們先來看看定義在ExtendedController中的其他輔助成員。

   1: public class ExtendedController: Controller
   2: {
   3:     private static Dictionary<Type, ControllerDescriptor> controllerDescriptors = new Dictionary<Type, ControllerDescriptor>();
   4:     private static object syncHelper = new object();
   5:  
   6:     protected override void OnException(ExceptionContext filterContext)
   7:     {
   8:         //省略成員
   9:     }      
  10:     
  11:     //描述當前Controller的ControllerDescriptor
  12:     public ControllerDescriptor Descriptor
  13:     {
  14:         get
  15:         {
  16:             ControllerDescriptor descriptor;
  17:             if(controllerDescriptors.TryGetValue(this.GetType(), out descriptor))
  18:             {
  19:                 return descriptor;
  20:             }
  21:             lock (syncHelper)
  22:             {
  23:                 if (controllerDescriptors.TryGetValue(this.GetType(), out descriptor))
  24:                 {
  25:                     return descriptor;
  26:                 }
  27:                 else
  28:                 {
  29:                     descriptor = new ReflectedControllerDescriptor(this.GetType());
  30:                     controllerDescriptors.Add(this.GetType(), descriptor);
  31:                     return descriptor;
  32:                 }
  33:             }
  34:         }
  35:     }
  36:     //獲取異常處理策略名稱
  37:     public string GetExceptionPolicyName()
  38:     {
  39:         string actionName = ControllerContext.RouteData.GetRequiredString("action");
  40:         ActionDescriptor actionDescriptor = this.Descriptor.FindAction(ControllerContext, actionName);
  41:         if (null == actionDescriptor)
  42:         {
  43:             return string.Empty;
  44:         }
  45:         ExceptionPolicyAttribute exceptionPolicyAttribute = actionDescriptor.GetCustomAttributes(true).OfType<ExceptionPolicyAttribute>().FirstOrDefault()??               
  46:            Descriptor.GetCustomAttributes(true).OfType<ExceptionPolicyAttribute>().FirstOrDefault()?? new ExceptionPolicyAttribute("");
  47:         return exceptionPolicyAttribute.ExceptionPolicyName;
  48:     }
  49:  
  50:     //獲取Handle-Error-Action名稱
  51:     public string GetHandleErrorActionName()
  52:     {
  53:         string actionName = ControllerContext.RouteData.GetRequiredString("action");
  54:         ActionDescriptor actionDescriptor = this.Descriptor.FindAction(ControllerContext, actionName);
  55:         if (null == actionDescriptor)
  56:         {
  57:             return string.Empty;
  58:         }
  59:         HandleErrorActionAttribute handleErrorActionAttribute = actionDescriptor.GetCustomAttributes(true).OfType<HandleErrorActionAttribute>().FirstOrDefault()??          
  60:             Descriptor.GetCustomAttributes(true).OfType<HandleErrorActionAttribute>().FirstOrDefault()?? new HandleErrorActionAttribute("");
  61:         return handleErrorActionAttribute.HandleErrorAction;
  62:     }
  63:  
  64:     //用於執行Handle-Error-Action的ActionInvoker
  65:     public HandleErrorActionInvoker HandleErrorActionInvoker { get; private set; }
  66:  
  67:     public ExtendedController()
  68:     {
  69:         this.HandleErrorActionInvoker = new HandleErrorActionInvoker();
  70:     }
  71: }

ExtendedController的Descriptor屬性用於返回描述自身的ControllerDescriptor對象,實際上是一個ReflectedControllerDescriptor對象。為了避免頻繁的反射操作造成對性能的影響,我們將基於某個類型解析出來的ReflectedControllerDescriptor對象進行了全局性緩存。

GetExceptionPolicyName方法用於返回當前采用的異常處理策略名稱。異常處理策略名稱是通過具有如下定義的ExceptionPolicyAttribute特性來指定的。該特性既可以應用在Controller類型上,也可以應用在Action方法上,換句話說,我們可以采用不同的策略來處理從不同Action執行過程中拋出的異常。GetExceptionPolicyName利用ControllerDesctior和ActionDescriptor可以很容易地得到應用的ExceptionPolicyAttribute特性,進而得到相應的異常處理策略名稱。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
   2: public class ExceptionPolicyAttribute: Attribute
   3: {
   4:     public string ExceptionPolicyName { get; private set; }
   5:     public ExceptionPolicyAttribute(string exceptionPolicyName)
   6:     {
   7:         this.ExceptionPolicyName = exceptionPolicyName;
   8:     }
   9: }

另一個方法GetHandleErrorActionName用於獲取通過應用在Action方法上的特性HandleErrorActionAttribute設置的Handle-Error-Action的名稱。該特性定義如下,它既可以應用於某個Action方法,也可以應用於Controller類。GetHandleErrorActionName方法同樣利用ControllerDesctior和ActionDescriptor得到應用的ExceptionPolicyAttribute特性,並最終得到對應的異常處理Action名稱。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false)]
   2: public class HandleErrorActionAttribute: Attribute
   3: {
   4:     public string HandleErrorAction { get; private set; }
   5:     public HandleErrorActionAttribute(string handleErrorAction = "")
   6:     {
   7:         this.HandleErrorAction = handleErrorAction;
   8:     }
   9: }

通過HandleErrorActionAttribute特性設置的Handle-Error-Action需要手工執行以實現對當前請求的響應,為此我們創建了一個具有如下定義的HandleErrorActionInvoker。它是ControllerActionInvoker的子類,Handle-Error-Action需要手工執行以實現對當前請求的響應的執行通過虛方法InvokeActionMethod來完成。ExtendedController的HandleErrorActionInvoker返回的就是這樣一個對象。

   1: public class HandleErrorActionInvoker: ControllerActionInvoker
   2: {
   3:     public virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
   4:     {
   5:         IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);
   6:         return base.InvokeActionMethod(controllerContext, actionDescriptor, parameterValues);
   7:     }
   8: }

整個異常處理和最終對請求的相應實現在如下所示的OnException方法中,流程並不複雜,在這裏就不一一贅述了。不過對於整個處理流程,有兩個點值得一提:其一,在調用EntLib的EHAB對異常處理過程中,允許相應的ExceptionHandler設置一個友好的錯誤消息,而這個消息被保存在當前HttpContext的Items中。其二,在調用異常處理方法之前,我們錯誤消息添加到當前的ModelState中,這也是為什麼在上麵的實例演示中錯誤消息會自動出現在ValidationSummary中的根本原因。

   1: public class ExtendedController: Controller
   2: {    
   3:     //其他成員
   4:     protected override void OnException(ExceptionContext filterContext)
   5:     {
   6:         //或者當前的ExceptionPolicy,如果不存在,則直接調用基類OnException方法
   7:         string exceptionPolicyName = this.GetExceptionPolicyName();
   8:         if (string.IsNullOrEmpty(exceptionPolicyName))
   9:         {
  10:             base.OnException(filterContext);
  11:             return;
  12:         }
  13:  
  14:         //利用EntLib的EHAB進行異常處理,並獲取錯誤消息和最後拋出的異常
  15:         filterContext.ExceptionHandled = true;
  16:         Exception exceptionToThrow;
  17:         string errorMessage;
  18:         try
  19:         {
  20:             ExceptionPolicy.HandleException(filterContext.Exception,exceptionPolicyName, out exceptionToThrow);
  21:             errorMessage = System.Web.HttpContext.Current.GetErrorMessage();
  22:         }
  23:         finally
  24:         {
  25:             System.Web.HttpContext.Current.ClearErrorMessage();
  26:         }
  27:         exceptionToThrow = exceptionToThrow ?? filterContext.Exception;
  28:  
  29:         //對於Ajax請求,直接返回一個用於封裝異常的JsonResult
  30:         if (Request.IsAjaxRequest())
  31:         {
  32:             filterContext.Result = Json(new ExceptionDetail(exceptionToThrow, errorMessage));
  33:             return;
  34:         }
  35:  
  36:         //如果設置了匹配的HandleErrorAction,則調用之;
  37:         //否則將Error View呈現出來
  38:         string handleErrorAction = this.GetHandleErrorActionName();
  39:         string controllerName = ControllerContext.RouteData.GetRequiredString("controller");
  40:         string actionName = ControllerContext.RouteData.GetRequiredString("action");
  41:         errorMessage = string.IsNullOrEmpty(errorMessage) ? exceptionToThrow.Message : errorMessage;
  42:         if (string.IsNullOrEmpty(handleErrorAction))
  43:         {
  44:             filterContext.Result = View("Error", new ExtendedHandleErrorInfo(exceptionToThrow, controllerName, actionName, errorMessage));
  45:         }
  46:         else
  47:         {
  48:             ActionDescriptor actionDescriptor = Descriptor.FindAction(ControllerContext, handleErrorAction);                
  49:             ModelState.AddModelError("", errorMessage);
  50:             filterContext.Result = this.HandleErrorActionInvoker.InvokeActionMethod(ControllerContext, actionDescriptor);
  51:         }
  52:     }      
  53: }

在調用EntLib的EHAB進行異常處理之後從當前HttpContext提取錯誤消息,以及最後清除消息分別是通過HttpContext的擴展方法GetErrorMessage和ClearErrorMessage實現的。如下麵的代碼片斷所示,除了這兩個擴展方法我們還定義了另一個用於設置錯誤消息的SetErrorMessage方法。

   1: public static class HttpContextExtensions
   2: {
   3:     public static string keyOfErrorMessage = Guid.NewGuid().ToString();
   4:  
   5:     public static void SetErrorMessage(this HttpContext context, string errorMessage)
   6:     {
   7:         context.Items[keyOfErrorMessage]=errorMessage;
   8:     }
   9:  
  10:     public static string GetErrorMessage(this HttpContext context)
  11:     {
  12:         return context.Items[keyOfErrorMessage] as string;
  13:     }
  14:  
  15:     public static void ClearErrorMessage(this HttpContext context)
  16:     {
  17:         if (context.Items.Contains(keyOfErrorMessage))
  18:         {
  19:             context.Items.Remove(keyOfErrorMessage);
  20:         }            
  21:     }
  22: }

用於設置錯誤信息的ErrorMessageHandler以及對應配置元素類型ErrorMessageHandlerData定義如下。ErrorMessageHandler表示錯誤消息的ErrorMessage屬性在構造函數中被初始化,而在實現的HandleException方法中直接通過調用當前HttpContext的擴展方法SetErrorMessage進行錯誤消息的設置。

   1: [ConfigurationElementType(typeof(ErrorMessageHandlerData))]
   2: public class ErrorMessageHandler: IExceptionHandler
   3: {
   4:     public string ErrorMessage { get; private set; }
   5:     public ErrorMessageHandler(string errorMessage)
   6:     {
   7:         this.ErrorMessage = errorMessage;
   8:     }
   9:     public Exception HandleException(Exception exception, Guid handlingInstanceId)
  10:     {
  11:         if (null != HttpContext.Current)
  12:         {
  13:             HttpContext.Current.SetErrorMessage(this.ErrorMessage);
  14:         }
  15:         return exception;
  16:     }
  17: }
  18:  
  19: public class ErrorMessageHandlerData : ExceptionHandlerData
  20: {
  21:     [ConfigurationProperty("errorMessage", IsRequired=true)]
  22:     public string ErrorMessage
  23:     {
  24:         get { return (string)this["errorMessage"]; }
  25:         set { this["errorMessage"] = value; }
  26:     }
  27:  
  28:     public override IEnumerable<TypeRegistration> GetRegistrations(string namePrefix)
  29:     {
  30:         yield return new TypeRegistration<IExceptionHandler>(() => new ErrorMessageHandler(this.ErrorMessage))
  31:         {
  32:             Name = this.BuildName(namePrefix),
  33:             Lifetime = TypeRegistrationLifetime.Transient
  34:         };
  35:     }
  36: }

 

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


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

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

  上一篇:go  ASP.NET MVC集成EntLib實現“自動化”異常處理[實例篇]
  下一篇:go  了解ASP.NET MVC幾種ActionResult的本質:EmptyResult & ContentResult