13. 异常处理

13. 异常处理

前言

Masa提供了用于处理Web应用程序异常的模型,并提供了基于中间件的全局异常以及基于异常过滤器的异常处理,下面我们就来介绍下如何使用它

全局异常

基于中间件实现的全局异常处理,用于捕捉应用程序异常,并将异常信息处理后返回

  1. 新建ASP.NET Core 空项目Assignment.GlobalExceptionDemo,并安装Masa.Utils.ExceptionsFluentValidation.AspNetCore

    1
    2
    3
    4
    5
    dotnet new web -o Assignment.GlobalExceptionDemo
    cd Assignment.GlobalExceptionDemo

    dotnet add package Masa.Utils.Exceptions --version 0.5.0-preview.5 //提供全局异常过滤器
    dotnet add package FluentValidation.AspNetCore --version 11.1.2 //自定义验证库,为测试自定义异常处理,非必须
  2. 新建用户类User

    1
    2
    3
    4
    5
    6
    public class User
    {
    public string Name { get; set; }

    public int Age { get; set; }
    }
  3. 新增用户验证类UserValidator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class UserValidator : AbstractValidator<User>
    {
    public UserValidator()
    {
    RuleFor(u => u.Name).Must(name => !string.IsNullOrEmpty(name)).WithMessage($"{nameof(User.Name)} is not empty");
    RuleFor(u => u.Age)
    .LessThanOrEqualTo(150).WithMessage($"{nameof(User.Age)} must be less than 150")
    .GreaterThanOrEqualTo(0).WithMessage($"{nameof(User.Age)} must be greater than 0");
    }
    }
  4. 新增使用FluentValidation,修改Program

    1
    builder.Services.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<User>());
  5. 使用全局异常,修改Program

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //支持处理自定义异常
    app.UseMasaExceptionHandler(options =>
    {
    //支持处理自定义异常
    options.ExceptionHandler = context =>
    {
    if (context.Exception is ValidationException ex)
    {
    string message = ex.Errors.Select(error => error.ErrorMessage).FirstOrDefault()!;
    context.ToResult(message);
    }
    };
    });
  6. 新增注册用户方法(用于自定义抛出异常)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.MapPost("/register", (User user) =>
    {
    var validator = new UserValidator();
    var results = validator.Validate(user);
    if (!results.IsValid)
    throw new ValidationException(results.Errors);

    //todo: Impersonate a registered user
    });

更多使用技巧可查看

全局异常过滤器

基于MVC的全局异常过滤器,用于捕捉应用程序异常,并将异常信息处理后返回

  1. 新建ASP.NET Core 空项目Assignment.GlobalFilterDemo,并安装Masa.Utils.ExceptionsFluentValidation.AspNetCore

    1
    2
    3
    4
    5
    dotnet new web -o Assignment.GlobalFilterDemo
    cd Assignment.GlobalFilterDemo

    dotnet add package Masa.Utils.Exceptions --version 0.5.0-preview.5 //提供全局异常过滤器
    dotnet add package FluentValidation.AspNetCore --version 11.1.2 //自定义验证库,为测试自定义异常处理,非必须
  2. 新建用户类User

    1
    2
    3
    4
    5
    6
    public class User
    {
    public string Name { get; set; }

    public int Age { get; set; }
    }
  3. 新增用户验证类UserValidator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class UserValidator : AbstractValidator<User>
    {
    public UserValidator()
    {
    RuleFor(u => u.Name).Must(name => !string.IsNullOrEmpty(name)).WithMessage($"{nameof(User.Name)} is not empty");
    RuleFor(u => u.Age)
    .LessThanOrEqualTo(150).WithMessage($"{nameof(User.Age)} must be less than 150")
    .GreaterThanOrEqualTo(0).WithMessage($"{nameof(User.Age)} must be greater than 0");
    }
    }
  4. 新增使用FluentValidation,并使用MasaException,修改Program

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    builder.Services.Configure<ApiBehaviorOptions>(options => options.SuppressModelStateInvalidFilter = true);//禁用默认Model验证

    builder.Services
    .AddMvc()
    .AddFluentValidation()//使用FluentValidation

    //使用MasaException
    .AddMasaExceptionHandler(options =>
    {
    options.ExceptionHandler = context =>
    {
    if (context.Exception is ValidationException ex)
    {
    string message = ex.Errors.Select(error => error.ErrorMessage).FirstOrDefault()!;
    context.ToResult(message);
    }
    };
    });
  5. 新增注册用户方法,用于自定义抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [ApiController]
    [Route("[Action]")]
    public class UserController : ControllerBase
    {
    [HttpPost]
    public void Register(User user)
    {
    var validator = new UserValidator();
    var results = validator.Validate(user);
    if (!results.IsValid)
    throw new ValidationException(results.Errors);

    //todo: Impersonate a registered user
    }
    }

修改日志级别

无论是全局异常中间件还是Mvc的异常过滤器,其中异常类型为UserFriendlyException的默认日志等级为Information,其余异常的日志等级为Error,那么如果我想修改对应异常的日志等级应该怎么做?

配置异常日志关系:

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

按照此方式,可以将类型为ArgumentNullException异常的日志等级设置为None

常见问题

A: 为什么使用全局异常后没有记录日志?
Q:

  1. 检查是否指定了自定义异常处理,并且设置了异常已被处理(ExceptionHandled = true)
  2. 检查当前异常类型是否配置了指定的日志等级,且当前日志等级小于默认记录日志的等级

A: 实现IMasaExceptionHandler后,为什么发生异常后没有进入OnException
Q: 未注入到服务集合且没有指定使用指定的ExceptionHanlder

  • 自定义异常Handler

    1
    2
    3
    4
    5
    6
    7
    public class ExceptionHandler : IMasaExceptionHandler
    {
    public void OnException(MasaExceptionContext context)
    {
    throw new NotImplementedException();
    }
    }
  • 注册异常Handler(以下两种方法均可)

    • 注册到服务集合

      1
      2
      3
      builder.Services.AddSingleton<IMasaExceptionHandler, ExceptionHandler>();

      app.UseMasaExceptionHandler();
    • 指定使用某个Handler

      1
      app.UseMasaExceptionHandler(options => options.UseExceptionHanlder<ExceptionHandler>());

异常Handler的构造函数中的参数必须在服务中有注册,且生命周期为Singleton

总结

Masa提供的全局异常中间件,是通过Middleware在请求的最外层捕捉异常,当系统发生错误时,捕捉异常进行处理,并通过预设的异常处理器进行处理,具体的错误异常流程图如下

Exception.png

根据流程图我们可以很清晰的分辨出异常所在位置,通过增加自定义异常处理机制,使得全局异常更适合我们自己的项目,除此之外,后续支持I18n后,全局异常也会支持I18n, 届时全局异常会更加方便

本章源码

Assignment13

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

开源地址

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

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

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

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

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

如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

16373211753064.png

作者

MASA

发布于

2022-07-08

更新于

2022-09-26

许可协议