ABP异常处理
前言
在ABP中使用UserFriendlyException抛出异常,HTTP状态码为什么是403?下面用这一段测试代码:[HttpPost]
public async Task<PeopleDto> GetPeopleAsync(string sn)
{
//这里只是测试abp的UserFriendlyException异常
if (sn.Equals("123", StringComparison.OrdinalIgnoreCase))
{
throw new UserFriendlyException("测试异常");
}
if (sn.IsNullOrWhiteSpace())
{
throw new UserFriendlyException("查询单号不能为空");
}
IQueryable<People> query = await _peopleRepository.GetQueryableAsync();
query = query.WhereIf(!sn.IsNullOrWhiteSpace(), p => p.SN.Contains(sn)); //这里只是单个条件,真实环境有多个条件
var item = await ObjectMapper.GetMapper().ProjectTo<PeopleDto>(query.AsNoTracking()).FirstOrDefaultAsync();
if (item == null)
{
throw new UserFriendlyException($"没有查询到单号:{sn}数据");
}
return item;
}
在Swagger调用接口:
返回的Http状态码是403,有时候容易觉得是是在没权限调用这个接口.其实并不是.下面学习一下ABP的异常处理.
ABP是在什么时候处理异常的呢?
ABP是在什么时候处理异常的呢?带着这个疑问,在项目中没看到直接处理异常呢(在Asp.Net Core项目,是使用了很多中间件的)? 我们先看一下启动项目中的OnApplicationInitialization有没有Execption相关的中间件.所以只能查看项目的Main代码:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
namespace BookStore.Web;
public class Program
{
public async static Task<int> Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Information()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("Logs/logs.txt"))
.WriteTo.Async(c => c.Console())
.CreateLogger();
try
{
Log.Information("Starting web host.");
var builder = WebApplication.CreateBuilder(args);
builder.Host.AddAppSettingsSecretsJson()
.UseAutofac()
.UseSerilog();
await builder.AddApplicationAsync<BookStoreWebModule>();
var app = builder.Build();
await app.InitializeApplicationAsync(); //看来看去,是不是在这里
await app.RunAsync();
return 0;
}
catch (Exception ex)
{
if (ex is HostAbortedException)
{
throw;
}
Log.Fatal(ex, "Host terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
果然在这里,在AbpApplicationBuilderExtensions.cs看到UseAbpExcptionHandling.
//看到有异常处理的中间件
public static IApplicationBuilder UseAbpExceptionHandling(this IApplicationBuilder app)
{
if (app.Properties.ContainsKey(ExceptionHandlingMiddlewareMarker))
{
return app;
}
app.Properties[ExceptionHandlingMiddlewareMarker] = true;
return app.UseMiddleware<AbpExceptionHandlingMiddleware>(); //看来看去,是不是在这里
}
接着我们继续学习AbpExceptionHandingMiddleware中间件的源码:
public class AbpExceptionHandlingMiddleware : AbpMiddlewareBase, ITransientDependency
{
private readonly ILogger<AbpExceptionHandlingMiddleware> _logger;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
public AbpExceptionHandlingMiddleware(ILogger<AbpExceptionHandlingMiddleware> logger)
{
_logger = logger;
_clearCacheHeadersDelegate = ClearCacheHeaders;
}
public async override Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
//***如果没有异常,处理下一个中间件
await next(context);
}
catch (Exception ex)
{
//***有异常时,捕获异常
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.LogWarning("An exception occurred, but response has already started!");
throw;
}
if (context.Items["_AbpActionInfo"] is AbpActionInfoInHttpContext actionInfo)
{
if (actionInfo.IsObjectResult) //TODO: Align with AbpExceptionFilter.ShouldHandleException!
{
//具体处理异常
await HandleAndWrapException(context, ex);
return;
}
}
throw;
}
}
private async Task HandleAndWrapException(HttpContext httpContext, Exception exception)
{
_logger.LogException(exception);
await httpContext
.RequestServices
.GetRequiredService<IExceptionNotifier>()
.NotifyAsync(
new ExceptionNotificationContext(exception)
);
if (exception is AbpAuthorizationException)
{
await httpContext.RequestServices.GetRequiredService<IAbpAuthorizationExceptionHandler>()
.HandleAsync(exception.As<AbpAuthorizationException>(), httpContext);
}
else
{
var errorInfoConverter = httpContext.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>();
var statusCodeFinder = httpContext.RequestServices.GetRequiredService<IHttpExceptionStatusCodeFinder>(); //***看到Http Exception StatusCode
var jsonSerializer = httpContext.RequestServices.GetRequiredService<IJsonSerializer>();
var exceptionHandlingOptions = httpContext.RequestServices.GetRequiredService<IOptions<AbpExceptionHandlingOptions>>().Value;
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCodeFinder.GetStatusCode(httpContext, exception);
httpContext.Response.OnStarting(_clearCacheHeadersDelegate, httpContext.Response);
httpContext.Response.Headers.Append(AbpHttpConsts.AbpErrorFormat, "true");
httpContext.Response.Headers.Append("Content-Type", "application/json");
await httpContext.Response.WriteAsync(
jsonSerializer.Serialize(
new RemoteServiceErrorResponse(
errorInfoConverter.Convert(exception, options =>
{
options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients;
options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients;
})
)
)
);
}
}
private Task ClearCacheHeaders(object state)
{
var response = (HttpResponse)state;
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}
}
再接着我们看
public class DefaultHttpExceptionStatusCodeFinder : IHttpExceptionStatusCodeFinder, ITransientDependency
{
protected AbpExceptionHttpStatusCodeOptions Options { get; }
public DefaultHttpExceptionStatusCodeFinder(
IOptions<AbpExceptionHttpStatusCodeOptions> options)
{
Options = options.Value;
}
public virtual HttpStatusCode GetStatusCode(HttpContext httpContext, Exception exception)
{
if (exception is IHasHttpStatusCode exceptionWithHttpStatusCode &&
exceptionWithHttpStatusCode.HttpStatusCode > 0)
{
return (HttpStatusCode)exceptionWithHttpStatusCode.HttpStatusCode;
}
if (exception is IHasErrorCode exceptionWithErrorCode &&
!exceptionWithErrorCode.Code.IsNullOrWhiteSpace())
{
if (Options.ErrorCodeToHttpStatusCodeMappings.TryGetValue(exceptionWithErrorCode.Code!, out var status))
{
return status;
}
}
if (exception is AbpAuthorizationException)
{
return httpContext.User.Identity!.IsAuthenticated
? HttpStatusCode.Forbidden
: HttpStatusCode.Unauthorized;
}
//TODO: Handle SecurityException..?
if (exception is AbpValidationException)
{
return HttpStatusCode.BadRequest; //400
}
if (exception is EntityNotFoundException)
{
return HttpStatusCode.NotFound; //4040
}
if (exception is AbpDbConcurrencyException)
{
return HttpStatusCode.Conflict; //409
}
if (exception is NotImplementedException)
{
return HttpStatusCode.NotImplemented; //501
}
if (exception is IBusinessException)
{
return HttpStatusCode.Forbidden; //403 符合我们使用UserFriendlyException响应的状态码
}
return HttpStatusCode.InternalServerError; //500
}
}
看到403状态码对应的异常,是IBusinessException,我们在回到UserFriendlyException,对UserFriendlyException源码进行查看.
//继承BusinessException,实现IUserFriendException
[Serializable]
public class UserFriendlyException : BusinessException, IUserFriendlyException
{
}
//IUserFriendlyException 实现IBusinessException接口
public interface IUserFriendlyException : IBusinessException
{
}
到这里基本上,弄明白UserFriendlyException是如何返回403状态码的.怎么对UseAbpExceptionHandling启用呢?,查找源码发现是在启用工作单元之前,先启用的UseAbpExceptionHandling.
public static IApplicationBuilder UseUnitOfWork(this IApplicationBuilder app)
{
return app
.UseAbpExceptionHandling() //先启用异常处理的中间件
.UseMiddleware<AbpUnitOfWorkMiddleware>(); //启用工作单元的中间件
}
这样就和Module中OnApplicationInitialization中启用工作单元中间件衔接起来了.
app.UseUnitOfWork(); //使用工作单元中间件
秋风
2024-08-06