ASP.NET CORE 學習之自定義異常處理

爲何異常處理選擇中間件?html

傳統的ASP.NET能夠採用異常過濾器的方式處理異常,在ASP.NET CORE中,是以多箇中間件鏈接而成的管道形式處理請求的,不過經常使用的五大過濾器得以保留,一樣能夠採用異常過濾器處理異常,可是異常過濾器不能處理MVC中間件之外的異常,爲了全局統一考慮,採用中間件處理異常更爲合適json

 

爲何選擇自定義異常中間件?api

 先來看看ASP.NET CORE 內置的三個異常處理中間件 DeveloperExceptionPageMiddleware , ExceptionHandlerMiddleware, StatusCodePagesMiddleware 服務器

1.DeveloperExceptionPageMiddleware 
 能給出詳細的請求/返回/錯誤信息,由於包含敏感信息,因此僅適合開發環境mvc

2.ExceptionHandlerMiddleware  (蔣神博客 http://www.cnblogs.com/artech/p/error-handling-in-asp-net-core-3.html)app

僅處理500錯誤async

3.StatusCodePagesMiddleware  (蔣神博客 http://www.cnblogs.com/artech/p/error-handling-in-asp-net-core-4.html)性能

能處理400-599之間的錯誤,但須要Response中不能包含內容(ContentLength=0 && ContentType=null,經實驗不能響應mvc裏未捕獲異常)測試

因爲ExceptionHandlerMiddleware和StatusCodePagesMiddleware的各自的限制條件,二者須要搭配使用。相比之下自定義中間件更加靈活,既能對各類錯誤狀態進行統一處理,也能按照配置決定處理方式。ui

 

CustomExceptionMiddleWare

首先聲明異常中間件的配置類

 1     /// <summary>
 2     /// 異常中間件配置對象
 3     /// </summary>
 4     public class CustomExceptionMiddleWareOption
 5     {
 6         public CustomExceptionMiddleWareOption(
 7             CustomExceptionHandleType handleType = CustomExceptionHandleType.JsonHandle,
 8             IList<PathString> jsonHandleUrlKeys = null,
 9             string errorHandingPath = "")
10         {
11             HandleType = handleType;
12             JsonHandleUrlKeys = jsonHandleUrlKeys;
13             ErrorHandingPath = errorHandingPath;
14         }
15 
16         /// <summary>
17         /// 異常處理方式
18         /// </summary>
19         public CustomExceptionHandleType HandleType { get; set; }
20 
21         /// <summary>
22         /// Json處理方式的Url關鍵字
23         /// <para>僅HandleType=Both時生效</para>
24         /// </summary>
25         public IList<PathString> JsonHandleUrlKeys { get; set; }
26 
27         /// <summary>
28         /// 錯誤跳轉頁面
29         /// </summary>
30         public PathString ErrorHandingPath { get; set; }
31     }
32 
33     /// <summary>
34     /// 錯誤處理方式
35     /// </summary>
36     public enum CustomExceptionHandleType
37     {
38         JsonHandle = 0,   //Json形式處理
39         PageHandle = 1,   //跳轉網頁處理
40         Both = 2          //根據Url關鍵字自動處理
41     }

聲明異常中間件的成員

        /// <summary>
        /// 管道請求委託
        /// </summary>
        private RequestDelegate _next;

        /// <summary>
        /// 配置對象
        /// </summary>
        private CustomExceptionMiddleWareOption _option;

        /// <summary>
        /// 須要處理的狀態碼字典
        /// </summary>
        private IDictionary<int, string> exceptionStatusCodeDic;

        public CustomExceptionMiddleWare(RequestDelegate next, CustomExceptionMiddleWareOption option)
        {
            _next = next;
            _option = option;
            exceptionStatusCodeDic = new Dictionary<int, string>
            {
                { 401, "未受權的請求" },
                { 404, "找不到該頁面" },
                { 403, "訪問被拒絕" },
                { 500, "服務器發生意外的錯誤" }
                //其他狀態自行擴展
            };
        }

異常中間件主要邏輯

 1         public async Task Invoke(HttpContext context)
 2         {
 3             Exception exception = null;
 4             try
 5             {
 6                 await _next(context);   //調用管道執行下一個中間件
 7             }
 8             catch (Exception ex)
 9             {
10                 context.Response.Clear();    
11                 context.Response.StatusCode = 500;   //發生未捕獲的異常,手動設置狀態碼
12                 exception = ex;
13             }
14             finally
15             {
16                 if (exceptionStatusCodeDic.ContainsKey(context.Response.StatusCode) && 
17                     !context.Items.ContainsKey("ExceptionHandled"))  //預處理標記
18                 {
19                     var errorMsg = string.Empty;
20                     if (context.Response.StatusCode == 500 && exception != null)
21                     {
22                         errorMsg = $"{exceptionStatusCodeDic[context.Response.StatusCode]}\r\n{(exception.InnerException != null ? exception.InnerException.Message : exception.Message)}";
23                     }
24                     else
25                     {
26                         errorMsg = exceptionStatusCodeDic[context.Response.StatusCode];
27                     }
28                     exception = new Exception(errorMsg);
29                 }
30 
31                 if (exception != null)
32                 {
33                     var handleType = _option.HandleType;
34                     if (handleType == CustomExceptionHandleType.Both)   //根據Url關鍵字決定異常處理方式
35                     {
36                         var requestPath = context.Request.Path;
37                         handleType = _option.JsonHandleUrlKeys != null && _option.JsonHandleUrlKeys.Count(
38                             k => requestPath.StartsWithSegments(k, StringComparison.CurrentCultureIgnoreCase)) > 0 ?
39                             CustomExceptionHandleType.JsonHandle :
40                             CustomExceptionHandleType.PageHandle;
41                     }
42                     
43                     if (handleType == CustomExceptionHandleType.JsonHandle)
44                         await JsonHandle(context, exception);
45                     else
46                         await PageHandle(context, exception, _option.ErrorHandingPath);
47                 }
48             }
49         }
50 
51         /// <summary>
52         /// 統一格式響應類
53         /// </summary>
54         /// <param name="ex"></param>
55         /// <returns></returns>
56         private ApiResponse GetApiResponse(Exception ex)
57         {
58             return new ApiResponse() { IsSuccess = false, Message = ex.Message };
59         }
60 
61         /// <summary>
62         /// 處理方式:返回Json格式
63         /// </summary>
64         /// <param name="context"></param>
65         /// <param name="ex"></param>
66         /// <returns></returns>
67         private async Task JsonHandle(HttpContext context, Exception ex)
68         {
69             var apiResponse = GetApiResponse(ex);
70             var serialzeStr = JsonConvert.SerializeObject(apiResponse);
71             context.Response.ContentType = "application/json";
72             await context.Response.WriteAsync(serialzeStr, Encoding.UTF8);
73         }
74 
75         /// <summary>
76         /// 處理方式:跳轉網頁
77         /// </summary>
78         /// <param name="context"></param>
79         /// <param name="ex"></param>
80         /// <param name="path"></param>
81         /// <returns></returns>
82         private async Task PageHandle(HttpContext context, Exception ex, PathString path)
83         {
84             context.Items.Add("Exception", ex);
85             var originPath = context.Request.Path;
86             context.Request.Path = path;   //設置請求頁面爲錯誤跳轉頁面
87             try
88             {
89                 await _next(context);      
90             }
91             catch { }
92             finally
93             {
94                 context.Request.Path = originPath;   //恢復原始請求頁面
95             }
96         }

使用擴展類進行中間件註冊

1  public static class CustomExceptionMiddleWareExtensions
2     {
3 
4         public static IApplicationBuilder UseCustomException(this IApplicationBuilder app, CustomExceptionMiddleWareOption option)
5         {
6             return app.UseMiddleware<CustomExceptionMiddleWare>(option);
7         }
8     }

在Startup.cs的Configuref方法中註冊異常中間件

1   app.UseCustomException(new CustomExceptionMiddleWareOption(
2                     handleType: CustomExceptionHandleType.Both,  //根據url關鍵字決定處理方式
3                     jsonHandleUrlKeys: new PathString[] { "/api" },
4                     errorHandingPath: "/home/error"));

 

接下來咱們來進行測試,首先模擬一個將會進行頁面跳轉的未經捕獲的異常

 

訪問/home/about的結果

 

訪問/home/test的結果 (該地址不存在)

 

OK異常跳轉頁面的方式測試完成,接下來咱們測試返回統一格式(json)的異常處理,一樣先模擬一個未經捕獲的異常

 

訪問/api/token/gettesterror的結果

 

訪問/api/token/test的結果 (該地址不存在)

 

訪問/api/token/getvalue的結果 (該接口須要身份驗證)

 

測試完成,頁面跳轉和統一格式返回都沒有問題,自定義異常中間件已按預期工做

須要注意的是,自定義中間件會響應每一個HTTP請求,因此處理邏輯必定要精簡,防止發生沒必要要的性能問題

相關文章
相關標籤/搜索