Глобальная обработка ошибок: IExceptionHandler, middleware
↑ 3.5 Инфраструктура приложения: ошибки, health checks, фоновые задачи · → Следующая
Зачем это на собесе
«Как в вашем API обрабатываются исключения?» — ждут единое место, ProblemDetails и отсутствие утечки деталей.
Объяснение
Исключения не должны обрабатываться в каждом action try/catch. Один обработчик на входе конвейера преобразует их в ответ.
public class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> log) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext ctx, Exception ex, CancellationToken ct)
{
var (status, title) = ex switch
{
NotFoundException => (404, "Not found"),
ConflictException => (409, "Conflict"),
ValidationException => (422, "Validation failed"),
_ => (500, "Unexpected error"),
};
if (status == 500) log.LogError(ex, "Unhandled exception {TraceId}", ctx.TraceIdentifier);
ctx.Response.StatusCode = status;
await ctx.Response.WriteAsJsonAsync(new ProblemDetails { Status = status, Title = title, Extensions = { ["traceId"] = ctx.TraceIdentifier } }, ct);
return true; // обработано
}
}
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();| Способ | Комментарий |
|---|---|
IExceptionHandler (.NET 8+) | рекомендуется, цепочка обработчиков |
UseExceptionHandler + lambda | простой вариант |
| Exception filter MVC | только MVC, не ловит middleware |
| Result pattern | ожидаемые ошибки без исключений |
Нюансы и подводные камни
- Исключения для ожидаемых бизнес-ситуаций дороги и «шумят»: рассмотрите
Result<T>. - Не возвращайте
ex.Messageи стек клиенту в проде. UseExceptionHandlerставьте первым.- Логируйте один раз: обработчик логирует, остальные слои не дублируют.
OperationCanceledExceptionиз-за отмены клиента — не ошибка (499/без ответа).
Практика
- Добавьте
IExceptionHandlerи маппинг доменных исключений в коды. - Добавьте
traceIdв ответ и найдите его в логах. - Замените исключения на
Result<T>в одном сценарии.
Вопросы с ответами
Где обрабатывать исключения в ASP.NET Core?
В
IExceptionHandler/UseExceptionHandlerв начале конвейера, единый формат ProblemDetails.
Что вернуть клиенту при 500?
Общее сообщение и
traceId; детали — только в логах.
Исключения или Result?
Исключения — для неожиданного; ожидаемые сбои (валидация, не найдено) удобнее и дешевле как значения.