ABP 异常处理 第四篇

时间:2021-09-10 02:37:22

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 > )
{
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 异常处理 第四篇

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 //
: (int)HttpStatusCode.Unauthorized; //
} if (context.Exception is AbpValidationException)
{
return (int)HttpStatusCode.BadRequest; //
} if (context.Exception is EntityNotFoundException)
{
return (int)HttpStatusCode.NotFound; //
} return (int)HttpStatusCode.InternalServerError; //
}
}

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

{
"result": {
"totalCount": ,
"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}");
}
}