Бекенд на C# Web Api

To Kaiten

Глобальная обработка исключений

Зачем использовать

В Action'ах контроллеров могут возникать непредвиденные ошибки. Эти ошибки можно обрабатывать в блоках try-catch, однако если в приложении много контроллеров, то будет неудобно каждый раз писать один и тот же код для обработки ошибок.

Вместо этого можно создать глобальный обработчик исключений, который будет срабатывать при возникновении исключения в любом action'е из любого контроллера.

Что такое Middleware

Для глобальной обработки исключений используется компонент Middleware. Таких компонентов может быть много, и каждый из них может выполнять свою роль. Эти компоненты обрабатывают HTTP-запросы и ответы на определенных этапах жизненного цикла в ASP.NET Core приложения. Они формируют конвейер обработки запросов, через который проходит каждый запрос к приложению.

Middleware обрабатывают только HTTP запросы, то есть Action'ы в контроллерах. Если в приложении есть, например, фоновая задача, выполняющаяся отдельно от контроллеров, то Middleware не будет обрабатывать эту задачу.

Использование встроенного Middleware

С помощью метода UseExceptionHandler мы можем обработать исключения как в примере ниже:

public static class ExceptionMiddlewareExtensions
{
    public class ErrorDetails
    {
        public int StatusCode { get; set; }
        public string Message { get; set; }
        public override string ToString()
        {
            return JsonSerializer.Serialize(this);
        }
    }

    public static void ConfigureExceptionHandler(this IApplicationBuilder app)
    {
        app.UseExceptionHandler(appError =>
        {
            appError.Run(async context =>
            {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                context.Response.ContentType = "application/json";
                var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                if (contextFeature != null)
                {
                    Log.Error($"Something went wrong: {contextFeature.Error}");
                    await context.Response.WriteAsync(new ErrorDetails()
                    {
                        StatusCode = context.Response.StatusCode,
                        Message = "Internal Server Error."
                    }.ToString());
                }
            });
        });
    }
}

В данном случае мы записываем в лог что произошла ошибка. Вместо этого, можно было бы, например, отправлять уведомления в какой-нибудь мессенджер. И все это делается из одного места в приложении.

Не забудьте в Program вызвать этот метод:

var app = builder.Build();

...

app.ConfigureExceptionHandler();

Использование кастомного Middleware

Вместо встроенного Middleware можно создать собственный. В примере ниже продемонстрирован такой класс:

public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public ExceptionMiddleware(RequestDelegate next)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception ex)
        {
            Log.Error($"Something went wrong: {ex}");
            await HandleExceptionAsync(httpContext, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        await context.Response.WriteAsync(new ErrorDetails()
        {
            StatusCode = context.Response.StatusCode,
            Message = "Internal Server Error from the custom middleware."
        }.ToString());
    }
}

В блоке try мы пытаемся вызвать action контроллера, и в случае ошибки записываем лог. Концептуально, разницы между встроенным и кастомным Middleware нет. Однако кастомный вариант предоставляет больше возможностей для модификации логики.

Также чтобы этот класс заработал нужно зарегистрировать его в Program:

var app = builder.Build();

...

app.UseMiddleware<ExceptionMiddleware>();

Ссылки

Code Maze — Global Error Handling in ASP.NET Core Web API

AleksandrKonst — Middleware. Встроенные в ASP.NET Core


Автор документа: Артём Ветик