feat(api, domain, core): Глобальные модели ошибок. ConfigureValidator - валидатор контроллеров. GlobalExceptionHandler - глобальный обработчик ошибок в API.
This commit is contained in:
51
PARR.API/Infrastructure/ConfigureValidator.cs
Normal file
51
PARR.API/Infrastructure/ConfigureValidator.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.AspNetCore;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Infrastructure.Middleware;
|
||||
|
||||
namespace PARR.API.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// Настройка валидаторов
|
||||
/// </summary>
|
||||
public static class ConfigureValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Подключить конфигурацию валидаторов
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddValidatorServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddExceptionHandler<GlobalExceptionHandler>(); // регим GlobalExceptionHandler
|
||||
services.AddProblemDetails(); // Генерирует стандартный формат ошибки RFC 7807
|
||||
|
||||
// Настройка контроллеров и кастомного формата ошибок валидации
|
||||
services.AddControllers()
|
||||
.ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
options.InvalidModelStateResponseFactory = context =>
|
||||
{
|
||||
// наша стандартная модель ошибок
|
||||
var errors = context.ModelState.Values
|
||||
.SelectMany(t => t.Errors)
|
||||
.Select(t => new ErrorModel { Message= t.ErrorMessage })
|
||||
.ToList();
|
||||
|
||||
return new BadRequestObjectResult( new Response( false, errors));
|
||||
};
|
||||
});
|
||||
|
||||
// Регистрируем валидаторы API.
|
||||
// Просканирует всю сборку API (где лежит Program.cs) и найдет там валидаторы
|
||||
services.AddValidatorsFromAssembly(typeof(Program).Assembly);
|
||||
//services.AddValidatorsFromAssemblies(Assembly.GetExecutingAssembly());
|
||||
|
||||
// Включаем авто-валидацию FluentValidation для контроллеров
|
||||
services.AddFluentValidationAutoValidation();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,73 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.Domain.Exceptions;
|
||||
|
||||
namespace PARR.API.Infrastructure.Middleware
|
||||
{
|
||||
public class GlobalExceptionHandler//: IExceptionHandler
|
||||
/// <summary>
|
||||
/// Глобальная обработка ошибок согласно моих кастомных Exception
|
||||
/// </summary>
|
||||
public class GlobalExceptionHandler : IExceptionHandler
|
||||
{
|
||||
//todo:
|
||||
private readonly ILogger<GlobalExceptionHandler> logger;
|
||||
|
||||
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogError(exception, "Ошибка во время запроса {TraceId}: {Message}", httpContext.TraceIdentifier, exception.Message);
|
||||
|
||||
// определяем статус код, в зависимости от типа исключения
|
||||
|
||||
var statusCode = exception switch
|
||||
{
|
||||
NotFoundException => StatusCodes.Status404NotFound,
|
||||
DbErrorException => StatusCodes.Status502BadGateway,
|
||||
ExternalServiceException => StatusCodes.Status502BadGateway,
|
||||
AppValidationException => StatusCodes.Status400BadRequest,
|
||||
FluentValidation.ValidationException => StatusCodes.Status400BadRequest,
|
||||
AlreadyExistsException => StatusCodes.Status409Conflict,
|
||||
UnauthorizedException => StatusCodes.Status401Unauthorized,
|
||||
ForbiddenException => StatusCodes.Status403Forbidden,
|
||||
//todo: ---------- другие ошибки ----------
|
||||
_ => StatusCodes.Status500InternalServerError
|
||||
};
|
||||
|
||||
// Для критических серверных ошибок (5xx) пишем детальный лог
|
||||
if (statusCode >= 500)
|
||||
{
|
||||
logger.LogError(exception, "Критическая ошибка сервера: {Message}", exception.Message);
|
||||
}
|
||||
|
||||
//var errorMessage = exception.Message;
|
||||
var errors = new List<ErrorModel>();
|
||||
|
||||
// Если это ошибка FluentValidatioun, вытаскиваем детали по каждому полю
|
||||
if (exception is FluentValidation.ValidationException fluentEx)
|
||||
{
|
||||
// ошибка валидации, вернем по каждому полю
|
||||
foreach (var error in fluentEx.Errors)
|
||||
{
|
||||
errors.Add(new ErrorModel { FieldName = error.PropertyName, Message = error.ErrorMessage });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// это не ошибка валидации, вернем сообщение
|
||||
errors.Add(new ErrorModel { Message = exception.Message });
|
||||
}
|
||||
|
||||
httpContext.Response.StatusCode = statusCode;
|
||||
|
||||
var response = new Response<object?>(null, false, errors, exception.Message);
|
||||
|
||||
await httpContext.Response.WriteAsJsonAsync(response, cancellationToken);
|
||||
|
||||
// ошибка обработана
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user