143
技術社區[雲棲]
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