一、應用場景 對於B/S應用程式,在部署到正式環境運行的過程中,很有可能出現一些在前期測試過程中沒有發現的一些異常或者錯誤,或者說只有在特定條件滿足時才會發生的一些異常,對於使用ASP.NET MVC開發的應用程式站點,在部署到IIS上後,如果開發人員未對程式進行錯誤處理,那麼一旦程式出現未處理的錯 ...
一、應用場景
對於B/S應用程式,在部署到正式環境運行的過程中,很有可能出現一些在前期測試過程中沒有發現的一些異常或者錯誤,或者說只有在特定條件滿足時才會發生的一些異常,對於使用ASP.NET MVC開發的應用程式站點,在部署到IIS上後,如果開發人員未對程式進行錯誤處理,那麼一旦程式出現未處理的錯誤或異常,用戶將看到一個讓人感到及其困惑的錯誤堆棧跟蹤頁面,使得站點的用戶體驗下降,從程式的角度上來說,不做自定義錯誤處理也不利於程式出問題時的根源查找,因為很多時候有些錯誤只在特定條件下滿足時才重現,一旦錯過,可能就需要花大量時間去測試來重現問題,如果此時開發人員有對程式中的運行時異常進行日誌記錄,那麼或許將提供一些有價值的錯誤根源信息,下麵我將向下大家講解如何實現自定義異常處理並跳轉到友好的錯誤提示頁面。
二、異常處理&自定義錯誤頁
1、通過異常過濾器 實現異常處理和自定義錯誤頁
asp.net mvc 提供了 異常過濾器 的方式來實現當執行controller中某個action方法時拋出了未處理的異常時的捕捉,mvc中的異常過濾器是以特性(Attribute)的形式存在的,定義一個自定義異常過濾器只需要兩個步驟:
1、定義一個類,繼承FilterAttribute類,並實現IExceptionFilter介面 2、應用自定義異常過濾器至指定的 action方法 或 controller類 或 全局應用。
異常過濾器代碼
1 using log4net; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace Blog20180413.Filters 9 { 10 public class CustomExceptionFilterAttribute : FilterAttribute, IExceptionFilter 11 { 12 //log4net組件,用於日誌記錄。 13 static readonly ILog log = LogManager.GetLogger(typeof(CustomExceptionFilterAttribute)); 14 public void OnException(ExceptionContext filterContext) 15 { 16 //對捕獲到的異常信息進行日誌記錄,方便開發人員排查問題。 17 log.Error("應用程式異常", filterContext.Exception); 18 19 //跳轉到自定義的錯誤頁,增強用戶體驗。 20 ActionResult result = new ViewResult() { ViewName = "CustomErrorPage" }; 21 filterContext.Result = result; 22 //異常處理結束後,一定要將ExceptionHandled設置為true,否則仍然會繼續拋出錯誤。 23 filterContext.ExceptionHandled = true; 24 } 25 } 26 }View Code
使用異常過濾器
1 using Blog20180413.Filters; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace Blog20180413.Controllers 9 { 10 public class TestExceptionHandleController : Controller 11 { 12 [CustomExceptionFilter] 13 public ActionResult Index() 14 { 15 string str = string.Empty; 16 //將拋出轉換異常 17 int result = int.Parse(str); 18 return View(); 19 } 20 } 21 }View Code
註意:
第二個步驟中提到,可以將自定義異常過濾器 只應用到 action或者controller,如果只想將指定的異常過濾器以特性的形式應用到指定的一個或者多個controller或者action,而不想應用到所有的controller或者action,那麼必須將該異常過濾器繼承FilterAttribute類,這是因為mvc框架是通過FilterAttributeFilterProvider.GetFilters來獲取標記在指定controller或者action上的異常過濾器特性的,而GetFilters內部邏輯要求必須繼承自FilterAttribute類。
如果需要將自定義的異常過濾器應用到所有的controller的action上,那麼需要將該自定義異常過濾器註冊到全局,代碼如下:
1 using Blog20180413.Filters; 2 using System.Web; 3 using System.Web.Mvc; 4 5 namespace Blog20180413 6 { 7 public class FilterConfig 8 { 9 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 10 { 11 filters.Add(new CustomExceptionFilterAttribute()); 12 } 13 } 14 }
2、通過在Global.asax 中定義Application_Error方法 實現異常處理和自定義錯誤頁
上面提到的 自定義異常過濾器只能捕獲在執行action方法過程中拋出的異常(即使註冊為全局過濾器也只能捕獲action方法執行過程中拋出的異常),如果需要捕獲更高級別的異常,也就是在請求執行過程中出現的任何異常(如在控制器的構造函數中拋出異常),那麼可以使用該種方式,代碼如下:
1 using log4net; 2 using log4net.Config; 3 using System; 4 using System.Collections.Generic; 5 using System.IO; 6 using System.Linq; 7 using System.Web; 8 using System.Web.Mvc; 9 using System.Web.Routing; 10 11 namespace Blog20180413 12 { 13 public class MvcApplication : System.Web.HttpApplication 14 { 15 static readonly ILog log = LogManager.GetLogger(typeof(MvcApplication)); 16 protected void Application_Start() 17 { 18 AreaRegistration.RegisterAllAreas(); 19 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 20 RouteConfig.RegisterRoutes(RouteTable.Routes); 21 XmlConfigurator.ConfigureAndWatch(new FileInfo(Server.MapPath("~/web.config"))); 22 } 23 24 protected void Application_Error(object sender, EventArgs e) 25 { 26 Exception exception = Server.GetLastError(); 27 //Server.ClearError(); 28 //這裡記錄錯誤日誌信息 29 log.Error("MvcApplication 捕獲異常", exception); 30 //跳轉到指定的自定義錯誤頁 31 Response.Redirect("/CustomErrorHandle/CustomErrorPage"); 32 } 33 } 34 }View Code
3、通過配置system.web->customErrors節點 實現自定義錯誤頁
當你的站點發生異常時,如果你只是想簡單的跳轉到一個自定義錯誤頁面,而不是對異常進一步處理時,那麼你可以簡單的作如下配置操作即可:
需要在web.config中做如下配置:
1 <system.web> 2 <customErrors mode="On" defaultRedirect="CustomErrorPage"> 3 </customErrors> 4 </system.web>
註意:這裡的CustomErrorPage是一個視圖文件,放在Shared共用目錄下。
如果你註冊了HandleErrorAttribute異常過濾器到全局,那麼在你的錯誤頁中將能獲取到和異常相關的一些信息。但此時配置到defaultRedirect的值的必須是Error
也就是自定義錯誤視圖頁面的名稱必須為Error.cshtml,並且放在Shared目錄,當然,你也可以通過在創建HandleErrorAttribute全局過濾器的過程中,設置器View屬性,這樣你就可以不用講錯誤視圖的名稱設置為Error了.如下:
1 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 2 { 3 HandleErrorAttribute errorAttribute = new HandleErrorAttribute(); 4 errorAttribute.View = "CustomErrorPage"; 5 filters.Add(errorAttribute); 6 }
註冊HandleErrorAttribute(使用預設的錯誤視圖頁面文件名)
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } }
定義Error.cshtml視圖頁
1 @{ 2 Layout = null; 3 } 4 @model HandleErrorInfo 5 <!DOCTYPE html> 6 <html> 7 <head> 8 <meta name="viewport" content="width=device-width" /> 9 <title>Error</title> 10 </head> 11 <body> 12 <div> 13 @*通過HandleErrorAttribute異常過濾器捕獲到的異常信息存儲在Model屬性中*@ 14 @Model.Exception.Message 15 </div> 16 </body> 17 </html>View Code
之所以通過註冊HandleErrorAttribute過濾器捕獲的異常在錯誤頁中能獲取異常信息可以看HandleErrorAttribute類的內部實現,發現載入錯誤視圖頁面的過程中,傳遞了一個HandleErrorInfo對象過去。
1 public virtual void OnException(ExceptionContext filterContext) 2 { 3 if (filterContext == null) 4 { 5 throw new ArgumentNullException("filterContext"); 6 } 7 if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled)) 8 { 9 Exception innerException = filterContext.Exception; 10 if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException)) 11 { 12 string controllerName = (string) filterContext.RouteData.Values["controller"]; 13 string actionName = (string) filterContext.RouteData.Values["action"]; 14 HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); 15 ViewResult result = new ViewResult { 16 ViewName = this.View, 17 MasterName = this.Master, 18 ViewData = new ViewDataDictionary<HandleErrorInfo>(model), 19 TempData = filterContext.Controller.TempData 20 }; 21 filterContext.Result = result; 22 filterContext.ExceptionHandled = true; 23 filterContext.HttpContext.Response.Clear(); 24 filterContext.HttpContext.Response.StatusCode = 500; 25 filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; 26 } 27 } 28 }View Code
1 public string View 2 { 3 get 4 { 5 if (string.IsNullOrEmpty(this._view)) 6 { 7 return "Error"; 8 } 9 return this._view; 10 } 11 set => 12 (this._view = value); 13 } 14View Code
三、總結
總的來說,Application_Error方法用於處理針對請求管道級別的發生的異常錯誤,而Mvc異常過濾器則只能處理在執行action方法過程中出現的異常.能處理的範圍相對Application_Error較小,但在實際項目開發中,用Mvc異常過濾器處理異常相對會多一點,因為我們的功能業務往往體現在控制器的action方法執行的過程中,也就是在這個過程中較容易產生異常。故開發中用Mvc異常過濾器就能適應大部分的異常處理需求。