从DbContextAutoCommiter的设计来分析ASP.NET Core MVC异常处理机制

96 阅读2分钟

异常处理方面依照输出返回的内容分为WebMvc/WebApi两种异常处理, WebMvc对应Html页面, WebApi对应Json + Envelop的数据包封装

相关注意事项

  1. 每个请求最终都会对应到一个Action来处理, 这点类似于之前的HttpHandler, 每个请求最终都是有Handler处理完成的, 这点又体现在 Response的输出上, 可以通过 HttpContext.Response.HasStarted来判断
  2. 下面注册部分的代码, 我们增加了一个AutoCommiter, 意在所有Action完成之后自动做一次Commit, 避免每个Action内部做Commit, 鉴于第一点的原因, 这个必须加载最里面, 并且这个不能放在Middleware中处理(之前就是放在Middleware中处理的, 后面出现问题才转移到这里),
  3. 进一步说明: AutoCommiter如果写在Middleware中, 出现提交异常后,我们期望通过Reponse直接输出流, 发现没法写出去, 提示 Response流已经启动了, 这一点是符合上述第一条的判断的, 所以这个AutoCommiter必须移动到ActionFilters中来处理

Action异常的处理方式

Action异常并不采用Try Cache的形式, 异常处理的ActionFilter接口是IExceptionFilter, 而在Action中直接抛出的异常, 并没办法通过TryCache在IActionFilter/IAsyncActionFilter中获取, 而是需要通过ActionExecutedContext.Exception来获取, 可见Action处理过程中如果有异常发生, 会同通过 TryCache拿到异常之后放到ExecutedContext.Exception中, 因此我们上面的AutoCommiter在出现异常之后, 也需要把Exception放到该属性中, 不可直接抛出

public class AutoCommiterFilterAttribute : ActionFilterAttribute, IActionFilter, IAsyncActionFilter, IResultFilter
{
    public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
    {
        if (actionExecutedContext.Exception != null) //如果有Exception发生,直接跳过处理
        {
            return;
        }
        try
        {
            var dbContextCommiter = actionExecutedContext.HttpContext.RequestServices.GetService<IDbContextCommiter>();
            if (dbContextCommiter != null && dbContextCommiter.IsDbContextCreated)
            {
                dbContextCommiter.CommitAsync().GetAwaiter().GetResult();
            }
        }
        catch (Exception ex)
        {
            actionExecutedContext.Exception = ex;
        }
    }
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var result = await next();
        if (result.Exception != null) //如果有Exception发生,直接跳过处理
        {
            return;
        }
        try
        {
            var dbContextCommiter = context.HttpContext.RequestServices.GetService<IDbContextCommiter>();
            if (dbContextCommiter != null && dbContextCommiter.IsDbContextCreated)
            {
                await dbContextCommiter.CommitAsync();
            }
        }
        catch (Exception ex)
        {
            result.Exception = ex;
        }
    }
}

WebApi异常处理

框架只提供相关类库, 并不做异常处理的自动注册, 需要再业务系统中自己完成相关注册

注册

整体的注册必须严格按照下面的顺序,

  1. 作为参与业务处理的AutoCommiterFilter要放到离业务代码最近的地方

  2. 做封包业务的EnvelopFilter放在所有业务代码处理完成的地方

  3. 做异常拦截处理的ExceptionHandler, 放到最外层, 负责为Action处理阶段的异常进行兜底

    public void ConfigureMvcOptions(MvcOptions mvcOptions, AppBuildContext appBuildContext) { mvcOptions.Filters.Add(); mvcOptions.Filters.Add(); mvcOptions.Filters.Add(); }

WebMVC异常处理

做的不多, 暂不涉及, 后续遇到再补充