標籤:還需要 protect 異常處理 自訂 filters inner ble strong div
MVC過濾器
- IAuthorizationFilter->OnAuthorization(授權)
- IActionFilter ->OnActionExecuting(行為)
- Action
- IActionFilter ->OnActionExecuted(行為)
- IResultFilter ->OnResultExecuting(結果)
- View
- IResultFilter ->OnResultExecuted(結果)
- *IExceptionFilter ->OnException(異常),此方法並不在以上的順序執行中,有異常發生時即會執行,有點類似於中斷
- 當同時在Controller和Action中都設定了過濾器後,執行順序一般是由外到裡,即“全域”->“控制器”->“行為”
- Controller->IAuthorizationFilter->OnAuthorization
- Action ->IAuthorizationFilter->OnAuthorization
- Controller->IActionFilter ->OnActionExecuting
- Action ->IActionFilter ->OnActionExecuting
- Action
- Action ->IActionFilter ->OnActionExecuted
- Controller->IActionFilter ->OnActionExecuted
- Controller->IResultFilter ->OnResultExecuting
- Action ->IResultFilter ->OnActionExecuting
- Action ->IResultFilter ->OnActionExecuted
- Controller->IResultFilter ->OnActionExecuted
- 因為異常是從裡往外拋,因次異常的處理順序則剛好相反,一般是由裡到外,即“行為”->“控制器”->“全域”
- Action ->IExceptionFilter->OnException
- Controller->IExceptionFilter->OnException
系統內建的異常處理
我們習慣使用的過濾器,要麼是為Action加上Attribute,要麼就是為Controller加上Attribute。上面所說的全域過濾器是怎麼回事呢?先看看Gloabal裡的代碼:
protected void Application_Start(){
//註冊Area AreaRegistration.RegisterAllAreas();
//註冊過濾器 RegisterGlobalFilters(GlobalFilters.Filters);
//註冊路由 RegisterRoutes(RouteTable.Routes);
}
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
由上可知,在應用程式啟動的時候就已經註冊了全域過濾器,HandleErrorAttribute就是系統內建的異常過濾器。在這註冊的全域過濾器,可以不用到每個Controller或者是每個Action去聲明,直接作用於全域了,即可以捕捉整個網站的所有異常。看看它的源碼是怎麼處理異常的:
public virtual void OnException(ExceptionContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled)) { Exception innerException = filterContext.Exception; if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException)) { string controllerName = (string) filterContext.RouteData.Values["controller"]; string actionName = (string) filterContext.RouteData.Values["action"]; HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); ViewResult result = new ViewResult { ViewName = this.View, MasterName = this.Master, ViewData = new ViewDataDictionary<HandleErrorInfo>(model), TempData = filterContext.Controller.TempData }; filterContext.Result = result; filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); filterContext.HttpContext.Response.StatusCode = 500; filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; } } }
HandleErrorAttribute的異常處理邏輯裡,產生了一個HandleErrorInfo類的Model,並設定返回的結果為一個新產生的ViewResult。這個視圖預設的ViewName是Error,對應於Share檔案夾裡的Error視圖。而內建的Error視圖沒有用到HandleErrorInfo的Model,因此公開的資訊也不是很多,可以根據具體的需求改造一下。例如:
@model HandleErrorInfo<br /><div class="container"> <div class="alert alert-error"> <h4> Exception:</h4> <br /> <p> There was a <b>@Model.Exception.GetType().Name</b> while rendering <b>@Model.ControllerName</b>‘s<b>@Model.ActionName</b> action.</p> <p> @Model.Exception.Message </p> </div> <div class="alert"> <h4> Stack trace:</h4> <br /> <pre>@Model.Exception.StackTrace</pre> </div> </div>
這個過濾器要能起效,還需要在設定檔中配置一下:<customErrors mode="On" />
自訂的異常統一處理
在實現異常的統一處理之前,先來明確一下需求:
- 網站所有頁面在異常發生後,均需要記錄異常日誌,並轉向錯誤提示頁面(異常內容的詳略程度由具體需求決定)
- 所有返回JSON資料的非同步請求,不但需要記錄異常日誌,而且需要向用戶端返回JSON格式的錯誤資訊提示,而不是轉向錯誤提示頁面(非同步請求也不可能轉向錯誤提示頁面)
- 採用AOP思想,將異常處理解耦
- 盡量精簡聲明Attribute的重複代碼
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class JsonExceptionAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled) { //返回異常JSON filterContext.Result = new JsonResult { Data = new { Success = false, Message = filterContext.Exception.Message } }; } } }
說明:需要注意的是,不需要調用base.OnException,否則會跳過LogExceptionAttribute先執行HandleErrorAttribute的處理邏輯,從而返回結果不再是JsonResult,而是ViewResult,用戶端也就無法處理非JSON的結果了。
這裡也不需要設定filterContext.ExceptionHandled = true,否則在LogExceptionAttribute處理時,因為 !filterContext.ExceptionHandled 的判斷條件,LogExceptionAttribute的邏輯不會執行,也就不會記錄異常日誌了。
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] public class LogExceptionAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled) { string controllerName = (string)filterContext.RouteData.Values["controller"]; string actionName = (string)filterContext.RouteData.Values["action"]; string msgTemplate = "在執行 controller[{0}] 的 action[{1}] 時產生異常"; LogManager.GetLogger("LogExceptionAttribute").Error(string.Format(msgTemplate, controllerName, actionName), filterContext.Exception); } if (filterContext.Result is JsonResult) { //當結果為json時,設定異常已處理 filterContext.ExceptionHandled = true; } else { //否則調用原始設定 base.OnException(filterContext); } } }
修改全域過濾器:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleError2LogAttribute());//全域的日誌異常過濾器 //filters.Add(new HandleErrorAttribute());}
調用樣本:
[HttpPost][JsonException]public JsonResult Add(string ip, int port){ ... //處理邏輯 return Json(new { Success = true, Message = "添加成功" });}
MVC異常過濾器