2021-10-16 23:40

C# MVC 的 UserException 攔截處理

為了將錯誤訊息呈現給使用者,要完成幾件事:

  • 用 Filter 攔截 UserException
  • 將錯誤訊息存到 TempData,之後呈現在畫面上
  • 回到原本的畫面,需要產生 ViewResult
  • 針對 Ajax 請求用 400 狀態回應,並將訊息放在 http content


.Net Framework MVC

  1. using System; 
  2. using System.Linq; 
  3. using System.Text.RegularExpressions; 
  4. using System.Web.Mvc; 
  5.  
  6. namespace XXXXX.Mvc.Filters 
  7. { 
  8.    /// <summary>例外訊息過濾器</summary> 
  9.    public class ExceptionMessageActionFilter : ActionFilterAttribute 
  10.    { 
  11.  
  12.        /// <summary></summary> 
  13.        public override void OnActionExecuting(ActionExecutingContext filterContext) 
  14.        { 
  15.            /* 自動將 Action 的 Parameters 中的 ViewModel 賦予給 ViewData 
  16.             * 不然要自己在 Action 的第一行寫 ViewData.Model = domain; 
  17.             */ 
  18.  
  19.            string paramName = filterContext.ActionDescriptor.GetParameters() 
  20.                .OrderBy(x => Regex.IsMatch(x.ParameterType.ToString(), @"(ViewModel|Domain)\b") ? 0 : 1) 
  21.                .Select(x => x.ParameterName) 
  22.                .FirstOrDefault(); 
  23.  
  24.            if (paramName != null) 
  25.            { filterContext.Controller.ViewData.Model = filterContext.ActionParameters[paramName]; } 
  26.        } 
  27.  
  28.  
  29.        /// <summary></summary> 
  30.        public override void OnActionExecuted(ActionExecutedContext filterContext) 
  31.        { 
  32.            base.OnActionExecuted(filterContext); 
  33.  
  34.            /* 不處理子 Action */ 
  35.            if (filterContext.IsChildAction) { return; } 
  36.  
  37.            /* 判斷 Exception 是否已經被處理了 */ 
  38.            if (filterContext.ExceptionHandled) { return; } 
  39.  
  40.            /* 只針對 UserException 進行錯誤處理*/ 
  41.            var ex = filterContext.Exception as UserException; 
  42.            if (ex == null) { return; } 
  43.  
  44.  
  45.            /* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */ 
  46.            filterContext.ExceptionHandled = true; 
  47.  
  48.            if (filterContext.HttpContext.Request.IsAjaxRequest()) 
  49.            { 
  50.                /* 對 Ajax 請求的處理 */ 
  51.                filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; 
  52.                filterContext.HttpContext.Response.StatusCode = 400; 
  53.                filterContext.Result = new ContentResult { Content = ex.Message }; 
  54.            } 
  55.            else if (ex is JwNoDataException) 
  56.            { 
  57.                /* 資料不存在的處理 */ 
  58.                filterContext.Controller.TempData["StatusError"] = ex.Message; 
  59.                filterContext.Result = new HttpNotFoundResult("[" + ex.Message + "]"); 
  60.            } 
  61.            else 
  62.            { 
  63.                /* 一般畫面的處理 */ 
  64.                filterContext.Controller.TempData["StatusError"] = ex.Message; 
  65.                filterContext.Result = new ViewResult 
  66.                { 
  67.                    ViewData = filterContext.Controller.ViewData, 
  68.                    TempData = filterContext.Controller.TempData 
  69.                }; 
  70.            } 
  71.        } 
  72.  
  73.    } 
  74. } 

在 FilterConfig.cs 配置

  1. public class FilterConfig 
  2. { 
  3.    public static void RegisterGlobalFilters(GlobalFilterCollection filters) 
  4.    { 
  5.        //... 
  6.  
  7.        filters.Add(new ExceptionMessageActionFilter()); 
  8.  
  9.        //... 
  10.    } 
  11. } 


Net core 3.1 MVC

  1. using System; 
  2. using System.Linq; 
  3. using System.Text.RegularExpressions; 
  4. using Microsoft.AspNetCore.Mvc; 
  5. using Microsoft.AspNetCore.Mvc.Filters; 
  6.  
  7. namespace Orion.Mvc.Filters 
  8. { 
  9.    /// <summary>例外訊息過濾器</summary> 
  10.    public class ExceptionMessageActionFilter : ActionFilterAttribute 
  11.    { 
  12.  
  13.        /// <summary></summary> 
  14.        public override void OnActionExecuting(ActionExecutingContext filterContext) 
  15.        { 
  16.            /* 自動將 Action 的 Arguments 中的 ViewModel 賦予給 ViewData 
  17.             * 不然要自己在 Action 的第一行寫 ViewData.Model = domain; 
  18.             */ 
  19.  
  20.            var arguments = filterContext.ActionArguments.Values.Where(x => x != null); 
  21.  
  22.            object model = arguments 
  23.                .OrderBy(x => Regex.IsMatch(x.GetType().Name, @"(ViewModel|Domain)\b") ? 0 : 1) 
  24.                .FirstOrDefault(); 
  25.  
  26.            var controller = filterContext.Controller as Controller; 
  27.            controller.ViewData.Model = model; 
  28.        } 
  29.  
  30.  
  31.        /// <summary></summary> 
  32.        public override void OnActionExecuted(ActionExecutedContext filterContext) 
  33.        { 
  34.            /* 判斷 Exception 是否已經被處理了 */ 
  35.            base.OnActionExecuted(filterContext); 
  36.            if (filterContext.ExceptionHandled) { return; } 
  37.  
  38.  
  39.            /* 只針對 UserException 進行錯誤處理*/ 
  40.            var ex = filterContext.Exception as UserException; 
  41.            if (ex == null) { return; } 
  42.  
  43.            /* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */ 
  44.            filterContext.ExceptionHandled = true; 
  45.  
  46.  
  47.            var controller = filterContext.Controller as Controller; 
  48.  
  49.            var headers = filterContext.HttpContext.Request.Headers; 
  50.            bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest"; 
  51.  
  52.            if (isAjax) 
  53.            { 
  54.                /* 對 Ajax 請求的處理 */ 
  55.                filterContext.HttpContext.Response.StatusCode = 400; 
  56.                filterContext.Result = new ContentResult { StatusCode = 400, Content = ex.Message }; 
  57.            } 
  58.            else if (ex is UserNoDataException) 
  59.            { 
  60.                /* 資料不存在的處理 */ 
  61.                controller.TempData["StatusError"] = ex.Message; 
  62.                filterContext.HttpContext.Response.StatusCode = 404; 
  63.            } 
  64.            else 
  65.            { 
  66.                /* 一般畫面的處理 */ 
  67.                controller.TempData["StatusError"] = ex.Message; 
  68.                filterContext.Result = new ViewResult 
  69.                { 
  70.                    ViewData = controller.ViewData, 
  71.                    TempData = controller.TempData 
  72.                }; 
  73.            } 
  74.        } 
  75.  
  76.    } 
  77. } 

在 Startup.cs 配置

  1. public void ConfigureServices(IServiceCollection services) 
  2. { 
  3.    //... 
  4.  
  5.    IMvcBuilder mvcBuilder = services 
  6.        .AddMvc(options => 
  7.        { 
  8.            //... 
  9.            options.Filters.Add(new ExceptionMessageActionFilter()); 
  10.            //... 
  11.        }) 
  12.        .AddControllersAsServices() 
  13.        ; 
  14.  
  15.    //... 
  16. } 


Net core 3.1 Razor Page

  1. using System; 
  2. using System.Linq; 
  3. using Microsoft.AspNetCore.Mvc; 
  4. using Microsoft.AspNetCore.Mvc.Filters; 
  5. using Microsoft.AspNetCore.Mvc.RazorPages; 
  6.  
  7. namespace XXXXX.Api.Filters 
  8. { 
  9.  
  10.    public class ExceptionMessagePageFilter : AbstractPageFilter 
  11.    { 
  12.        public override void OnPageHandlerExecuted(PageHandlerExecutedContext context) 
  13.        { 
  14.            if (context.ExceptionHandled) { return; } 
  15.  
  16.            /* 判斷是否有指定的 Exception */ 
  17.            var ex = context.Exception; 
  18.            if (ex is UserException userEx) { handleUserException(context, userEx); return; } 
  19.            if (ex is HttpException httpEx) { handleHttpException(context, httpEx); return; } 
  20.        } 
  21.  
  22.  
  23.        private void handleUserException(PageHandlerExecutedContext context, UserException ex) 
  24.        { 
  25.            /* 只針對 PageModel 進行錯誤處理*/ 
  26.            var page = context.HandlerInstance as PageModel; 
  27.            if (page == null) { return; } 
  28.  
  29.            /* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */ 
  30.            context.ExceptionHandled = true; 
  31.  
  32.            var headers = filterContext.HttpContext.Request.Headers; 
  33.            bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest"; 
  34.  
  35.            if (isAjax) 
  36.            { 
  37.                /* 對 Ajax 請求的處理 */ 
  38.                context.HttpContext.Response.StatusCode = 400; 
  39.                context.Result = new ContentResult 
  40.                { 
  41.                    StatusCode = 400, 
  42.                    Content = ex.Message 
  43.                }; 
  44.            } 
  45.            else if (ex is UserNoDataException) 
  46.            { 
  47.                /* 資料不存在的處理 */ 
  48.                page.TempData["StatusError"] = ex.Message; 
  49.                context.HttpContext.Response.StatusCode = 404; 
  50.            } 
  51.            else 
  52.            { 
  53.                /* 一般畫面的處理 */ 
  54.                page.TempData["StatusError"] = ex.Message; 
  55.                context.Result = page.Page(); 
  56.            } 
  57.        } 
  58.  
  59.  
  60.  
  61.        private void handleHttpException(PageHandlerExecutedContext context, HttpException ex) 
  62.        { 
  63.            context.ExceptionHandled = true; 
  64.  
  65.            var headers = context.HttpContext.Request.Headers; 
  66.            bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest"; 
  67.  
  68.            if (isAjax) 
  69.            { 
  70.                context.HttpContext.Response.StatusCode = ex.StatusCode; 
  71.                context.Result = new ContentResult 
  72.                { 
  73.                    StatusCode = ex.StatusCode, 
  74.                    Content = ex.Message, 
  75.                }; 
  76.            } 
  77.            else 
  78.            { 
  79.                context.HttpContext.Response.StatusCode = ex.StatusCode; 
  80.                context.Result = new StatusCodeResult(ex.StatusCode); 
  81.            } 
  82.        } 
  83.    } 
  84. } 

在 Startup.cs 配置

  1. public void ConfigureServices(IServiceCollection services) 
  2. { 
  3.    //... 
  4.  
  5.    IMvcBuilder mvcBuilder = services 
  6.        .AddRazorPages(options => 
  7.        { 
  8.            //... 
  9.        }) 
  10.        .AddMvcOptions(options => 
  11.        { 
  12.            //... 
  13.            options.Filters.Add(new ExceptionMessagePageFilter()); 
  14.            //... 
  15.        }) 
  16.        ; 
  17.  
  18.    //... 
  19. } 


0 回應: