ASP.NET MVC基于異常處理的解決方案
EntLib的異常處理應用塊(Exception Handling Application Block)是一個不錯的異常處理框架,它使我們可以采用配置的方式來定義異常處理策略。而ASP.NET MVC是一個***可擴展開發框架,在這篇文章中我將通過它的擴展實現與EntLib的集成,并提供一個完整的解決異常處理解決方案。
目錄
一、基本異常處理策略
二、通過自定義Action處理異常
三、通過配置的Error View處理異常
四、自定義ActionInvoker:ExceptionActionInvoker
五、自定義Controller:BaseController
一、基本異常處理策略
我們首先來討論我們的解決方案具體采用的異常處理策略:
對于執行Controller的某個Action方法拋出的異常,我們會按照指定配置策略進行處理。我們可以采取日志記錄、異常替換和封裝這些常用的異常處理方式;
對于處理后的異常,如果異常處理策略規定需要將其拋出,則會自動重定向到與異常類型匹配的出錯頁面。我們會維護一個異常類型和Error View的匹配關系;
對于處理后的異常,如果異常處理策略規定不需要將其拋出,則會執行與當前Action操作相匹配的錯誤處理Action進行處理。異常處理Action方法默認采用“On{Action}Error”這樣的命名規則,而當前上下文會與異常處理操作方法的參數進行綁定。除次之外,我們會設置當前ModelState的錯誤信息;
如果用戶不曾定義相應的異常處理Action,依然采用“錯誤頁面重定向”方式進行異常處理。
二、通過自定義Action處理異常
為了讓讀者對上面介紹的異常處理頁面有一個深刻的理解,我們來進行一個實例演示。該實例用于模擬用戶登錄,我們定義了如下一個只包含用戶名和密碼兩個屬性的Model:LoginInfoModel。
- namespaceArtech.Mvc.ExceptionHandling.Models
- {
- publicclassLoginInfo
- {
- [Display(Name ="User Name")]
- [Required(ErrorMessage = "User Name is manadatory!")]
- publicstringUserName { get; set; }
- [Display(Name = "Password")]
- [DataType(DataType.Password)]
- [Required(ErrorMessage = "Password is manadatory!")]
- publicstringPassword { get; set; }
- }
- }
我們定義了如下一個AccountController,它是我們自定義的BaseController的子類。AccountController在構造的時候調用基類構造函數指定的參數代表異常處理策略的配置名稱。SignIn方法代表用于進行“登錄”的操作,而OnSignInError就表示該操作對應的異常處理操作。如果在SignIn操作中拋出的異常經過處理后無需再拋出,則會通過調用OnSignInError,而此時ModelState已經被設置了相應的錯誤消息。
- publicclassAccountController : BaseController
- {
- publicAccountController()
- base("myPolicy")5:{ }
- publicActionResult SignIn()
- {
- returnView(newLoginInfo());
- }
- [HttpPost]
- publicActionResult SignIn(LoginInfo loginInfo)
- {
- if(!ModelState.IsValid)
- {
- returnthis.View(newLoginInfo { UserName = loginInfo.UserName });
- }
- if(loginInfo.UserName != "Foo")
- {
- thrownewInvalidUserNameException();
- }
- if(loginInfo.Password != "password")
- {
- thrownewUserNamePasswordNotMatchException();
- }
- ViewBag.Message = "Authentication Succeeds!";
- returnthis.View(newLoginInfo { UserName = loginInfo.UserName });
- }
- publicActionResult OnSignInError(stringuserName)
- {
- returnthis.View(newLoginInfo { UserName = userName });
- }
- }
具體定義在SignIn操作方法中的認證邏輯是這樣的:如果用戶名不是“Foo”則拋出InvalidUserNameException異常;如果密碼不是“password”則拋出UserNamePasswordNotMatchException異常。下面是SignIn操作對應的View的定義:
- @model Artech.Mvc.ExceptionHandling.Models.LoginInfo
- @{
- ViewBag.Title = "SignIn";
- }
- @Html.ValidationSummary()
- @if (ViewBag.Messages != null)
- {
- @ViewBag.Messages
- }
- @using (Html.BeginForm())
- {
- @Html.EditorForModel()<inputtype="submit"value="SignIn"/>
- }
在AccountController初始化時指定的異常處理策略“myPolicy”定義在如下的配置中。我們專門針對SignIn操作方法拋出的InvalidUserNameException和UserNamePasswordNotMatchException進行了處理,而ErrorMessageSettingHandler是我們自定義的異常處理器,它僅僅用于設置錯誤消息。如下面的代碼片斷所示,如果上述的這兩種類型的異常被拋出,最終的錯誤消息會被指定為“User name does not exist!”和“User name does not match password!”。
- <exceptionHandling>
- <exceptionPolicies>
- <addnameaddname="myPolicy">
- <exceptionTypes>
- <addnameaddname="InvalidUserNameException"
- type="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
- postHandlingAction="None">
- <exceptionHandlers>
- <addnameaddname="ErrorMessageSettingHandler"
- type="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling"
- errorMessage="User name does not exist!"/>
- </exceptionHandlers>
- </add>
- <addnameaddname="UserNamePasswordNotMatchException"
- type="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"
- postHandlingAction="None">
- <exceptionHandlers>
- <addnameaddname="ErrorMessageSettingHandler"
- type="Artech.Mvc.ExceptionHandling.ErrorMessageSettingHandler, Artech.Mvc.ExceptionHandling"
- errorMessage="User name does not match password!"/>
- </exceptionHandlers>
- </add>
- </exceptionTypes>
- </add>
- </exceptionPolicies>
- </exceptionHandling>
現在我們通過路由映射將AccountController和Sign設置為默認Controller和Action后,開啟我們的應用程序。在輸入錯誤的用戶名和錯誤明碼的情況下在ValidationSummary中將自動得到相應的錯誤消息。
三、通過配置的Error View處理異常
在上面的配置中,針對InvalidUserNameException和UserNamePasswordNotMatchException這兩種異常類型的配置策略都將PostHandlingAction屬性設置為“None”,意味著不會將原來的異常和處理后的異常進行重新拋出。現在我們將該屬性設置為“ThrowNewException”,意味著我們會將處理后的異常重新拋出來。
- <exceptionHandling>
- <exceptionPolicies>
- <addnameaddname="myPolicy">
- <exceptionTypes>
- <addnameaddname="InvalidUserNameException"type="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
- postHandlingAction="ThrowNewException">
- ...
- <addnameaddname="UserNamePasswordNotMatchException"type="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"9:postHandlingAction="ThrowNewException">
- ...
- </add>
- </exceptionTypes>
- </add>
- </exceptionPolicies>
- </exceptionHandling>
按照我們上面的異常處理策略,在這種情況下我們將采用“錯誤頁面”的方式來進行異常處理。也HandleErrorAttribute的處理方式類似,我們支持異常類型和Error View之間的匹配關系,而這是通過類似于如下的配置來定義的。值得一提的是,這里的異常類型是經過處理后重新拋出的異常
- <exceptionHandling>
- <exceptionPolicies>
- <addnameaddname="myPolicy">
- <exceptionTypes>
- <addnameaddname="InvalidUserNameException"type="Artech.Mvc.ExceptionHandling.Models.InvalidUserNameException, Artech.Mvc.ExceptionHandling"
- postHandlingAction="ThrowNewException">
- ...
- <addnameaddname="UserNamePasswordNotMatchException"type="Artech.Mvc.ExceptionHandling.Models.UserNamePasswordNotMatchException, Artech.Mvc.ExceptionHandling"9:postHandlingAction="ThrowNewException">
- ...
- </add>
- </exceptionTypes>
- </add>
- </exceptionPolicies>
- </exceptionHandling>
如上面的配置所示,我們為InvalidUserNameException和UserNamePasswordNotMatchException這兩種異常類型定義了不同的Error View,分別是“InvalideUserNameError”和“UserNamePasswordNotMatchError”,詳細定義如下所示:
- @{
- Layout = null;
- }
- <!DOCTYPEhtml>
- <html>
- <head>
- <title>Error</title>
- </head>
- <body>
- <pstylepstyle="color:Red; font-weight:bold">Sorry,the user name you specify does not exist!</p>
- </body>
- </html>
- @{
- Layout = null;
- }
- <!DOCTYPEhtml>
- <html>
- <head>
- <title>Error</title>
- </head>
- <body>
- <pstylepstyle="color:Red; font-weight:bold">Sorry, The password does not match the given user name!</p>
- </body>
- </html>
現在我們按照上面的方式運行我們的程序,在分別輸入錯誤的用戶名和密碼的情況下會自動顯現相應的錯誤頁面。
四、自定義ActionInvoker:ExceptionActionInvoker
對于上述的兩種不同的異常處理方式最終是通過自定義的ActionInvoker來實現的,我們將其命名為ExceptionActionInvoker。如下面的代碼片斷所式,ExceptionActionInvoker直接繼承自ControllerActionInvoker。屬性ExceptionPolicy是一個基于指定的異常策略名稱創建的ExceptionPolicyImpl 對象,用于針對EntLib進行的異常處理。而屬性GetErrorView是一個用于獲得作為錯誤頁面的ViewResult對象的委托。整個異常處理的核心定義在InvokeAction方法中,該方法中指定的handleErrorActionName參數代表的是“異常處理操作名稱”,整個方法就是按照上述的異常處理策略實現的。
- usingSystem;
- usingSystem.Collections.Generic;
- usingSystem.Linq;
- usingSystem.Web;
- usingSystem.Web.Mvc;
- usingArtech.Mvc.ExceptionHandling.Configuration;
- usingMicrosoft.Practices.EnterpriseLibrary.Common.Configuration;
- usingMicrosoft.Practices.EnterpriseLibrary.ExceptionHandling;
- namespaceArtech.Mvc.ExceptionHandling
- {
- publicclassExceptionActionInvoker: ControllerActionInvoker
- {
- protectedExceptionHandlingSettings ExceptionHandlingSettings{get; privateset;}
- protectedvirtualFunc<string, HandleErrorInfo, ViewResult> GetErrorView { get; privateset; }
- publicExceptionPolicyImpl ExceptionPolicy { get; privateset; }
- publicExceptionActionInvoker(stringexceptionPolicy,Func<string, HandleErrorInfo, ViewResult> getErrorView)
- {
- this.ExceptionPolicy = EnterpriseLibraryContainer.Current.GetInstance<ExceptionPolicyImpl>(exceptionPolicy);
- this.GetErrorView = getErrorView;
- this.ExceptionHandlingSettings = ExceptionHandlingSettings.GetSection();
- }
- publicoverrideboolInvokeAction(ControllerContext controllerContext, stringhandleErrorActionName)
- {
- ExceptionContext exceptionContext = controllerContext asExceptionContext;
- if(null== exceptionContext)
- {
- thrownewArgumentException("The controllerContext must be ExceptionContext!", "controllerContext");
- }
- try
- {
- exceptionContext.ExceptionHandled = true;
- if(this.ExceptionPolicy.HandleException(exceptionContext.Exception))
- {
- HandleRethrownException(exceptionContext);
- }
- else
- {
- if(ExceptionHandlingContext.Current.Errors.Count == 0)
- {
- ExceptionHandlingContext.Current.Errors.Add(exceptionContext.Exception.Message);
- }
- ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(exceptionContext);
- ActionDescriptor handleErrorAction = FindAction(exceptionContext, controllerDescriptor, handleErrorActionName);
- if(null!= handleErrorAction)
- {
- IDictionary<string, object>parameters = GetParameterValues(controllerContext, handleErrorAction);
- exceptionContext.Result = this.InvokeActionMethod(exceptionContext, handleErrorAction, parameters);
- }
- else
- {
- HandleRethrownException(exceptionContext);
- }
- }
- returntrue;
- }
- catch(Exception ex)
- {
- exceptionContext.Exception = ex;60:HandleRethrownException(exceptionContext);
- returntrue;
- }
- }
- protectedvirtualvoidHandleRethrownException(ExceptionContext exceptionContext)
- {
- stringerrorViewName = this.GetErrorViewName(exceptionContext.Exception.GetType());
- stringcontrollerName = (string)exceptionContext.RouteData.GetRequiredString("controller");
- stringaction = (string)exceptionContext.RouteData.GetRequiredString("action");
- HandleErrorInfo handleErrorInfo = newHandleErrorInfo(exceptionContext.Exception, controllerName, action);70:exceptionContext.Result = this.GetErrorView(errorViewName, handleErrorInfo);
- }
- protectedstringGetErrorViewName(Type exceptionType)
- {
- ExceptionErrorViewElement element = ExceptionHandlingSettings.ExceptionErrorViews75:.Cast<ExceptionErrorViewElement>().FirstOrDefault(el=>el.ExceptionType == exceptionType);
- if(null!= element)
- {
- returnelement.ErrorView;
- }
- if(null== element &&null!= exceptionType.BaseType!= null)
- {
- returnGetErrorViewName(exceptionType.BaseType);
- }
- else
- {
- return"Error";
- }
- }
- }
- }
五、自定義Controller:BaseController
ExceptionActionInvoker最終在我們自定義的Controller基類BaseController中被調用的。ExceptionActionInvoker對象在構造函數中被初始化,并在重寫的OnException方法中被調用。
- usingSystem;
- usingSystem.Web.Mvc;
- namespaceArtech.Mvc.ExceptionHandling
- {
- publicabstractclassBaseController : Controller
- {
- publicBaseController(stringexceptionPolicy)
- {
- Func<string, HandleErrorInfo, ViewResult> getErrorView = (viewName, handleErrorInfo) => this.View(viewName, handleErrorInfo);
- this.ExceptionActionInvoker = newExceptionActionInvoker(exceptionPolicy,getErrorView);
- }
- publicBaseController(ExceptionActionInvoker actionInvoker)
- {
- this.ExceptionActionInvoker = actionInvoker;
- }
- publicvirtualExceptionActionInvoker ExceptionActionInvoker { get; privateset; }
- protectedvirtualstringGetHandleErrorActionName(stringactionName)
- {
- returnstring.Format("On{0}Error", actionName);
- }
- protectedoverridevoidOnException(ExceptionContext filterContext)
- {
- using(ExceptionHandlingContextScope contextScope = newExceptionHandlingContextScope(filterContext))
- {
- stringactionName = RouteData.GetRequiredString("action");
- stringhandleErrorActionName = this.GetHandleErrorActionName(actionName);
- this.ExceptionActionInvoker.InvokeAction(filterContext, handleErrorActionName);
- foreach(var error inExceptionHandlingContext.Current.Errors)
- {
- ModelState.AddModelError(Guid.NewGuid().ToString() ,error.ErrorMessage);
- }
- }
- }
- }
- }
值得一提的是:整個OnException方法中的操作都在一個ExceptionHandlingContextScope中進行的。顧名思義, 我們通過ExceptionHandlingContextScope為ExceptionHandlingContext創建了一個范圍。ExceptionHandlingContext定義如下,我們可以通過它獲得當前的ExceptionContext和ModelErrorCollection,而靜態屬性Current返回當前的ExceptionHandlingContext對象。
- publicclassExceptionHandlingContext
- {
- [ThreadStatic]
- privatestaticExceptionHandlingContext current;
- publicExceptionContext ExceptionContext { get; privateset; }
- publicModelErrorCollection Errors { get; privateset; }
- publicExceptionHandlingContext(ExceptionContext exceptionContext)
- {
- this.ExceptionContext = exceptionContext;
- this.Errors = newModelErrorCollection();
- }
- publicstaticExceptionHandlingContext Current
- {
- get { returncurrent; }
- set { current = value;}
- }
- }
在BaseController的OnException方法中,當執行了ExceptionActionInvoker的InvokeAction之后,我們會將當前ExceptionHandlingContext的ModelError轉移到當前的ModelState中。這就是為什么我們會通過ValidationSummary顯示錯誤信息的原因。對于我們的例子來說,錯誤消息的指定是通過如下所示的ErrorMessageSettingHandler 實現的,而它僅僅將指定的錯誤消息添加到當前ExceptionHandlingContext的Errors屬性集合中而已。
- [ConfigurationElementType(typeof(ErrorMessageSettingHandlerData))]
- publicclassErrorMessageSettingHandler : IExceptionHandler
- {
- publicstringErrorMessage { get; privateset; }
- publicErrorMessageSettingHandler(stringerrorMessage)
- {
- this.ErrorMessage = errorMessage;
- }
- publicException HandleException(Exception exception, Guid handlingInstanceId)
- {
- if(null== ExceptionHandlingContext.Current)
- {
- thrownewInvalidOperationException("...");
- }
- if(string.IsNullOrEmpty(this.ErrorMessage))
- {
- ExceptionHandlingContext.Current.Errors.Add(exception.Message);
- }
- else
- {
- ExceptionHandlingContext.Current.Errors.Add(this.ErrorMessage);
- }
- returnexception;
- }
- }
原文鏈接:http://www.cnblogs.com/artech/archive/2012/01/10/exception-handling-in-asp-mvc.html
【編輯推薦】