為了將錯誤訊息呈現給使用者,要完成幾件事:
- 用 Filter 攔截 UserException
- 將錯誤訊息存到 TempData,之後呈現在畫面上
- 回到原本的畫面,需要產生 ViewResult
- 針對 Ajax 請求用 400 狀態回應,並將訊息放在 http content
.Net Framework MVC
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace XXXXX.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Parameters 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
string paramName = filterContext.ActionDescriptor.GetParameters()
.OrderBy(x => Regex.IsMatch(x.ParameterType.ToString(), @"(ViewModel|Domain)\b") ? 0 : 1)
.Select(x => x.ParameterName)
.FirstOrDefault();
if (paramName != null)
{ filterContext.Controller.ViewData.Model = filterContext.ActionParameters[paramName]; }
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
/* 不處理子 Action */
if (filterContext.IsChildAction) { return; }
/* 判斷 Exception 是否已經被處理了 */
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { Content = ex.Message };
}
else if (ex is JwNoDataException)
{
/* 資料不存在的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new HttpNotFoundResult("[" + ex.Message + "]");
}
else
{
/* 一般畫面的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData
};
}
}
}
}
在 FilterConfig.cs 配置
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//...
filters.Add(new ExceptionMessageActionFilter());
//...
}
}
Net core 3.1 MVC
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Orion.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Arguments 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
var arguments = filterContext.ActionArguments.Values.Where(x => x != null);
object model = arguments
.OrderBy(x => Regex.IsMatch(x.GetType().Name, @"(ViewModel|Domain)\b") ? 0 : 1)
.FirstOrDefault();
var controller = filterContext.Controller as Controller;
controller.ViewData.Model = model;
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
/* 判斷 Exception 是否已經被處理了 */
base.OnActionExecuted(filterContext);
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
var controller = filterContext.Controller as Controller;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { StatusCode = 400, Content = ex.Message };
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = controller.ViewData,
TempData = controller.TempData
};
}
}
}
}
在 Startup.cs 配置
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddMvc(options =>
{
//...
options.Filters.Add(new ExceptionMessageActionFilter());
//...
})
.AddControllersAsServices()
;
//...
}
Net core 3.1 Razor Page
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace XXXXX.Api.Filters
{
public class ExceptionMessagePageFilter : AbstractPageFilter
{
public override void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
if (context.ExceptionHandled) { return; }
/* 判斷是否有指定的 Exception */
var ex = context.Exception;
if (ex is UserException userEx) { handleUserException(context, userEx); return; }
if (ex is HttpException httpEx) { handleHttpException(context, httpEx); return; }
}
private void handleUserException(PageHandlerExecutedContext context, UserException ex)
{
/* 只針對 PageModel 進行錯誤處理*/
var page = context.HandlerInstance as PageModel;
if (page == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
context.ExceptionHandled = true;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
context.HttpContext.Response.StatusCode = 400;
context.Result = new ContentResult
{
StatusCode = 400,
Content = ex.Message
};
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
page.TempData["StatusError"] = ex.Message;
context.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
page.TempData["StatusError"] = ex.Message;
context.Result = page.Page();
}
}
private void handleHttpException(PageHandlerExecutedContext context, HttpException ex)
{
context.ExceptionHandled = true;
var headers = context.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new ContentResult
{
StatusCode = ex.StatusCode,
Content = ex.Message,
};
}
else
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new StatusCodeResult(ex.StatusCode);
}
}
}
}
在 Startup.cs 配置
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddRazorPages(options =>
{
//...
})
.AddMvcOptions(options =>
{
//...
options.Filters.Add(new ExceptionMessagePageFilter());
//...
})
;
//...
}