ASP.NET MVC 默認提供了一個異常過濾器HandleError特性,使用該特性可以極為方便的捕捉並處理控制器和操作拋出的異常,也可以將此特性注冊為全局異常過濾器從而捕捉項目中的所有異常。如果想要簡單的消滅錯誤黃頁(錯誤詳細頁),使用HandlerErrorAttribute是不錯的選擇!
本文演示項目下載地址:GlobalExceptionHandle-By-HandleErrorAttribute.zip,項目使用的VS2013和ASP.NET MVC 5框架,項目中也拷貝了HandleErrorAttribute的源碼以供參考。
HandleErrorAttribute初步使用
使用HandleErrorAttribute處理異常很簡單,首先要開啟Web.config配置文件中的自定義錯誤,因為HandleError特性是依賴自定義錯誤的,customErrors的Mode必須要設置為On或RemoteOnly:
<customErrors mode="On" defaultRedirect="~/Error/Index"></customErrors>
到了這里基本上就成功啟用了異常過濾器,因為VS2013新建的ASP.NET MVC 5項目默認將HandleError注冊為全局異常過濾器,只要項目中的控制器和操作方法有拋出異常,默認就會被HandleError特性捕獲,從而跳轉到默認的錯誤詳細頁面~/Views/Shared/Error.cshtml。打開項目下App_Start的FilterConfig類(全局過濾器配置類),可以看到已經被注冊的HandleErrorAttribute類:

public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //如果要更改默認的錯誤視圖,需要設置View屬性 //filters.Add(new HandleErrorAttribute() { View = "~/Views/Error/CustomHttpError.cshtml" }); } }
備注:關於FilterConfig類,如果不清楚的朋友可以看這篇文章:ASP.NET MVC 5 學習筆記之FilterConfig類
這里可以動態拋出一些異常來進行測試,在默認的Home控制器下復制如下代碼:
public class HomeController : Controller { public ActionResult Index() { return View(); } /// <summary> /// 拋出HTTP 500 /// </summary> /// <returns></returns> public ActionResult ThrowHttp500() { throw new HttpException(500, "服務器錯誤"); } /// <summary> /// 拋出HTTP 404 /// </summary> /// <returns></returns> public ActionResult ThrowHttp404() { throw new HttpException(404, "頁面未找到"); } /// <summary> /// 拋出未引用對象異常 /// ,此處單獨使用HandleError特性 /// ,並指定異常類型及響應視圖 /// </summary> /// <returns></returns> [HandleError(ExceptionType = typeof(NullReferenceException),View="CustomError")] public ActionResult ThrowNullReferenceException() { throw new NullReferenceException(); } /// <summary> /// 引發輸入字符串的格式不正確異常 /// ,此處指定了響應的錯誤頁面 /// ,由於是不同的控制器所以要完整的相對路徑 /// </summary> /// <returns></returns> [HandleError(View = "~/Views/Error/CustomHttpError.cshtml")] public ActionResult ThrowFormatException() { string str = ""; int count = Convert.ToInt32(str); return View("Index"); } }
備注:記得添加對應的錯誤視圖頁和控制器,建議直接下載演示項目。
首頁操作演示界面如下:

靈活運用HandleError特性
HandleError特性提供了許多屬性,我們可以更加靈活的處理項目中拋出的異常。文檔截圖:

一般來說最常用的屬性是View,Order和ExceptionType,至於AllowMultiple屬性,除非業務有特殊要求,不然默認都是允許的。
自定義錯誤信息頁面
先說View屬性,設置此屬性就可以自定義要跳轉的錯誤視圖頁,否則一旦有異常拋出,默認會跳轉到Error.cshtml這個頁面,該頁面路徑為:~/Views/Shared/Error.cshtml。
設置View屬性一定要注意路徑問題,如果跳轉的異常信息頁面屬於其他控制器,即控制器路由地址不同,那么一定要用完整的相對路徑,否則引發二次異常:未找到視圖**或其母版視圖,或沒有視圖引擎支持搜索的位置。,比如下面代碼(此聲明是在Home控制器中,但是要跳轉的錯誤頁面則是在Error控制器中):
[HandleError(View = "~/Views/Error/CustomHttpError.cshtml")]
如果跳轉的異常頁面是屬於當前控制器,或者是屬於公共視圖頁的,就可以直接設置視圖名稱,代碼如下:
[HandleError(View="CustomError")]
備注:CustomError是一個公共的自定義錯誤信息視圖頁面,另外公共視圖頁都是放在~/Views/Shared/這個路徑下。
如果不想使用默認的Error.cshtml,想將所有的異常響應跳轉到自定義的異常信息視圖頁,那么只要通過全局過濾器配置類FilterConfig,在注冊HandleErrorAttribute時設置View屬性即可,代碼如下:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute() { View = "~/Views/Error/CustomHttpError.cshtml" }); } }
同樣要注意視圖路徑問題,其實這里最好使用公共視圖頁。這里額外說下Master屬性,此屬性可以指定要跳轉的視圖頁的母板。基本上很少用,除非項目業務有需要,比如動態控制母板什么的,不然的話都是在視圖中直接設置好的。
獲取詳細的異常信息
HandleErrorInfo類是HandleErrorAttribute默認提供的錯誤信息實體

可以通過此類獲取引發異常的控制器和操作方法名稱,以及通過獲取Exception對象獲取詳細的錯誤內容。只要在對應的視圖頁中聲明此類即可,演示代碼:
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "自定義的公共錯誤頁面";
}
@section styles{
<style type="text/css"> p {padding: 10px;} </style> } <h2>自定義的公共錯誤頁面</h2> @if (Model != null) { <p class="bg-danger text-danger"> 異常類型:@Model.Exception.GetType().Name </p> <p class="bg-danger text-danger"> 觸發異常的控制器:@Model.ControllerName </p> <p class="bg-danger text-danger"> 觸發異常的操作方法:@Model.ActionName </p> <p class="bg-danger text-danger"> 錯誤信息:@Model.Exception.Message </p> <p class="bg-info text-info"> 頁面路徑:~/Views/Shared/CustomError.cshtml </p> }
演示的視圖截圖:

針對異常類型
ExceptionType屬性可以讓HandleError針對某種異常類型做出處理,可以搭配View屬性來使用,便於跳轉到不同的異常信息頁面,演示代碼:
[HandleError(ExceptionType = typeof(NullReferenceException),View="CustomError")]
上面代碼一旦捕獲到NullReferenceException異常,就會跳轉到CustomError視圖頁。注意一點,ExceptionType需要使用typeof轉化異常類型。
關於過濾器的執行順序
如果聲明了多個HandleError異常過濾器,就需要使用到Order屬性設置運行順序。Order屬性默認值為-1,也是最高優先級,正常來說整數值越大優先級越低,但是由於HandleError是繼承於IExceptionFilter接口,所以優先級順序是相反的,也就是說整數值越大,優先級也就越大,這里分享下官方文檔的資料:
這里有兩點需要注意:
- 如果Order屬性小於-1會拋出異常,所以只能設置大於等於-1的整數值。
- 如果沒有設置Order屬性,由於默認值都是-1(即最高優先級),會導致過濾器的執行順序變成無序隨機。
使用HandleErrorAttribute的一些注意事項
雖然HandleError特性使用起來很簡單,但是依然有很多需要注意很多地方,下面會詳細的分析特性的局限性和一些缺點。
依賴於ASP.NET的自定義錯誤模塊
如果customErrors mode="Off",則HandleError則無效,不對任何異常進行處理。
源碼:
// If custom errors are disabled, we need to let the normal ASP.NET exception handler // execute so that the user can see useful debugging information. if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled) { return; }
從源碼中可以看到,當HttpContext.IsCustomErrorEnabled屬性為false時(即未啟用自定義錯誤),就不進行任何處理,直接return停止流程。
只能處理500服務器錯誤
像HTPP 404、401、503等錯誤代碼,都是不處理的。源碼可以很直接看出,只要不是500錯誤就直接return返回:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method), // ignore it. if (new HttpException(null, exception).GetHttpCode() != 500) { return; }
因此除了500錯誤,其他錯誤只能通過自定義錯誤模塊配置響應的頁面:
<customErrors mode="On" defaultRedirect="~/Error/Index"> <error redirect="~/Error/NotFound" statusCode="404"/> <error redirect="~/Error/NotFound" statusCode="400"/> </customErrors>
只能跳轉到視圖
HandleError只提供了一個View屬性,所以響應的異常信息頁也只能是視圖,其他的靜態頁面或者.aspx之類的都是無法進行跳轉的,可以使用自定義異常過濾器實現此功能。
跳轉的原理是通過設置ExceptionContext.Result屬性:
filterContext.Result = new ViewResult { ViewName = View, MasterName = Master, ViewData = new ViewDataDictionary<HandleErrorInfo>(model), TempData = filterContext.Controller.TempData };
不利於SEO搜索引擎優化
這里涉及到部分SEO的知識,簡單來說錯誤頁面是使用302跳轉,因此對搜索引擎抓取網站內容並不友好。如果是互聯網行業的網站,考慮到SEO的問題還是不要用HandleErrorAttribute處理異常,類似企業系統的網站倒是十分適合使用。
至於應對的方法可以參考此文:ASP.NET MVC全局異常處理和捕獲的思路
同時聲明多個HandleError的效果
多個HandleError最終只有一個過濾器會執行,其他的過濾器會自動停止執行,至於是哪個過濾器執行則是依靠Order屬性進行優先級確定,原理是使用ExceptionContext.ExceptionHandled屬性進行判斷,一旦該屬性為true則表示當前拋出的異常已經被其他過濾器處理了,直接return停止余下的流程。
高級進階之繼承HandleErrorAttribute實現自定義功能
通過繼承HandleErrorAttribute類創建新的異常處理特性,可以使我們擴展更多的自定義功能,比如記錄錯誤日志、發送錯誤信息郵件等。繼承后只要重寫此類中的OnException方法即可實現功能,演示代碼如下:
public class CustomHandleErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { /* 調用基類的OnException方法,實現基礎的功能。 * 如果要完全的自定義,就不需要調用基類的方法 */ base.OnException(filterContext); /* 此處可進行記錄錯誤日志,發送錯誤通知等操作 * 通過Exception對象和HttpException對象可獲取相關異常信息。 * Exception exception = filterContext.Exception; * HttpException httpException = new HttpException(null, exception); */ } }
調用基類的OnException方法依然要注意HandleErrorAttribute類的一些限制,除非不使用基類的方法。繼承后的新特性使用方法和HandleError一樣,依然可以全局注冊和局部使用。
如果需要完全自定義異常處理的功能,建議直接繼承IExceptionFilter接口以實現功能,可以參考這篇文章:ASP.NET MVC實現IExceptionFilter接口編寫自定義異常處理過濾器
參考資料分享
作者:十有三
出處:http://shiyousan.com/post/635838881238204198
歡迎轉載本文,本文版權歸作者所有,轉載請聲明出處或保留此段聲明。^_^請尊重他人勞動成果,共建美好的網絡環境。
