2022年9月13日 星期二

例外處理機制

目的 : 讓前端呼叫者可以顯示易懂的錯誤訊息

步驟 :
1. 自訂 ActionResult
public class CustomError : IActionResult
{
private readonly HttpStatusCode _status;
private readonly string _errorMessage;

public CustomError(HttpStatusCode status, string errorMessage)
{
_status = status;
_errorMessage = errorMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(new
{
errorMessage = _errorMessage
})
{
StatusCode = (int)_status,
};
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _errorMessage;
await objectResult.ExecuteResultAsync(context);
}
}

2. 自訂 ExceptionFilter
public class BaseExceptionFilter : IExceptionFilter
{
private readonly IHostEnvironment _hostEnvironment;

public BaseExceptionFilter(IHostEnvironment hostEnvironment) =>
_hostEnvironment = hostEnvironment;

public void OnException(ExceptionContext context)
{
if (_hostEnvironment.IsDevelopment())
{
// 顯示英文訊息方便google
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
}

string controllerName = context.RouteData.Values["controller"].ToString();
string actionName = context.RouteData.Values["action"].ToString();
var request = context.HttpContext.Request;
var requestUrl = request.Scheme + "://" + request.Host + request.Path;
                var username = context.HttpContext.User?.Identity.Name;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("xxx發生未預期錯誤");
sb.AppendLine($"錯誤訊息: {context.Exception.詳細訊息()}");
sb.AppendLine($"堆疊追蹤: {context.Exception.StackTrace.Replace("<","<").Replace(">",">")}"); // 若使用skype 通知則不接受<和>
sb.AppendLine($"網址: {requestUrl}");
         sb.AppendLine($"系統使用者: {username}");
sb.AppendLine($"Controller: {controllerName}");
sb.AppendLine($"Action: {actionName}");
                if (_hostEnvironment.IsDevelopment()) {
                    // 直接顯示除錯訊息
                    context.Result = new CustomError(HttpStatusCode.InternalServerError, sb.ToString());
                }
                else {
                    // todo: 使用 skype、line、email 等方式發送除錯訊息給資訊人員
                     context.Result =  new CustomError(HttpStatusCode.InternalServerError, "發生未預期錯誤,請洽資訊部");
                }
}
}

3.1. 建立頂層 Controller,遇到未攔截的錯誤時透過自訂 ExceptionFilter 回傳自訂 ActionResult
[TypeFilter(typeof(BaseExceptionFilter))]
public class BaseController : ControllerBase

3.2. Controller Action 若要回傳錯誤則透過自訂ActionResult
ex. return new CustomError(HttpStatusCode.BadRequest, "請輸入收貨地址");

4. 前端顯示錯誤訊息
ex.
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
console.error(error);
if (error.response.status == 401) {
alert('登入逾時');
setTimeout(() => {
window.location.reload();
}, 2000);
}
else {
if (error.response.data.errorMessage) alert(error.response.data.errorMessage);
else alert(error.message);
}
return Promise.reject(error);
});

非 core 版後端寫法

public class BaseExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// 顯示英文訊息方便google
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");

string controllerName = context.RouteData.Values["controller"].ToString();
string actionName = context.RouteData.Values["action"].ToString();
var requestUrl = context.HttpContext.Request.Url.AbsoluteUri;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("xxx發生未預期錯誤");
sb.AppendLine($"錯誤訊息: {context.Exception.詳細訊息()}");
sb.AppendLine($"堆疊追蹤: {context.Exception.StackTrace.Replace("<", "<").Replace(">", ">")}");
sb.AppendLine($"網址: {requestUrl}");
sb.AppendLine($"系統使用者: {xxx}");
sb.AppendLine($"Controller: {controllerName}");
sb.AppendLine($"Action: {actionName}");
// todo: 使用 skype、line、email 等方式發送除錯訊息給資訊人員
context.ExceptionHandled = true;
context.HttpContext.Response.TrySkipIisCustomErrors = true;
context.HttpContext.Response.Write("發生未預期錯誤,請洽資訊部");
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}

頂層Controller
[BaseExceptionFilter]
public class BaseController : Controller
{
  
}
※若要回傳可預期錯誤給使用者看,則用以下方式
async public Task<IHttpActionResult> xxxAsync()
{
if (xxx)
{
HttpContext.Current.Response.Write(xxx);
HttpContext.Current.Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
return default;
}

沒有留言:

自訂權限驗證機制

// 使用 filter [Route("api/[controller]")] [ApiController] [Authorize] [TypeFilter(typeof(CustomAsyncAuthorizationFilter))] public c...