• ABP 异常处理 第四篇


    1、ABP异常处理机制是通过过滤器实现的,我们查看的webAPI的异常处理,我们来看看他的源码,AbpApiExceptionFilterAttribute 继承ExceptionFilterAttribute, ITransientDependency

        public class AbpApiExceptionFilterAttribute : ExceptionFilterAttribute, ITransientDependency
        {
            /// <summary>
            /// Reference to the <see cref="ILogger"/>.
            /// </summary>
            public ILogger Logger { get; set; }
    
            /// <summary>
            /// Reference to the <see cref="IEventBus"/>.
            /// </summary>
            public IEventBus EventBus { get; set; }
    
            public IAbpSession AbpSession { get; set; }
    
            protected IAbpWebApiConfiguration Configuration { get; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="AbpApiExceptionFilterAttribute"/> class.
            /// </summary>
            public AbpApiExceptionFilterAttribute(IAbpWebApiConfiguration configuration)
            {
                Configuration = configuration;
                Logger = NullLogger.Instance;
                EventBus = NullEventBus.Instance;
                AbpSession = NullAbpSession.Instance;
            }
    
            /// <summary>
            /// Raises the exception event.
            /// </summary>
            /// <param name="context">The context for the action.</param>
            public override void OnException(HttpActionExecutedContext context)
            {
                var wrapResultAttribute = HttpActionDescriptorHelper
                    .GetWrapResultAttributeOrNull(context.ActionContext.ActionDescriptor) ??
                    Configuration.DefaultWrapResultAttribute;
    
                if (wrapResultAttribute.LogError)
                {
                    LogHelper.LogException(Logger, context.Exception);
                }
    
                if (!wrapResultAttribute.WrapOnError)
                {
                    return;
                }
    
                if (IsIgnoredUrl(context.Request.RequestUri))
                {
                    return;
                }
       
                if (context.Exception is HttpException)
                {
                    var httpException = context.Exception as HttpException;
                    var httpStatusCode = (HttpStatusCode) httpException.GetHttpCode();
    
                    context.Response = context.Request.CreateResponse(
                        httpStatusCode,
                        new AjaxResponse(
                            new ErrorInfo(httpException.Message),
                            httpStatusCode == HttpStatusCode.Unauthorized || httpStatusCode == HttpStatusCode.Forbidden
                        )
                    );
                }
                else
                {
                    context.Response = context.Request.CreateResponse(
                        GetStatusCode(context),
                        new AjaxResponse(
                            SingletonDependency<IErrorInfoBuilder>.Instance.BuildForException(context.Exception),
                            context.Exception is Abp.Authorization.AbpAuthorizationException)
                    );
                }
    
                EventBus.Trigger(this, new AbpHandledExceptionData(context.Exception));
            }
    
            protected virtual HttpStatusCode GetStatusCode(HttpActionExecutedContext context)
            {
                if (context.Exception is Abp.Authorization.AbpAuthorizationException)
                {
                    return AbpSession.UserId.HasValue
                        ? HttpStatusCode.Forbidden
                        : HttpStatusCode.Unauthorized;
                }
    
                if (context.Exception is AbpValidationException)
                {
                    return HttpStatusCode.BadRequest;
                }
    
                if (context.Exception is EntityNotFoundException)
                {
                    return HttpStatusCode.NotFound;
                }
    
                return HttpStatusCode.InternalServerError;
            }
    
            protected virtual bool IsIgnoredUrl(Uri uri)
            {
                if (uri == null || uri.AbsolutePath.IsNullOrEmpty())
                {
                    return false;
                }
    
                return Configuration.ResultWrappingIgnoreUrls.Any(url => uri.AbsolutePath.StartsWith(url));
            }
        }

    我们看看他怎么处理异常的,他主要通过这个方法   SingletonDependency<IErrorInfoBuilder>.Instance.BuildForException(context.Exception)

    首先我们先了解他返回的数据结构,AjaxResponse,他的基类是AjaxResponseBase,ABP所有接口的返回值的都是封装到这个类里

     public abstract class AjaxResponseBase
        {
            /// <summary>
            /// This property can be used to redirect user to a specified URL.
            /// </summary>
            public string TargetUrl { get; set; }
    
            /// <summary>
            /// Indicates success status of the result.
            /// Set <see cref="Error"/> if this value is false.
            /// </summary>
            public bool Success { get; set; }
    
            /// <summary>
            /// Error details (Must and only set if <see cref="Success"/> is false).
            /// </summary>
            public ErrorInfo Error { get; set; }
    
            /// <summary>
            /// This property can be used to indicate that the current user has no privilege to perform this request.
            /// </summary>
            public bool UnAuthorizedRequest { get; set; }
    
            /// <summary>
            /// A special signature for AJAX responses. It's used in the client to detect if this is a response wrapped by ABP.
            /// </summary>
            public bool __abp { get; } = true;
        }

    属性error主要用来封装错误信息的,他的属性包含如下

      /// <summary>
            /// Error code.
            /// </summary>
            public int Code { get; set; }
    
            /// <summary>
            /// Error message.
            /// </summary>
            public string Message { get; set; }
    
            /// <summary>
            /// Error details.
            /// </summary>
            public string Details { get; set; }
    
            /// <summary>
            /// Validation errors if exists.
            /// </summary>
            public ValidationErrorInfo[] ValidationErrors { get; set; }
    ValidationErrors   这个属性用来保存验证信息的,他的主要属性如下
      /// <summary>
            /// Validation error message.
            /// </summary>
            public string Message { get; set; }
    
            /// <summary>
            /// Relate invalid members (fields/properties).
            /// </summary>
            public string[] Members { get; set; }

    以上是异常信息返回的数据结构,我们来看看他的核心代码,主要是ErrorInfoBuilder

        public class ErrorInfoBuilder : IErrorInfoBuilder, ISingletonDependency
        {
            private IExceptionToErrorInfoConverter Converter { get; set; }
    
            /// <inheritdoc/>
            public ErrorInfoBuilder(IAbpWebCommonModuleConfiguration configuration, ILocalizationManager localizationManager)
            {
                Converter = new DefaultErrorInfoConverter(configuration, localizationManager);
            }
    
            /// <inheritdoc/>
            public ErrorInfo BuildForException(Exception exception)
            {
                return Converter.Convert(exception);
            }
    
            /// <summary>
            /// Adds an exception converter that is used by <see cref="BuildForException"/> method.
            /// </summary>
            /// <param name="converter">Converter object</param>
            public void AddExceptionConverter(IExceptionToErrorInfoConverter converter)
            {
                converter.Next = Converter;
                Converter = converter;
            }
        }
     Converter.Convert(exception);  这个主要封装不同的异常信息,并且把它返回,返回类型是ErrorInfo,他的实现类是DefaultErrorInfoConvert,代码如下:
        internal class DefaultErrorInfoConverter : IExceptionToErrorInfoConverter
        {
            private readonly IAbpWebCommonModuleConfiguration _configuration;
            private readonly ILocalizationManager _localizationManager;
    
            public IExceptionToErrorInfoConverter Next { set; private get; }
    
            private bool SendAllExceptionsToClients
            {
                get
                {
                    return _configuration.SendAllExceptionsToClients;
                }
            }
    
            public DefaultErrorInfoConverter(
                IAbpWebCommonModuleConfiguration configuration,
                ILocalizationManager localizationManager)
            {
                _configuration = configuration;
                _localizationManager = localizationManager;
            }
    
            public ErrorInfo Convert(Exception exception)
            {
                var errorInfo = CreateErrorInfoWithoutCode(exception);
    
                if (exception is IHasErrorCode)
                {
                    errorInfo.Code = (exception as IHasErrorCode).Code;
                }
    
                return errorInfo;
            }
    
            private ErrorInfo CreateErrorInfoWithoutCode(Exception exception)
            {
                if (SendAllExceptionsToClients)
                {
                    return CreateDetailedErrorInfoFromException(exception);
                }
    
                if (exception is AggregateException && exception.InnerException != null)
                {
                    var aggException = exception as AggregateException;
                    if (aggException.InnerException is UserFriendlyException ||
                        aggException.InnerException is AbpValidationException)
                    {
                        exception = aggException.InnerException;
                    }
                }
    
                if (exception is UserFriendlyException)
                {
                    var userFriendlyException = exception as UserFriendlyException;
                    return new ErrorInfo(userFriendlyException.Message, userFriendlyException.Details);
                }
    
                if (exception is AbpValidationException)
                {
                    return new ErrorInfo(L("ValidationError"))
                    {
                        ValidationErrors = GetValidationErrorInfos(exception as AbpValidationException),
                        Details = GetValidationErrorNarrative(exception as AbpValidationException)
                    };
                }
    
                if (exception is EntityNotFoundException)
                {
                    var entityNotFoundException = exception as EntityNotFoundException;
    
                    if (entityNotFoundException.EntityType != null)
                    {
                        return new ErrorInfo(
                            string.Format(
                                L("EntityNotFound"),
                                entityNotFoundException.EntityType.Name,
                                entityNotFoundException.Id
                            )
                        );
                    }
    
                    return new ErrorInfo(
                        entityNotFoundException.Message
                    );
                }
    
                if (exception is Abp.Authorization.AbpAuthorizationException)
                {
                    var authorizationException = exception as Abp.Authorization.AbpAuthorizationException;
                    return new ErrorInfo(authorizationException.Message);
                }
    
                return new ErrorInfo(L("InternalServerError"));
            }
    
            private ErrorInfo CreateDetailedErrorInfoFromException(Exception exception)
            {
                var detailBuilder = new StringBuilder();
    
                AddExceptionToDetails(exception, detailBuilder);
    
                var errorInfo = new ErrorInfo(exception.Message, detailBuilder.ToString());
    
                if (exception is AbpValidationException)
                {
                    errorInfo.ValidationErrors = GetValidationErrorInfos(exception as AbpValidationException);
                }
    
                return errorInfo;
            }
    
            private void AddExceptionToDetails(Exception exception, StringBuilder detailBuilder)
            {
                //Exception Message
                detailBuilder.AppendLine(exception.GetType().Name + ": " + exception.Message);
    
                //Additional info for UserFriendlyException
                if (exception is UserFriendlyException)
                {
                    var userFriendlyException = exception as UserFriendlyException;
                    if (!string.IsNullOrEmpty(userFriendlyException.Details))
                    {
                        detailBuilder.AppendLine(userFriendlyException.Details);
                    }
                }
    
                //Additional info for AbpValidationException
                if (exception is AbpValidationException)
                {
                    var validationException = exception as AbpValidationException;
                    if (validationException.ValidationErrors.Count > 0)
                    {
                        detailBuilder.AppendLine(GetValidationErrorNarrative(validationException));
                    }
                }
    
                //Exception StackTrace
                if (!string.IsNullOrEmpty(exception.StackTrace))
                {
                    detailBuilder.AppendLine("STACK TRACE: " + exception.StackTrace);
                }
    
                //Inner exception
                if (exception.InnerException != null)
                {
                    AddExceptionToDetails(exception.InnerException, detailBuilder);
                }
    
                //Inner exceptions for AggregateException
                if (exception is AggregateException)
                {
                    var aggException = exception as AggregateException;
                    if (aggException.InnerExceptions.IsNullOrEmpty())
                    {
                        return;
                    }
    
                    foreach (var innerException in aggException.InnerExceptions)
                    {
                        AddExceptionToDetails(innerException, detailBuilder);
                    }
                }
            }
    
            private ValidationErrorInfo[] GetValidationErrorInfos(AbpValidationException validationException)
            {
                var validationErrorInfos = new List<ValidationErrorInfo>();
    
                foreach (var validationResult in validationException.ValidationErrors)
                {
                    var validationError = new ValidationErrorInfo(validationResult.ErrorMessage);
    
                    if (validationResult.MemberNames != null && validationResult.MemberNames.Any())
                    {
                        validationError.Members = validationResult.MemberNames.Select(m => m.ToCamelCase()).ToArray();
                    }
    
                    validationErrorInfos.Add(validationError);
                }
    
                return validationErrorInfos.ToArray();
            }
    
            private string GetValidationErrorNarrative(AbpValidationException validationException)
            {
                var detailBuilder = new StringBuilder();
                detailBuilder.AppendLine(L("ValidationNarrativeTitle"));
    
                foreach (var validationResult in validationException.ValidationErrors)
                {
                    detailBuilder.AppendFormat(" - {0}", validationResult.ErrorMessage);
                    detailBuilder.AppendLine();
                }
    
                return detailBuilder.ToString();
            }
    
            private string L(string name)
            {
                try
                {
                    return _localizationManager.GetString(AbpWebConsts.LocalizaionSourceName, name);
                }
                catch (Exception)
                {
                    return name;
                }
            }
        }


    以上ABP异常处理的源码分析,代码中我们实际使用如下:

    异常ABP异常分类

    ABP的异常基类源代码 ABPException的源代码如下

       /// <summary>
        /// Base exception type for those are thrown by Abp system for Abp specific exceptions.
        /// </summary>
        [Serializable]
        public class AbpException : Exception
        {
            /// <summary>
            /// Creates a new <see cref="AbpException"/> object.
            /// </summary>
            public AbpException()
            {
    
            }
    
            /// <summary>
            /// Creates a new <see cref="AbpException"/> object.
            /// </summary>
            public AbpException(SerializationInfo serializationInfo, StreamingContext context)
                : base(serializationInfo, context)
            {
    
            }
    
            /// <summary>
            /// Creates a new <see cref="AbpException"/> object.
            /// </summary>
            /// <param name="message">Exception message</param>
            public AbpException(string message)
                : base(message)
            {
    
            }
    
            /// <summary>
            /// Creates a new <see cref="AbpException"/> object.
            /// </summary>
            /// <param name="message">Exception message</param>
            /// <param name="innerException">Inner exception</param>
            public AbpException(string message, Exception innerException)
                : base(message, innerException)
            {
    
            }
        }

    ABP通过ABPExceptionFilter拦截异常的源代码

        public class AbpExceptionFilter : IExceptionFilter, ITransientDependency
        {
            //日志记录
            public ILogger Logger { get; set; }
    
            //事件总线
            public IEventBus EventBus { get; set; }
    
            // 错误信息构建器
            private readonly IErrorInfoBuilder _errorInfoBuilder;
    
            // AspNetCore 相关的配置信息
            private readonly IAbpAspNetCoreConfiguration _configuration;
    
            public AbpExceptionFilter(IErrorInfoBuilder errorInfoBuilder, IAbpAspNetCoreConfiguration configuration)
            {
                _errorInfoBuilder = errorInfoBuilder;
                _configuration = configuration;
    
                Logger = NullLogger.Instance;
                EventBus = NullEventBus.Instance;
            }
    
            public void OnException(ExceptionContext context)
            {
                if (!context.ActionDescriptor.IsControllerAction())
                {
                    return;
                }
    
                // // 获得方法的包装特性。决定后续操作,如果没有指定包装特性,则使用默认特性
                var wrapResultAttribute =
                    ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
                        context.ActionDescriptor.GetMethodInfo(),
                        _configuration.DefaultWrapResultAttribute
                    );
    
                //需要记录日志
                if (wrapResultAttribute.LogError)
                {
                    LogHelper.LogException(Logger, context.Exception);
                }
    
                //重构异常错误处理
                if (wrapResultAttribute.WrapOnError)
                {
                    HandleAndWrapException(context);
                }
            }
    
            /// <summary>
            /// 包装异常
            /// </summary>
            /// <param name="context"></param>
            protected virtual void HandleAndWrapException(ExceptionContext context)
            {
                //判断返回类型是否是objectresult,不是直接返回
                if (!ActionResultHelper.IsObjectResult(context.ActionDescriptor.GetMethodInfo().ReturnType))
                {
                    return;
                }
    
                // 设置 HTTP 上下文响应所返回的错误代码,由具体异常决定。
                context.HttpContext.Response.StatusCode = GetStatusCode(context);
    
                context.Result = new ObjectResult(
                    new AjaxResponse(
                        _errorInfoBuilder.BuildForException(context.Exception),
                        context.Exception is AbpAuthorizationException
                    )
                );
    
                //// 触发异常处理事件
                EventBus.Trigger(this, new AbpHandledExceptionData(context.Exception));
    
    
                // 处理完成,将异常上下文的内容置为空
                context.Exception = null; //Handled!
            }
    
    
            //根据不同的异常返回不同的status
            protected virtual int GetStatusCode(ExceptionContext context)
            {
                if (context.Exception is AbpAuthorizationException)
                {
                    return context.HttpContext.User.Identity.IsAuthenticated
                        ? (int)HttpStatusCode.Forbidden //403
                        : (int)HttpStatusCode.Unauthorized; //401
                }
    
                if (context.Exception is AbpValidationException)
                {
                    return (int)HttpStatusCode.BadRequest;  //400
                }
    
                if (context.Exception is EntityNotFoundException)
                {
                    return (int)HttpStatusCode.NotFound; //404
                }
    
                return (int)HttpStatusCode.InternalServerError;  //500
            }
        }

    ABP异常处理包装返回的结果结构,如果不想包装,请使用特性 DontWarpResult

    {
      "result": {
        "totalCount": 0,
        "items": []
      },
      "targetUrl": null,
      "success": true,
      "error": null,
      "unAuthorizedRequest": false,
      "__abp": true
    }

    ABP正常接口返回的数据结构如下

    public abstract class AjaxResponseBase
    {
        // 目标 Url 地址
        public string TargetUrl { get; set; }
    
        // 接口调用是否成功
        public bool Success { get; set; }
    
        // 当接口调用失败时,错误信息存放在此处
        public ErrorInfo Error { get; set; }
    
        // 是否是未授权的请求
        public bool UnAuthorizedRequest { get; set; }
    
        // 用于标识接口是否基于 Abp 框架开发
        public bool __abp { get; } = true;
    }

    显示额外的异常处理,需要在模块预处理的代码中加入:

    Configuration.Modules.AbpWebCommon().SendAllExceptionsToClients = true;

    实时监听异常处理,如果需要发生报警,重写异常处理事件

    public class ExceptionEventHandler : IEventHandler, ITransientDependency
    {
        /// 
    
        /// Handler handles the event by implementing this method.
        /// 
    
        /// Event data
        public void HandleEvent(AbpHandledExceptionData eventData)
        {
            Console.WriteLine($"当前异常信息为:{eventData.Exception.Message}");
        }
    }
  • 相关阅读:
    Raid卡在Write back 与Write through 时的性能差异
    mysql 的outfile以及infile 语法简单备份恢复表
    @SneakyThrows
    java中的mmap实现--转
    以ATT&CK为例构建网络安全知识图
    横向移动攻击点与识别
    Tomcat开启JMX监控
    mysql serverTimezone
    自增还是UUID?数据库主键的类型选择,为啥不能用uuid做MySQL的主键?
    数据库:查询结果中增加数据库不存在的字段的方法
  • 原文地址:https://www.cnblogs.com/topguntopgun/p/9630913.html
Copyright © 2020-2023  润新知