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}"); } }