【ASP.NET Core】處理異常(下篇)

上一篇中,老周給大夥伴們扯了有關 ASP.NET Core 中異常處理的簡單方法。按照老周的優良做風,咱們應該順着這個思路繼續挖掘。html

本文老周就蚍蜉撼樹地介紹一下如何使用 MVC Filter 來處理異常。MVC 模型(固然適用於 Razor Page 、Web API 模型)能夠用一系列的 Filter 來對請求與迴應消息進行過濾處理。其中,在 Microsoft.AspNetCore.Mvc.Filters 命名空間下,你會發現有兩個接口,它們跟異常處理有關:api

IExceptionFilter:實現 OnException 方法,能夠自定義回傳給客戶端的異常信息。瀏覽器

IAsyncExceptionFilter:跟上面的同樣的,只不過這廝支持異步等待而已。服務器

 

在實現處理異常的 Filter 時,傳給 OnException / OnExceptionAsync 方法的有一個 ExceptionContext 類型參數,咱們能夠經過它來設置自定義的返回結果。app

訪問 Exception 屬性,你能夠獲得相關的異常實例,固然這個屬性是可寫的,因此你能夠獲取異常實例後,將它改成其餘異常實例,再從新賦給這個屬性,好比,你用你本身編寫的異常類來從新封裝。經過 Result 屬性設置返回結果,這個與 MVC Action 方法的返回方法同樣,不一樣的是,在 Action 方法中,你能夠調用 Controller 基類的方法來返回對應的 Result ,而對於 Result 屬性,你必須顯式地去建立實現了 IActionResult 接口的類型實例。異步

另外,值得注意的是,ExceptionContext 類還有一個 ExceptionHandled 屬性,該屬性值可讀可寫,主要是用於標識當前發生的異常是否已通過處理。這主要是應對 Filter 的執行順序的,一種狀況是你可能使用了多個 Filter 來處理異常,在處理過程當中你就能夠將這個屬性值設爲 true 以表示這個錯誤已處理過了,後面的就沒必要處理了;另外一種狀況是,以 Attribute 方式使用的 Filter 的優先級會比全局使用的 Filter 高,也許在 Attribute 上我沒有對異常進行處理,那麼到了全局 Filter 執行的時候,我就能夠檢查一下這個屬性,若是沒有處理就進行一下處理。關於 Attribute 方式使用 Filter 老周隨後會說的,這裏先提一下。ide

 

好了,我們先說說如何實現本身的異常處理 Filter,其實很簡單,看下面代碼。測試

    public class MyExceptionFilter : IExceptionFilter, IFilterMetadata
    {
        public void OnException(ExceptionContext context)
        {
            if(context.ExceptionHandled == false)
            {
                string msg = context.Exception.Message;
                context.Result = new ContentResult
                {
                    Content = msg,
                    StatusCode = StatusCodes.Status200OK,
                    ContentType = "text/html;charset=utf-8"
                };
            }
            context.ExceptionHandled = true; //異常已處理了
        }

在 OnException 方法中,我直接獲取異常信息,而後用一個 ContentResult 對象來返回,這個是相似於 MVC 中 Controller . Action 方法返回結果,我這裏簡單地以 HTML 文本形式返回,一旦處理到異常,應用程序會自動把這個 Result 返回給客戶端。ui

你可能發現了,我除了實現 IExceptionFilter 接口外,還實現了一個 IFilterMetadata 接口,這個接口是必須的,否則待會兒咱們沒法應用這個 Filter 了,爲何呢,等一下你就會明白了。spa

這裏實現的這個是同步調用的,若是你但願有一個可異步等待的版本,那麼,你就順便實現一下 IAsyncExceptionFilter 接口。把上面的代碼改成:

    public class MyExceptionFilter : IExceptionFilter, IAsyncExceptionFilter, IFilterMetadata
    {
        public void OnException(ExceptionContext context)
        {
            if(context.ExceptionHandled == false)
            {
                string msg = context.Exception.Message;
                context.Result = new ContentResult
                {
                    Content = msg,
                    StatusCode = StatusCodes.Status200OK,
                    ContentType = "text/html;charset=utf-8"
                };
            }
            context.ExceptionHandled = true; //異常已處理了
        }

        public Task OnExceptionAsync(ExceptionContext context)
        {
 OnException(context); return Task.CompletedTask;
        }
    }

 

好了,接下來我們得考慮怎麼用它了。在 Startup.ConfigureServices 方法中,添加 MVC 功能後能夠把我們本身寫的 Filter 添加進去。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(opt =>
            {
                opt.Filters.Add<MyExceptionFilter>();             });
        }

上面代碼添加 Filter 後,是用於全局的,說白了,當應用程序內無論哪一個 Controller 裏面發生的異常,都會通過我們添加的 Filter 處理。

 

如今咱們測試一下這個異常處理的 Filter 起到什麼做用。爲了避免影響測試,請把 Configure 方法中這段代碼刪除。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc();
        }

變成這樣

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvc();
        }

 

而後,隨便弄段代碼來測試。

        [HttpPost("/code")]
        public IActionResult SubmitSome(int val)
        {
            if(val <= 0) { throw new ArgumentException("號碼不能小於或等於 0。"); } return Content($"恭喜你,中獎了。\n中獎號碼爲:{val}", "text/html;charset=utf-8");
        }

這個邏輯很簡單,就是在前臺頁面輸入一個數值,而後 POST 上來,若是數值不是大於 0 的值就拋異常。

 

而後我故意輸入一個 -10。

 

 POST 後在服務器上引起異常。

繼續執行,讓 Filter 對異常進行處理。

最後,異常信息就返回給瀏覽器了。

 

 這樣說明我們寫的 Filter 起做用了。

剛剛說過,在 ConfigureServices 方法中添加的 Filter 是用於全局的,若是咱們的項目中有個別的 Controller 或者 Controller 中的個別方法,但願使用專門的 Filter 去處理異常,這時候就能夠考慮以 Attribute 的方式去處理。

要用 Attribute 方式處理異常,須要實現 ExceptionFilterAttribute 抽象類。該抽象類已實現了我們上面提到過的幾個接口。

這個類還實現了 IOrderedFilter 接口,能夠用來安排多個 Attribute 實例在處理異常上的順序(假設你用了多個實例來處理)。

 

下面我們本身實現一個 Attribute ,用來處理異常。

    public class MyExceptionFilterAttribute : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            var ex = context.Exception;
            // 構建錯誤信息對象
            var dic = new Dictionary<string, object>
            {
                ["err_code"] = 80250,
                ["err_msg"] = ex.Message,
                ["err_sol"] = "建議攜帶你的數據到醫院作檢查。"
            };
            // 設置結果
            context.Result = new JsonResult(dic);
            context.ExceptionHandled = true;
        }

        public override Task OnExceptionAsync(ExceptionContext context)
        {
            OnException(context);
            return Task.CompletedTask;
        }
    }

上面代碼中,我以 JSON 格式返回錯誤數據。

 

這個 Attribute 能夠用於類與方法,而後我們用 Web API 來測試。

    [Route("api/[controller]")]
    public class DemoController : Controller
    {
        [HttpGet]
        [MyExceptionFilter] public IActionResult Compute(int m, int n)
        {
            if (m < 0 || n < 0)
            {
                throw new Exception("數值不能小於 0。");
            }
            return Json(new { num1 = m, num2 = n, result = m + n });
        }
    }

此處把 attrbute 用到方法上。

 

運行應用程序,而後請出 Postman 大叔來幫咱們測試 Web API。爲參數 m 和 n 賦值,而後以 GET 方式發送請求。

得到正確的結果,如今我們提交小於 0 的參數。就會返回剛剛自定義的錯誤。

 

 好了,今天的內容就說到這裏,下次有空繼續扯。

示例源代碼下載地址

相關文章
相關標籤/搜索