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);
}
}
👍🎉🎊