ABP Exception Handling
ABP Exception Handling
2023/6/1
➡️

Exception Handling

异常处理 ABP 提供了用于处理 Web 应用程序异常的标准模型.

自动 处理所有异常 .如果是 API/AJAX 请求,会向客户端返回一个标准格式化后的错误消息 . 自动隐 藏 内部详细错误 并返回标准错误消息. 为异常消息的 本地化 提供一种可配置的方式. 自动为标准异 常设置 HTTP 状态代码 ,并提供可配置选项,以映射自定义异常. 自动处理异常当满足下面任意一个条 件时,AbpExceptionFilter 会处理此异常:

当 controller action 方法返回类型是 object result(而不是 view result)并有异常抛出时. 当一 个请求为 AJAX(Http 请求头中 X-Requested-With 为 XMLHttpRequest)时. 当客户端接受的返回类型 为 application/json(Http 请求头中 accept 为 application/json)时. 如果异常被处理过,则会自动 记录日志并将格式化的 JSON 消息返回给客户端.

IHasErrorCode

public interface IHasErrorCode
{
    string? Code { get; }
}

IHasErrorDetails

 public interface IHasErrorDetails
{
    string? Details { get; }
}

IHasValidationErrors

public interface IHasValidationErrors
{
    IList<ValidationResult> ValidationErrors { get; }
}

IHasLogLevel

public interface IHasLogLevel
{
    /// <summary>
    /// Log severity.
    /// </summary>
    LogLevel LogLevel { get; set; }
}

IBusinessException 业务异常

namespace Volo.Abp;


public interface IBusinessException
{

}

[Serializable]
public class BusinessException : Exception,
    IBusinessException,
    IHasErrorCode,
    IHasErrorDetails,
    IHasLogLevel
{
    public string? Code { get; set; }

    public string? Details { get; set; }

    public LogLevel LogLevel { get; set; }

    //默认日志级别为异常
    public BusinessException(
        string? code = null,
        string? message = null,
        string? details = null,
        Exception? innerException = null,
        LogLevel logLevel = LogLevel.Warning)
        : base(message, innerException)
    {
        Code = code;
        Details = details;
        LogLevel = logLevel;
    }

    /// <summary>
    /// Constructor for serializing.
    /// </summary>
    public BusinessException(SerializationInfo serializationInfo, StreamingContext context)
        : base(serializationInfo, context)
    {

    }

    //Exception 的字典数据
    public BusinessException WithData(string name, object value)
    {
        Data[name] = value;
        return this;
    }
}

IUserFriendlyException

public interface IUserFriendlyException : IBusinessException
{

}

[Serializable]
public class UserFriendlyException : BusinessException, IUserFriendlyException
{
    public UserFriendlyException(
        string message,
        string? code = null,
        string? details = null,
        Exception? innerException = null,
        LogLevel logLevel = LogLevel.Warning)
        : base(
              code,
              message,
              details,
              innerException,
              logLevel)
    {
        Details = details;
    }

    public UserFriendlyException(
      SerializationInfo serializationInfo, StreamingContext context)
        : base(serializationInfo, context)
    {

    }
}

异常的处理及返回

namespace Volo.Abp.Http;


[Serializable]
public class RemoteServiceErrorInfo
{

    public string? Code { get; set; }
    public string? Message { get; set; }
    public string? Details { get; set; }
    public IDictionary? Data { get; set; }
    public RemoteServiceValidationErrorInfo[]? ValidationErrors { get; set; }
    public RemoteServiceErrorInfo()
    {

    }
    public RemoteServiceErrorInfo(string message, string? details = null,
     string? code = null, IDictionary? data = null)
    {
        Message = message;
        Details = details;
        Code = code;
        Data = data;
    }
}

public class RemoteServiceValidationErrorInfo
{
    public string Message { get; set; } = default!;
    public string[] Members { get; set; } = default!;
    public RemoteServiceValidationErrorInfo()
    {

    }
    public RemoteServiceValidationErrorInfo(string message)
    {
        Message = message;
    }

    public RemoteServiceValidationErrorInfo(string message, string[] members)
        : this(message)
    {
        Members = members;
    }
    public RemoteServiceValidationErrorInfo(string message, string member)
        : this(message, new[] { member })
    {

    }
}

返回给客户端的类型

public class RemoteServiceErrorResponse
{
    public RemoteServiceErrorInfo Error { get; set; }

    public RemoteServiceErrorResponse(RemoteServiceErrorInfo error)
    {
        Error = error;
    }
}

异常过滤器

abp 对异常的结果的返回最后在过滤器里处理

namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling;

//继承 asp.net core 的 异常过滤器IAsyncExceptionFilter
public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency
{
    public virtual async Task OnExceptionAsync(ExceptionContext context)
    {
        if (!ShouldHandleException(context))
        {
            LogException(context, out _);
            return;
        }

        await HandleAndWrapException(context);
    }

    //异常拦截的条件
    protected virtual bool ShouldHandleException(ExceptionContext context)
    {

        //拦截 ControllerAction 及返回类型是 IActionResult
        if (context.ActionDescriptor.IsControllerAction() &&
            context.ActionDescriptor.HasObjectResult())
        {
            return true;
        }

        //返回类型是 json
        if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
        {
            return true;
        }

        //请求类型是 ajax
        if (context.HttpContext.Request.IsAjax())
        {
            return true;
        }

        //这些都不是异常不拦截
        return false;
    }

    protected virtual async Task HandleAndWrapException(ExceptionContext context)
    {
        //TODO: Trigger an AbpExceptionHandled event or something like that.
        LogException(context, out var remoteServiceErrorInfo);

        //异常通知广播出去
        await context.GetRequiredService<IExceptionNotifier>().NotifyAsync(
          new ExceptionNotificationContext(context.Exception));

        //abp 授权拦截器拦截的异常
        if (context.Exception is AbpAuthorizationException)
        {
            await context.HttpContext.RequestServices.GetRequiredService<
            IAbpAuthorizationExceptionHandler>()
                .HandleAsync(context.Exception.As<AbpAuthorizationException>(),
                 context.HttpContext);
        }
        else
        {
            //业务异常

            //头里添加标记
            context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
            //设置状态吗
            context.HttpContext.Response.StatusCode = (int)context
                .GetRequiredService<IHttpExceptionStatusCodeFinder>()
                .GetStatusCode(context.HttpContext, context.Exception);

            //返回给客户端的 RemoteServiceErrorResponse 异常消息
            //使用asp.net core 的 ObjectResult 进行返回
            context.Result = new ObjectResult(new RemoteServiceErrorResponse(
              remoteServiceErrorInfo));
        }

        context.Exception = null!; //Handled!
    }

    //不进行拦截的异常只做记录
    protected virtual void LogException(ExceptionContext context,
     out RemoteServiceErrorInfo remoteServiceErrorInfo)
    {
        var exceptionHandlingOptions = context.GetRequiredService<
        IOptions<AbpExceptionHandlingOptions>>().Value;
        var exceptionToErrorInfoConverter = context.GetRequiredService<
        IExceptionToErrorInfoConverter>();
        remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(
          context.Exception, options =>
        {
            options.SendExceptionsDetailsToClients =
             exceptionHandlingOptions.SendExceptionsDetailsToClients;
            options.SendStackTraceToClients =
            exceptionHandlingOptions.SendStackTraceToClients;
        });

        var remoteServiceErrorInfoBuilder = new StringBuilder();
        remoteServiceErrorInfoBuilder.AppendLine(
          $"---------- {nameof(RemoteServiceErrorInfo)} ----------");
        remoteServiceErrorInfoBuilder.AppendLine(
          context.GetRequiredService<IJsonSerializer>().
          Serialize(remoteServiceErrorInfo, indented: true));

        var logger = context.GetService<ILogger<AbpExceptionFilter>>(
          NullLogger<AbpExceptionFilter>.Instance)!;
        var logLevel = context.Exception.GetLogLevel();
        logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());
        logger.LogException(context.Exception, logLevel);
    }
}
👍🎉🎊