Jak obsługiwać błędy w minimalnych aplikacjach interfejsu API

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Z udziałem DavidA Ackera

W tym artykule opisano sposób obsługi błędów w minimalnych aplikacjach interfejsu API.

Wyjątki

W minimalnej aplikacji interfejsu API istnieją dwa różne wbudowane mechanizmy scentralizowane do obsługi nieobsługiwane wyjątki:

W tej sekcji opisano następującą minimalną aplikację interfejsu API, aby zademonstrować sposoby obsługi wyjątków. 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();

Strona wyjątku dla deweloperów

Na stronie wyjątku dla deweloperów są wyświetlane szczegółowe ślady stosu błędów serwera. Używa DeveloperExceptionPageMiddleware go do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP i generowania odpowiedzi o błędach.

aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

Aby uzyskać więcej informacji na temat konfigurowania oprogramowania pośredniczącego, zobacz Oprogramowanie pośredniczące w minimalnych aplikacjach interfejsu API.

Korzystając z poprzedniej aplikacji interfejsu API Minimalne, gdy Developer Exception Page program wykryje nieobsługiwany wyjątek, generuje domyślną odpowiedź w postaci zwykłego tekstu podobną do następującego przykładu:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked

    System.InvalidOperationException: Sample Exception
    at Program.<>c.<<Main>$>b__0_1() in ....:line 17
    at lambda_method2(Closure, Object, HttpContext)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br

Ostrzeżenie

Nie włączaj strony wyjątku dla deweloperów, chyba że aplikacja jest uruchomiona w środowisku dewelopera. 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 Używanie wielu środowisk w programie ASP.NET Core.

Procedura obsługi wyjątków

W środowiskach nieprodukcyjnych użyj oprogramowania pośredniczącego obsługi wyjątków, aby wygenerować ładunek błędu. Aby skonfigurować metodę , wywołaj metodę Exception Handler MiddlewareUseExceptionHandler.

Na przykład poniższy kod zmienia aplikację w odpowiedzi na ładunek zgodny ze specyfikacją RFC 7807 do klienta. Aby uzyskać więcej informacji, zobacz sekcję Szczegóły problemu.

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 /users końcowy tworzy 200 OK z reprezentacją Userjson wartości , gdy id jest większa niż 0, w przeciwnym razie 400 BAD REQUEST kod stanu bez treści odpowiedzi. Aby uzyskać więcej informacji na temat tworzenia odpowiedzi, zobacz Tworzenie odpowiedzi w minimalnych aplikacjach interfejsu API.

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

Na przykład w poniższym przykładzie aplikacja zmienia odpowiedź na ładunek zgodny ze specyfikacją RFC 7807 do klienta dla wszystkich odpowiedzi klienta i serwera, w tym błędy 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 IProblemDetailsService implementuje interfejs, który obsługuje tworzenie szczegółów problemu w ASP.NET Core. Metoda AddProblemDetails rozszerzenia w systemie IServiceCollection rejestruje domyślną IProblemDetailsService implementację.

W aplikacjach ASP.NET Core następujące oprogramowanie pośredniczące generuje szczegóły problemów odpowiedzi HTTP podczas AddProblemDetails wywoływana, z wyjątkiem sytuacji, gdyAcceptnagłówek HTTP żądania nie zawiera jednego z typów zawartości obsługiwanych przez zarejestrowane IProblemDetailsWriter (domyślnie: application/json):

Minimalne aplikacje interfejsu API można skonfigurować do generowania odpowiedzi na szczegóły problemu dla wszystkich odpowiedzi na błędy klienta HTTP i serwera, które nie mają jeszcze zawartości treści przy użyciu AddProblemDetails metody rozszerzenia.

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

Rezerwowa usługa IProblemDetailsService

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

Powyższy kod ma następujące działanie:

  • Zapisuje komunikat o błędzie z kodem rezerwowym, jeśli problemDetailsService program nie może napisać elementu ProblemDetails. Na przykład punkt końcowy, w którym nagłówek Akceptuj żądanie określa typ nośnika DefaulProblemDetailsWriter , który nie obsługuje.
  • Używa oprogramowania pośredniczącego obsługi wyjątków.

Poniższy przykład jest podobny do powyższego z tą różnicą, że wywołuje metodę 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);

W tym artykule opisano sposób obsługi błędów w minimalnych aplikacjach interfejsu API.

Wyjątki

W minimalnej aplikacji interfejsu API istnieją dwa różne wbudowane mechanizmy scentralizowane do obsługi nieobsługiwane wyjątki:

W tej sekcji opisano następującą minimalną aplikację interfejsu API, aby zademonstrować sposoby obsługi wyjątków. Zgłasza wyjątek w przypadku żądania punktu końcowego /exception :

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

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

app.Run();

Strona wyjątku dla deweloperów

Na stronie wyjątku dla deweloperów są wyświetlane szczegółowe ślady stosu błędów serwera. Używa DeveloperExceptionPageMiddleware go do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP i generowania odpowiedzi o błędach.

aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

Aby uzyskać więcej informacji na temat konfigurowania oprogramowania pośredniczącego, zobacz Oprogramowanie pośredniczące w minimalnych aplikacjach interfejsu API.

Korzystając z poprzedniej aplikacji interfejsu API Minimalne, gdy Developer Exception Page program wykryje nieobsługiwany wyjątek, generuje domyślną odpowiedź w postaci zwykłego tekstu podobną do następującego przykładu:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked

    System.InvalidOperationException: Sample Exception
    at Program.<>c.<<Main>$>b__0_1() in ....:line 17
    at lambda_method2(Closure, Object, HttpContext)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br

Ostrzeżenie

Nie włączaj strony wyjątku dla deweloperów, chyba że aplikacja jest uruchomiona w środowisku dewelopera. 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 Używanie wielu środowisk w programie ASP.NET Core.

Procedura obsługi wyjątków

W środowiskach nieprodukcyjnych użyj oprogramowania pośredniczącego obsługi wyjątków, aby wygenerować ładunek błędu. Aby skonfigurować metodę , wywołaj metodę Exception Handler MiddlewareUseExceptionHandler.

Na przykład poniższy kod zmienia aplikację w odpowiedzi na ładunek zgodny ze specyfikacją RFC 7807 do klienta. Aby uzyskać więcej informacji, zobacz sekcję Szczegóły problemu.

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

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

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample 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.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Run();

public record User(int Id);

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

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

Na przykład w poniższym przykładzie aplikacja zmienia odpowiedź na ładunek zgodny ze specyfikacją RFC 7807 do klienta dla wszystkich odpowiedzi klienta i serwera, w tym błędy 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.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

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 IProblemDetailsService implementuje interfejs, który obsługuje tworzenie szczegółów problemu w ASP.NET Core. Metoda AddProblemDetails rozszerzenia w systemie IServiceCollection rejestruje domyślną IProblemDetailsService implementację.

W aplikacjach ASP.NET Core następujące oprogramowanie pośredniczące generuje szczegóły problemów odpowiedzi HTTP podczas AddProblemDetails wywoływana, z wyjątkiem sytuacji, gdyAcceptnagłówek HTTP żądania nie zawiera jednego z typów zawartości obsługiwanych przez zarejestrowane IProblemDetailsWriter (domyślnie: application/json):

Minimalne aplikacje interfejsu API można skonfigurować do generowania odpowiedzi na szczegóły problemu dla wszystkich odpowiedzi na błędy klienta HTTP i serwera, które nie mają jeszcze zawartości treści przy użyciu AddProblemDetails metody rozszerzenia.

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.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

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

app.Run();

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