有朋友問我,不想每個 Action 寫 try catch,怎麼在 Global ErrorHandler 紀錄相關參數,來,看看我怎麼做,以下將實作 HandleErrorAttribute
PS.若你的例外需要跟用戶互動,以下實作可能不適合
PS.無法捕捉IIS的例外,需要使用Elmah
開發環境
- VS 2017
- .NET Framework 4.7.2
- .NLog 4.6.3
若對 ErrorHandler 使用方式不熟悉的,請參考
https://shiyousan.com/post/635838881238204198
實作
自訂一個ErrorHandler,例外發生時寫 Log
public class GlobalHandleErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { var controllerName = filterContext.RouteData.Values["controller"].ToString(); var actionName = filterContext.RouteData.Values["action"].ToString(); var exception = filterContext.Exception; var logger = LogManager.GetLogger($"{controllerName}.{actionName}"); var message = $"例外:{exception.Message}"; logger.Error(exception, message); filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); var handler = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); var viewData = new ViewDataDictionary<HandleErrorInfo>(handler); //var viewName = "/Views/Error/Error.cshtml"; filterContext.Result = new ViewResult { ViewName = this.View, MasterName = this.Master, ViewData = viewData, TempData = filterContext.Controller.TempData }; } }
註冊到整個應用程式
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { //filters.Add(new HandleErrorAttribute()); filters.Add(new GlobalHandleErrorAttribute()); } }
模擬非預期例外
public class HomeController : Controller { public ActionResult Index() { throw new Exception("壞掉了"); return View(); } }
錯誤頁面
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Error</title> </head> <body> <hgroup> <h1>Error.</h1> <h2> An error occurred while processing your request. </h2> </hgroup> </body> </html>
執行結果,這樣就可以捕捉到例外了
現在,要記錄 Action 所使用的參數並傳遞給 GlobalHandleErrorAttribute Log 下來,要怎麼傳呢?
ASP.NET 提供了許多狀態,https://docs.microsoft.com/zh-tw/previous-versions/aspnet/75x4ha6s(v%3Dvs.100)
ASP.MVC 還可以選擇用 ViewBag、ViewData、TempData
這裡,我選用 HttpContext.Current.Items,這個物件只存在當前的 Http Requset,結束就沒了
public class HttpRequestState { private static readonly string RequestVariable = "Request_Variable"; public virtual object GetCurrentVariable() { return HttpContext.Current.Items[RequestVariable]; } public virtual string GetCurrentVariableToJson() { var requestItem = this.GetCurrentVariable(); return requestItem == null ? "Empty" : JsonConvert.SerializeObject(requestItem); } public virtual void SetCurrentVariable(object source) { HttpContext.Current.Items[RequestVariable] = source; } }
HttpRequestState 裡面的成員宣告成 virual,這樣做可以輕易的建立假物件,提高可測試性
集中初始化物件
public class InstanceUtility { private static HttpRequestState s_httpRequestState; public static HttpRequestState HttpRequestState { get { if (s_httpRequestState == null) { s_httpRequestState = new HttpRequestState(); } return s_httpRequestState; } set => s_httpRequestState = value; } }
紀錄狀態
public class HomeController : Controller { public ActionResult Index() { var employee = new Employee { Id = Guid.NewGuid(), Name = "yao", Age = 19 }; InstanceUtility.HttpRequestState.SetCurrentVariable(employee); throw new Exception("壞掉了"); return this.View(); } }
這裡做了幾件事
1.取出參數狀態並轉成 Json,寫入 Log
InstanceUtility.HttpRequestState.GetCurrentVariableToJson()
2.資料丟給ViewData
Viewvar viewData = new ViewDataDictionary<HandleErrorInfo>(handler);
viewData.Add("Request_Variable", requestJson);
public class GlobalHandleErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { var controllerName = filterContext.RouteData.Values["controller"].ToString(); var actionName = filterContext.RouteData.Values["action"].ToString(); var exception = filterContext.Exception; var logger = LogManager.GetLogger($"{controllerName}.{actionName}"); var requestJson = InstanceUtility.HttpRequestState.GetCurrentVariableToJson(); var logMessage = $"例外:{exception.Message},\r\n傳入參數:{requestJson}"; logger.Error(exception, logMessage); filterContext.ExceptionHandled = true; filterContext.HttpContext.Response.Clear(); var handler = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); var viewData = new ViewDataDictionary<HandleErrorInfo>(handler); viewData.Add("Request_Variable", requestJson);//傳入自訂的ViewData //var viewName = "/Views/Error/Error.cshtml"; filterContext.Result = new ViewResult { ViewName = this.View, MasterName = this.Master, ViewData = viewData, TempData = filterContext.Controller.TempData }; } }
這裡用兩種方式呈現結果
InstanceUtility.HttpRequestState.GetCurrentVariableToJson()
ViewData["Request_Variable"]
@model HandleErrorInfo @{ ViewBag.Title = "Contact"; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Error</title> </head> <body> <hgroup> <h1>Error.</h1> <h2> An error occurred while processing your request. An unexpected error has occurred. Please contact the system administrator. </h2> <b>Request Variable from HttpContext:</b> @InstanceUtility.HttpRequestState.GetCurrentVariableToJson() <br /> <b>Request Variable from ViewData:</b> @ViewData["Request_Variable"] <br /> @if (Model != null && HttpContext.Current.IsDebuggingEnabled) { <div> <p> <b>Exception:</b> @Model.Exception.Message<br /> <b>Controller:</b> @Model.ControllerName<br /> <b>Action:</b> @Model.ActionName<br /> </p> <div style="overflow: scroll"> <pre> @Model.Exception.StackTrace </pre> </div> </div> } </hgroup> </body> </html>
執行結果
專案範例
https://github.com/yaochangyu/sample.dotblog/tree/master/MVC5/ErrorHandlers/Lab.GlobalErrorHandler
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET