[ASP.NET MVC] Global Error Handler - 實作 HandleErrorAttribute 捕捉應用程式例外並紀錄請求參數

有朋友問我,不想每個 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="overflowscroll">
                    <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

Image result for microsoft+mvp+logo