Обработка ошибок в API ASP.NET Core

Замечание

Это не последняя версия этой статьи. См. версию этой статьи для .NET 10, чтобы узнать о текущем выпуске.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в разделе политика поддержки .NET и .NET Core. См. версию этой статьи для .NET 10, чтобы узнать о текущем выпуске.

В этой статье описывается обработка ошибок в ASP.NET Core API. Выбрана документация по минимальным API. Чтобы просмотреть документацию по API на основе контроллера, перейдите на вкладку Controllers. Инструкции по обработке ошибок Blazor см. в разделе об ошибках Handle в приложениях ASP.NET Core Blazor.

Страница исключений разработчика

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Страница исключений разработчика выполняется в начале конвейера промежуточного ПО, чтобы перехватывать необработанные исключения, возникающие в последующем промежуточном ПО.

ASP.NET Core приложения поддерживают страницу исключений разработчика по умолчанию, если оба:

Приложения, созданные с использованием более ранних шаблонов, то есть с помощью WebHost.CreateDefaultBuilder, могут включить страницу исключений разработчика, вызвав app.UseDeveloperExceptionPage.

Предупреждение

Не включите страницу исключений разработчика, если приложение не запущено в Development среде. Не делитесь подробными сведениями об исключениях публично при запуске приложения в рабочей среде. Дополнительные сведения о настройке сред см. в разделе ASP.NET Core среды выполнения.

Страница исключений разработчика может содержать следующие сведения об исключении и запросе:

  • Трассировка стека
  • Параметры строки запроса, если таковые есть
  • Файлы cookie, если таковые есть
  • Headers
  • Метаданные конечной точки, если таковые есть

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

На следующем рисунке показана пример страницы исключений разработчика с анимацией для отображения вкладок и отображаемых сведений:

Страница исключений разработчика, анимированная для отображения каждой выбранной вкладки.

В ответ на запрос с заголовком страница исключений разработчика возвращает обычный Accept: text/plain текст вместо HTML. Рассмотрим пример.

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

Чтобы просмотреть страницу исключений разработчика в минимальном API:

  • Запустите пример приложения в Development среде.
  • Перейдите к конечной точке /exception .

В этом разделе приведен пример приложения, демонстрирующий способы обработки исключений в минимальном API. Он создает исключение при запросе конечной точки /exception :

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Обработчик исключений

В средах, отличных от среды разработки, используйте промежуточное ПО для обработки исключений для формирования данных об ошибке.

Чтобы настроить exception handler middleware, вызовите UseExceptionHandler. Например, следующий код изменяет приложение так, чтобы оно отправляло клиенту ответ в формате, соответствующем RFC 7807. Дополнительные сведения см. в разделе "Сведения о проблеме" далее в этой статье.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Ответы на ошибки клиента и сервера

Рассмотрим следующее минимальное приложение API.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Эндпоинт /users возвращает 200 OK с представлением json для User, если id больше 0, в противном случае — код состояния 400 BAD REQUEST без тела ответа. Дополнительные сведения о создании ответа см. в статье Создание ответов в приложениях Minimal API.

Status Code Pages middleware можно настроить на формирование единого содержимого тела, если оно пустое, для всех ответов HTTP-клиента (400-499) или сервера (500 -599). ПО промежуточного слоя настраивается путем вызова метода расширения UseStatusCodePages .

Например, в следующем примере приложение настраивается так, чтобы отвечать клиенту полезной нагрузкой, соответствующей RFC 7807, для всех клиентских и серверных ответов, включая ошибки маршрутизации (например, 404 NOT FOUND). Дополнительные сведения см. в разделе "Сведения о проблеме ".

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Сведения о проблеме

Сведения о проблеме — это не единственный формат ответа, описывающий ошибку API HTTP, однако они часто используются для сообщения об ошибках для API HTTP.

Служба сведений о проблеме реализует интерфейс IProblemDetailsService, который поддерживает создание сведений о проблеме в ASP.NET Core. Метод расширения AddProblemDetails(IServiceCollection) на IServiceCollection регистрирует реализацию IProblemDetailsService по умолчанию.

В приложениях ASP.NET Core следующий промежуточный компонент создает HTTP-ответы для сведений о проблемах, когда вызывается AddProblemDetails, за исключением случаев, когда HTTP-заголовок запроса Accept не содержит один из типов контента, поддерживаемых зарегистрированным IProblemDetailsWriter (по умолчанию: application/json):

  • ExceptionHandlerMiddleware: формирует ответ с деталями проблемы, когда пользовательский обработчик не определён.
  • StatusCodePagesMiddleware: по умолчанию создает ответ с деталями проблемы.
  • DeveloperExceptionPageMiddleware: создает ответ сведений о проблеме в разработке, если Accept заголовок HTTP запроса не включает text/html.

Минимальные приложения API можно настроить на создание ответа с подробностями о проблеме для всех HTTP-ответов с ошибками клиента и сервера, тело которых ещё не содержит данных, с помощью метода расширения AddProblemDetails.

Следующий код настраивает приложение для создания сведений о проблеме:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Дополнительные сведения об использовании AddProblemDetailsсм. в разделе "Сведения о проблеме"

Резервный IProblemDetailsService

В следующем коде httpContext.Response.WriteAsync("Fallback: An error occurred.") возвращает ошибку, если реализация IProblemDetailsService не может создать ProblemDetails:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/exception", () =>
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Предыдущий код:

Замечание

Поддерживает DefaultProblemDetailsWriter следующие типы носителей в заголовке Accept запроса:

  • application/json
  • application/problem+json
  • Типы с подстановочными знаками, такие как */* и application/*

Типы носителей, отличные от JSON, например application/xml или text/html, не поддерживаются и запускают резервное поведение.

Следующий пример аналогичен предыдущему, за исключением того, что он вызывает Status Code Pages middleware.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseStatusCodePages(statusCodeHandlerApp =>
{
    statusCodeHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/users/{id:int}", (int id) =>
{
    return id <= 0 ? Results.BadRequest() : Results.Ok(new User(id));
});

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Дополнительные функции обработки ошибок

Миграция с контроллеров на минимальные API

Если вы мигрируете с API на основе контроллера на минимальные API:

  1. Замените фильтры действий на фильтры конечных точек или промежуточное ПО
  2. Замена проверки модели ручной проверкой или пользовательской привязкой
  3. Замените фильтры исключений на промежуточное ПО для обработки исключений
  4. Настройте подробные сведения о проблеме с помощью AddProblemDetails() для единообразных ответов об ошибках

Когда следует использовать обработку ошибок на основе контроллера

При необходимости рассмотрите api на основе контроллера:

  • Сложные сценарии проверки моделей
  • Централизованная обработка исключений между несколькими контроллерами
  • Точное управление форматированием ответа на ошибки
  • Интеграция с функциями MVC, такими как фильтры и соглашения

Подробные сведения об обработке ошибок на основе контроллера, включая ошибки проверки, настройку сведений о проблеме и фильтры исключений, см. в разделах вкладки "Контроллеры ".

Дополнительные ресурсы