MasaFramework -- 例外處理

2022-10-14 12:10:00

前言

在程式設計中,我們會遇到各種各樣的異常問題,一個例外處理不僅僅可以幫助開發者快速的定位問題,也可以給使用者更好的使用體驗,那麼我們在AspNetCore專案中如何捕獲以及處理異常呢?

而對應AspNetCore程式,我們有兩種例外處理方案,它們分別是:

  • 異常中介軟體
  • 異常過濾器

介紹

Masa Franework作為一個框架,它為開發者以及使用者提供更好的開發體驗和使用體驗的例外處理功能

Masa.Utils.Exceptions 中定義了兩種異常類

  • UserFriendlyException(友好異常)
  • MasaException(框架異常)

並提供了兩種例外處理方案,那接下來就讓我們看看它們是如何使用的

根據需要自行選擇一種方案使用即可

快速入門

專案基於.NET 6.0建立,必須安裝所必須的環境

異常中介軟體

基於中介軟體實現的全域性例外處理,用於捕捉應用程式異常,並將異常資訊處理後返回

  1. 新建ASP.NET Core 空專案Assignment.GlobalExceptionDemo,並安裝Masa.Utils.Exceptions
dotnet new web -o Assignment.GlobalExceptionDemo
cd Assignment.GlobalExceptionDemo

dotnet add package Masa.Utils.Exceptions --version 0.6.0-rc.3 //提供全域性異常過濾器
  1. 新建使用者類User
public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
  1. 使用全域性異常,修改Program
//支援處理自定義異常
app.UseMasaExceptionHandler(options =>
{
    //支援處理自定義異常
    options.ExceptionHandler = context =>
    {
        if (context.Exception is ArgumentNullException ex)
        {
            context.ToResult($"{ex.ParamName}不能為空");
        }
    };
});
  1. 新增註冊使用者方法(用於自定義丟擲異常)
app.MapPost("/register", (User user) =>
{
    if (string.IsNullOrEmpty(user.Name))
        throw new ArgumentNullException(nameof(user.Name));
    //todo: Impersonate a registered user
});

更多使用技巧可檢視

異常過濾器

基於MVC的全域性異常過濾器,用於捕捉應用程式異常,並將異常資訊處理後返回

  1. 新建ASP.NET Core 空專案Assignment.GlobalFilterDemo,並安裝Masa.Utils.Exceptions
dotnet new web -o Assignment.GlobalFilterDemo
cd Assignment.GlobalFilterDemo

dotnet add package Masa.Utils.Exceptions --version 0.6.0-rc.3 //提供全域性異常過濾器
  1. 新建使用者類User
public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
  1. 使用全域性異常過濾器,修改Program
builder.Services
    .AddMvc()
    //使用MasaException
    .AddMasaExceptionHandler(options =>
    {
        options.ExceptionHandler = context =>
        {
            if (context.Exception is ValidationException ex)
            {
                string message = ex.Errors.Select(error => error.ErrorMessage).FirstOrDefault()!;
                context.ToResult(message);
            }
        };
    });
  1. 新增註冊使用者方法,用於自定義丟擲異常
[ApiController]
[Route("[Action]")]
public class UserController : ControllerBase
{
    [HttpPost]
    public void Register(User user)
    {
        if (string.IsNullOrEmpty(user.Name))
            throw new ArgumentNullException(nameof(user.Name));

        //todo: Impersonate a registered user
    }
}

驗證全域性例外處理

分別啟用使用異常中介軟體的專案以及異常過濾器的專案,用Postman或者通過Swagger分別請求兩個專案的註冊使用者介面,其中Name為空,可得到以下提示,則代表全域性例外處理成功

進階

不論是通過中介軟體還是過濾器來處理全域性異常,我們都支援自定義例外處理,我們首先來看一下異常的處理流程

根據流程圖可以直觀的瞭解到,只要使用了Masa提供的例外處理,哪怕我們不自定義異常,框架也會幫助我們按照無自定義異常流程預設處理異常資訊,但如果我們希望對特定的異常做出特定的響應,那麼就需要我們自定義異常

自定義異常

自定義異常支援三種方式

以中介軟體為例:

方案一. 通過設定ExceptionHandler(例外處理),修改Program.cs

app.UseMasaExceptionHandler(options =>
{
    options.ExceptionHandler = context =>
    {
        // 根據context.Exception判斷異常型別,並通過context.ToResult()輸出響應內容
        if (context.Exception is ArgumentNullException ex)
        {
            context.ToResult($"{ex.ParamName}不能為空");
        }
    };
});

方案二. 通過自定義ExceptionHandler,並註冊到服務集合

  1. 自定義例外處理類ExceptionHandler,並繼承IMasaExceptionHandler
/// <summary>
/// 建構函式引數需支援從IOC獲取
/// </summary>
public class ExceptionHandler : IMasaExceptionHandler
{
    public void OnException(MasaExceptionContext context)
    {
        if (context.Exception is ArgumentNullException ex)
        {
            context.ToResult($"{ex.ParamName}不能為空");
        }
    }
}
  1. 使用指定的異常Handler,修改Program.cs

builder.Services.AddSingleton<IMasaExceptionHandler, ExceptionHandler>();//註冊自定義異常

var app = builder.Build();
app.UseMasaExceptionHandler();// 在Program中執行例外處理程式

方案三. 通過自定義ExceptionHandler並指定ExceptionHandler來實現

  1. 自定義例外處理類ExceptionHandler,並繼承IMasaExceptionHandler
/// <summary>
/// 建構函式引數需支援從IOC獲取
/// </summary>
public class ExceptionHandler : IMasaExceptionHandler
{
    public void OnException(MasaExceptionContext context)
    {
        if (context.Exception is ArgumentNullException ex)
        {
            context.ToResult($"{ex.ParamName}不能為空");
        }
    }
}
  1. 使用指定的異常Handler,修改Program.cs
app.UseMasaExceptionHandler(options => options.UseExceptionHanlder<ExceptionHandler>());//指定使用特定的例外處理程式

上述三種方案任選其一即可,提供的功能時一樣的,僅僅是寫法不同

修改HttpStatusCode狀態碼

MasaExceptionContext預設提供了ToResult方法支援輸入響應內容,狀態碼(預設: 299),內容型別 (預設:text/plain; charset=utf-8),我們可以根據自己的實際情況呼叫傳參即可

修改紀錄檔級別

異常型別為UserFriendlyException的預設紀錄檔等級為Information,其餘異常的紀錄檔等級為Error,那麼如果我想修改對應異常的紀錄檔等級應該怎麼做?

設定異常紀錄檔關係:

builder.Services.Configure<MasaExceptionLogRelationOptions>(options =>
{
    options.MapLogLevel<ArgumentNullException>(LogLevel.None);
});

按照此方式,可以將型別為ArgumentNullException異常的紀錄檔等級設定為None(不記錄紀錄檔)

常見問題

A: 為什麼使用全域性異常後沒有記錄紀錄檔?
Q:

  1. 檢查是否指定了自定義例外處理的Handler,並且當前異常已經被自定義例外處理程式處理(ExceptionHandled = true)
  2. 檢查當前異常型別是否設定了指定的紀錄檔等級,且當前紀錄檔等級小於預設記錄紀錄檔的等級

A: 實現IMasaExceptionHandler後,為什麼發生異常後沒有進入OnException
Q: 未注入到服務集合且沒有指定使用指定的ExceptionHanlder

  • 自定義異常Handler
public class ExceptionHandler : IMasaExceptionHandler
{
    public void OnException(MasaExceptionContext context)
    {
        throw new NotImplementedException();
    }
}    

可參考自定義異常中的方案二或者方案三修改即可

總結

Masa提供的全域性異常中介軟體,對自定義異常的擴充套件支援較好,並且後續Masa Framework支援I18n後,全域性異常也將增加I18n支援, 屆時全域性異常會更加方便

本章原始碼

Assignment13

https://github.com/zhenlei520/MasaFramework.Practice

開源地址

MASA.Framework:https://github.com/masastack/MASA.Framework

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯絡我們