Obsługa błędów w interfejsach API ASP.NET Core

Uwaga / Notatka

Nie jest to najnowsza wersja tego artykułu. Aby uzyskać bieżącą wersję, zobacz artykuł w wersji .NET 10.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy. Aby uzyskać bieżącą wersję, zobacz artykuł w wersji .NET 10.

W tym artykule opisano sposób obsługi błędów w interfejsach API ASP.NET Core. Wybrano dokumentację dla minimalnych interfejsów API. Aby wyświetlić dokumentację interfejsów API opartych na kontrolerach, wybierz kartę Controllers. Aby uzyskać wskazówki dotyczące obsługi błędów Blazor, zobacz Obsługi błędów w ASP.NET Core Blazor apps.

Strona wyjątku dla deweloperów

Na Stronie wyjątków dewelopera wyświetlane są szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Używa DeveloperExceptionPageMiddleware do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP oraz generowania odpowiedzi błędów. Strona wyjątku dla deweloperów jest uruchamiana na wczesnym etapie pipeline'u pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w oprogramowaniu pośredniczącym, które następuje.

Aplikacje ASP.NET Core włączają domyślnie stronę wyjątków dla deweloperów, gdy spełnione są oba poniższe warunki:

Aplikacje utworzone przy użyciu wcześniejszych szablonów, czyli przy użyciu metody WebHost.CreateDefaultBuilder, mogą włączyć stronę wyjątku dla deweloperów, wywołując metodę app.UseDeveloperExceptionPage.

Ostrzeżenie

Nie włączaj strony wyjątków dla programistów, chyba że aplikacja jest uruchomiona w Development środowisku. Nie udostępniaj publicznie szczegółowych informacji o wyjątkach, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz ASP.NET Core środowiska uruchomieniowe.

Strona wyjątku dewelopera może zawierać następujące informacje o wyjątku i żądaniu:

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli występują
  • Nagłówki
  • Metadane punktu końcowego, jeśli istnieją

Strona wyjątków dla deweloperów nie gwarantuje, że poda jakiekolwiek informacje. Użyj logowania, aby uzyskać pełne informacje o błędach.

Na poniższej ilustracji przedstawiono przykładową stronę wyjątków dla deweloperów z animacją pokazującą karty oraz prezentowane informacje.

Strona wyjątku dla deweloperów jest animowana, aby pokazać wybór każdej karty.

W odpowiedzi na żądanie z nagłówkiem Accept: text/plain strona wyjątków dla dewelopera zwraca zwykły tekst zamiast HTML. Przykład:

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

Aby wyświetlić stronę wyjątków dla deweloperów w Minimal API:

W tej sekcji opisano następującą przykładową aplikację, aby zademonstrować sposoby obsługi wyjątków w minimalnym interfejsie API. Zgłasza wyjątek w przypadku żądania punktu końcowego /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();

Mechanizm obsługi wyjątków

W środowiskach innych niż deweloperskie użyj middleware obsługi wyjątków, aby wygenerować treść błędu.

Aby skonfigurować exception handler middleware, wywołaj UseExceptionHandler. Na przykład poniższy kod zmienia działanie aplikacji tak, aby odpowiadała klientowi treścią zgodną ze specyfikacją RFC 7807. Aby uzyskać więcej informacji, zobacz sekcję Szczegóły problemu w dalszej części tego artykułu.

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();

Odpowiedzi na błędy klienta i serwera

Rozważmy następującą minimalną aplikację interfejsu 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);

Punkt końcowy /users zwraca 200 OK z reprezentacją json elementu User, gdy id jest większe niż 0, w przeciwnym razie kod stanu 400 BAD REQUEST bez treści odpowiedzi. Aby uzyskać więcej informacji na temat tworzenia odpowiedzi, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.

Element Status Code Pages middleware można skonfigurować tak, aby generował wspólną treść komunikatu, gdy jest ona pusta, dla wszystkich odpowiedzi HTTP klienta (400-499) lub serwera (500 -599). Oprogramowanie pośredniczące jest konfigurowane przez wywołanie metody rozszerzenia UseStatusCodePages .

Na przykład poniższy przykład zmienia aplikację tak, aby zwracała klientowi ładunek zgodny z RFC 7807 dla wszystkich odpowiedzi po stronie klienta i serwera, w tym błędów routingu (na przykład 404 NOT FOUND). Aby uzyskać więcej informacji, zobacz sekcję Szczegóły problemu .

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);

Szczegóły problemu

Szczegóły problemu nie są jedynym formatem odpowiedzi opisujący błąd interfejsu API HTTP, jednak są one często używane do zgłaszania błędów dla interfejsów API HTTP.

Usługa szczegółów problemu implementuje interfejs IProblemDetailsService, który obsługuje tworzenie szczegółów problemu w ASP.NET Core. Metoda rozszerzenia AddProblemDetails(IServiceCollection) na IServiceCollection rejestruje domyślną implementację IProblemDetailsService.

W aplikacjach ASP.NET Core następujące middleware generuje szczegóły dotyczące problemów w odpowiedziach HTTP po wywołaniu AddProblemDetails, z wyjątkiem sytuacji, gdy nagłówek żądania HTTP Accept nie zawiera jednego z typów zawartości obsługiwanych przez zarejestrowany IProblemDetailsWriter (domyślnie: application/json):

Aplikacje Minimal API można skonfigurować tak, aby generowały odpowiedzi ze szczegółami problemu dla wszystkich odpowiedzi błędów klienta i serwera HTTP, które nie zawierają jeszcze treści odpowiedzi, za pomocą metody rozszerzającej AddProblemDetails.

Poniższy kod konfiguruje aplikację w celu wygenerowania szczegółów problemu:

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);

Aby uzyskać więcej informacji na temat korzystania z programu AddProblemDetails, zobacz Szczegóły problemu

Zapasowa usługa IProblemDetailsService

W poniższym kodzie httpContext.Response.WriteAsync("Fallback: An error occurred.") zwraca błąd, jeśli implementacja IProblemDetailsService nie jest w stanie wygenerować obiektu 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();

Poprzedni kod:

  • Zapisuje komunikat o błędzie z kodem zastępczym, jeśli problemDetailsService nie może zapisać elementu ProblemDetails. Na przykład punkt końcowy, w którym nagłówek żądania Accept określa typ multimediów, którego element DefaultProblemDetailsWriter nie obsługuje.
  • Używa middleware obsługi wyjątków.

Uwaga / Notatka

Element DefaultProblemDetailsWriter obsługuje następujące typy nośników w nagłówku żądania Accept:

  • application/json
  • application/problem+json
  • Typy symboli wieloznacznych, takie jak */* i application/*

Typy multimediów inne niż JSON, takie jak application/xml lub text/html, nie są obsługiwane i powodują uruchomienie mechanizmu zastępczego.

Poniższy przykład jest podobny do poprzedniego, z wyjątkiem tego, że wywołuje 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);

Dodatkowe funkcje obsługi błędów

Migracja z kontrolerów do minimalnych interfejsów API

Jeśli migrujesz z interfejsów API opartych na kontrolerach do minimalnych interfejsów API:

  1. Zastępowanie filtrów akcji filtrami punktów końcowych lub oprogramowaniem pośredniczącym
  2. Zastąp walidację modelu walidacją ręczną lub niestandardowym wiązaniem
  3. Zastępowanie filtrów wyjątków oprogramowaniem pośredniczącym obsługującym wyjątki
  4. Konfigurowanie szczegółów problemu przy użyciu AddProblemDetails() funkcji spójnych odpowiedzi na błędy

Kiedy należy używać obsługi błędów opartych na kontrolerze

W razie potrzeby rozważ użycie interfejsów API opartych na kontrolerze:

  • Złożone scenariusze weryfikacji modelu
  • Scentralizowana obsługa wyjątków na wielu kontrolerach
  • Szczegółowa kontrola nad formatowaniem odpowiedzi na błędy
  • Integracja z funkcjami MVC, takimi jak filtry i konwencje

Aby uzyskać szczegółowe informacje na temat obsługi błędów opartych na kontrolerze, w tym błędów walidacji, dostosowywania szczegółów problemu i filtrów wyjątków, zobacz sekcje kart Kontrolery .