Udostępnij za pośrednictwem


Obsługa błędów w środowisku ASP.NET Core

Uwaga

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

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącym wydaniem, zobacz wersję artykułu .NET 9.

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ą, zobacz wersję tego artykułu .NET 9.

Autor: Tom Dykstra

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz również Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core i Obsługa błędów w minimalnych interfejsach API.

Aby uzyskać Blazor wskazówki dotyczące obsługi błędów, które uzupełniają lub zastępują wskazówki zawarte w tym artykule, zobacz Obsługa błędów w aplikacjach ASP.NET Core Blazor.

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ątków dla deweloperów uruchamia się wcześnie w pipeline'ie oprogramowania pośredniczącego, aby móc przechwytywać nieobsługiwane wyjątki zgłaszane w późniejszych etapach tego oprogramowania.

Aplikacje ASP.NET Core domyślnie włączają stronę z wyjątkiem dla programistów, gdy spełnione są oba następujące 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ątku Dewelopera, chyba że aplikacja jest uruchomiona w środowisku deweloperskim. 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.

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ą
  • Pliki cookie, jeśli istnieją
  • Nagłówki
  • Metadane punktu końcowego, jeśli istnieją

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

Na poniższej ilustracji przedstawiono przykładową stronę z wyjątkiem dla deweloperów, na której animacja pokazuje karty oraz 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. Na 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

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla środowiska produkcyjnego, wywołaj UseExceptionHandler. To oprogramowanie pośredniczące do obsługi wyjątków:

  • Przechwytuje i rejestruje nieobsługiwane wyjątki.
  • Ponownie wykonuje żądanie w alternatywnym kanale przy użyciu wskazanej ścieżki. Żądanie nie jest wykonywane ponownie, jeśli odpowiedź się zaczęła. Kod wygenerowany przez szablon ponownie wykonuje żądanie przy użyciu ścieżki /Error .

Ostrzeżenie

Jeśli alternatywny potok zgłasza wyjątek samodzielnie, middleware obsługi wyjątków ponownie zgłasza oryginalny wyjątek.

Ponieważ to oprogramowanie pośredniczące umożliwia ponowne wykonanie potoku żądań:

  • Oprogramowanie pośredniczące musi obsługiwać wielokrotne wejście przy użyciu tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowaniu ich przetwarzania na HttpContext, aby uniknąć jego ponownego przetwarzania. W przypadku obsługi treści żądania oznacza to buforowanie lub przechowywanie w pamięci podręcznej wyników, podobnie jak czytnik formularzy.
  • W przypadku przeciążenia UseExceptionHandler(IApplicationBuilder, String) używanego w szablonach, modyfikowana jest tylko ścieżka żądania, a dane trasy są usunięte. Dane żądania, takie jak nagłówki, metoda i elementy, są ponownie używane bez zmian.
  • Usługi o określonym zakresie pozostają takie same.

W poniższym przykładzie UseExceptionHandler dodaje middleware obsługujące wyjątki w środowiskach innych niż deweloperskie:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera stronę błędu (.cshtml) oraz klasę PageModel (ErrorModel) w folderze Pages. W przypadku aplikacji MVC szablon projektu zawiera metodę Error akcji i widok błędu Home dla kontrolera.

Oprogramowanie pośredniczące do obsługi wyjątków ponownie przetwarza żądanie przy użyciu oryginalnej metody HTTP. Jeśli punkt końcowy procedury obsługi błędów jest ograniczony do określonego zestawu metod HTTP, jest uruchamiany tylko dla tych metod HTTP. Na przykład akcja kontrolera MVC korzystająca z atrybutu [HttpGet] jest uruchamiana tylko dla żądań GET. Aby upewnić się, że wszystkie żądania docierają do niestandardowej strony obsługi błędów, nie ograniczaj ich do określonego zestawu metod HTTP.

Aby obsłużyć wyjątki inaczej na podstawie oryginalnej metody HTTP:

  • Dla Razor Pages utwórz wiele metod obsługi. Na przykład użyj OnGet do obsługi wyjątków GET i OnPost do obsługi wyjątków POST.
  • Dla wzorca MVC zastosuj atrybuty czasownika HTTP do wielu działań. Na przykład użyj [HttpGet] do obsługi wyjątków GET i użyj [HttpPost] do obsługi wyjątków POST.

Aby umożliwić nieuwierzytelnionym użytkownikom wyświetlanie niestandardowej strony obsługi błędów, upewnij się, że obsługuje dostęp anonimowy.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature, aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w obsłudze błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature, aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem bezpieczeństwa.

Mechanizm obsługi wyjątków lambda

Alternatywą dla niestandardowej strony obsługi wyjątków jest podanie lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Poniższy kod używa wyrażenia lambda do obsługi wyjątków:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Innym sposobem użycia lambda jest ustawienie kodu stanu na podstawie typu wyjątku, jak w poniższym przykładzie:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        StatusCodeSelector = ex => ex is TimeoutException
            ? StatusCodes.Status503ServiceUnavailable
            : StatusCodes.Status500InternalServerError
    });
}

Ostrzeżenie

Nie należy udostępniać klientom poufnych informacji o błędach. Serwowanie błędów jest zagrożeniem dla bezpieczeństwa.

IExceptionHandler

IExceptionHandler to interfejs, który daje deweloperowi wywołanie zwrotne do obsługi znanych wyjątków w centralnej lokalizacji.

IExceptionHandler implementacje są rejestrowane przez wywołanie metody IServiceCollection.AddExceptionHandler<T>. Okres istnienia IExceptionHandler wystąpienia jest pojedynczy. Można dodać wiele implementacji i są one wywoływane w kolejności zarejestrowanej.

Jeśli program obsługi wyjątków obsługuje żądanie, może wrócić true do zatrzymania przetwarzania. Jeśli wyjątek nie jest obsługiwany przez żadną procedurę obsługi wyjątków, kontrolka powraca do domyślnego zachowania i opcji oprogramowania pośredniczącego. Różne metryki i dzienniki są emitowane dla obsługiwanych i nieobsługiwanych wyjątków.

W poniższym przykładzie pokazano implementację IExceptionHandler :

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

W poniższym przykładzie pokazano, jak zarejestrować implementację IExceptionHandler iniekcji zależności:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

Po uruchomieniu poprzedniego kodu w środowisku programistycznym:

  • Funkcja CustomExceptionHandler jest wywoływana jako pierwsza w celu obsługi wyjątku.
  • Po rejestrowaniu wyjątku TryHandleAsync metoda zwraca falsewartość , więc zostanie wyświetlona strona wyjątku dewelopera.

W innych środowiskach:

  • Element jest wywoływany CustomExceptionHandler jako pierwszy w celu obsługi wyjątku.
  • Po rejestrowaniu wyjątku TryHandleAsync metoda zwraca falsewartość , więc zostanie wyświetlona /Error strona .

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony kodowej stanu kodów stanu http, takich jak 404 — Nie znaleziono. Gdy aplikacja ustawia kod stanu błędu HTTP 400-599, który nie ma treści, zwraca kod stanu i pustą treść odpowiedzi. Aby włączyć domyślne programy obsługi tylko do tekstu dla typowych kodów stanu błędów, wywołaj metodę UseStatusCodePages w pliku Program.cs:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Przed obsługą żądań należy wywołać UseStatusCodePages oprogramowanie pośredniczące. Na przykład wywołaj wywołanie UseStatusCodePages przed oprogramowaniem pośredniczącym plików statycznych i oprogramowaniem pośredniczącym punktów końcowych.

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Po UseStatusCodePages wywołaniu przeglądarka zwraca następującą odpowiedź:

Status Code: 404; Not Found

UseStatusCodePages nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Uwaga

Oprogramowanie pośredniczące stron kodu stanu nie przechwytuje wyjątków. Aby podać niestandardową stronę obsługi błędów, użyj strony obsługi wyjątków.

UseStatusCodePages z ciągiem formatu

Aby dostosować typ zawartości odpowiedzi i tekst, użyj przeciążenia UseStatusCodePages , które przyjmuje typ zawartości i ciąg formatu:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

W poprzednim kodzie {0} jest symbolem zastępczym kodu błędu.

UseStatusCodePages ciąg formatu nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UseStatusCodePages z lambda

Aby określić niestandardowy kod obsługi błędów i pisania odpowiedzi, użyj przeciążenia UseStatusCodePages , które przyjmuje wyrażenie lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages z lambda nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UżyjStronKodówStatusuZRdoctowaniami

UseStatusCodePagesWithRedirects Metoda rozszerzenia:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do punktu końcowego obsługi błędów podanego w szablonie adresu URL. Punkt końcowy obsługi błędów zwykle wyświetla informacje o błędzie i zwraca błąd HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Szablon adresu URL może zawierać symbol zastępczy {0} dla kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ zostanie zastąpiony przez element aplikacji PathBase. Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub stronę dedykowaną punktowi.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać i zwracać oryginalnego kodu stanu przy początkowej odpowiedzi przekierowania.

UżyjStronStatusuZWywołaniemPonownie

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Generuje treść odpowiedzi przez ponowne wykonanie potoku żądania przy użyciu alternatywnej ścieżki.
  • Nie zmienia kodu statusu przed ani po ponownym wykonaniu pipeline'u.

Nowe uruchomienie potoku może zmienić kod stanu odpowiedzi, ponieważ nowy potok całkowicie kontroluje kod stanu. Jeśli nowy pipeline nie zmieni kodu stanu, oryginalny kod stanu zostanie wysłany do klienta.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Jeśli określono punkt końcowy w aplikacji, utwórz widok MVC lub Razor stronę dla punktu końcowego.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj i zwróć oryginalny kod stanu z odpowiedzią.

Szablon adresu URL musi zaczynać się od / i może zawierać symbol zastępczy {0} kodu stanu. Aby przekazać kod stanu jako parametr ciągu zapytania, przekaż drugi argument do UseStatusCodePagesWithReExecuteelementu. Na przykład:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

Ponieważ to oprogramowanie pośredniczące może ponownie wykonać potok przetwarzania żądań:

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wejście przy użyciu tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowanie ich przetwarzania na HttpContext, aby uniknąć ponownego przetwarzania. W przypadku obsługi treści żądania oznacza to buforowanie lub przechowywanie w pamięci podręcznej wyników, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłącz strony kodu stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages].

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Kod obsługi wyjątków

Kod w sekcjach obsługi wyjątków może również zgłaszać wyjątki. Strony błędów produkcyjnych należy dokładnie przetestować i zachować szczególną ostrożność, aby uniknąć zgłaszania własnych wyjątków.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron wyjątków lub procedur obsługi. Odpowiedź musi zostać zakończona lub połączenie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer przechwytuje wyjątek przed wysłaniem nagłówków odpowiedzi, serwer wysyła odpowiedź 500 - Internal Server Error bez treści. Jeśli serwer przechwyci wyjątek po wysłaniu nagłówków odpowiedzi, zamknie połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące obsługujące wyjątki i filtry nie mają wpływu na to zachowanie.

Obsługa wyjątków rozruchowych

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Host można skonfigurować do przechwytywania błędów uruchamiania i przechwytywania szczegółowych błędów.

Warstwa hostingu może wyświetlić stronę błędu wykrytego podczas uruchamiania tylko wtedy, jeśli do niego dojdzie po związaniu adresu hosta z portem. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostingu rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

W przypadku uruchamiania w IIS (lub Azure App Service) lub IIS Express zwracany jest błąd 502.5 - Process Failure przez moduł ASP.NET Core, jeśli nie można uruchomić procesu. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Filtr AddDatabaseDeveloperPageExceptionFilter wyjątku strony dewelopera bazy danych przechwytuje wyjątki związane z bazą danych, które można rozwiązać przy użyciu migracji programu Entity Framework Core. W przypadku wystąpienia tych wyjątków odpowiedź HTML jest generowana ze szczegółami możliwych akcji w celu rozwiązania problemu. Ta strona jest włączona tylko w środowisku deweloperskim. Poniższy kod dodaje filtr wyjątków na stronie projektanta bazy danych:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Filtry wyjątków są przydatne do wychwytywania wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne, jak wbudowane oprogramowanie pośredniczące do obsługi wyjątków. Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

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(IServiceCollection) rozszerzenia na IServiceCollection rejestruje domyślną IProblemDetailsService implementację.

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

Poniższy kod konfiguruje aplikację tak, aby wygenerowała odpowiedź na szczegóły problemu dla wszystkich odpowiedzi na błędy klienta HTTP i serwera, które nie mają jeszcze zawartości treści:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

W następnej sekcji pokazano, jak dostosować treść odpowiedzi zawierającą szczegółowe dane problemu.

Dostosowywanie szczegółów problemu

Automatyczne tworzenie obiektu ProblemDetails można dostosować przy użyciu dowolnej z następujących opcji:

  1. Użyj ProblemDetailsOptions.CustomizeProblemDetails
  2. Użyj niestandardowego IProblemDetailsWriter
  3. Wywołaj IProblemDetailsService w oprogramowaniu pośredniczącym

CustomizeProblemDetails operacja

Wygenerowane szczegóły problemu można dostosować przy użyciu polecenia CustomizeProblemDetails, a dostosowania są stosowane do wszystkich szczegółów problemu generowanego automatycznie.

Poniższy kod używa ProblemDetailsOptions do ustawienia CustomizeProblemDetails.

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Na przykład wynik punktu dostępu generuje następującą szczegółową treść odpowiedzi dotyczącą problemu:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

Zwyczaj IProblemDetailsWriter

Implementację IProblemDetailsWriter można utworzyć na potrzeby zaawansowanych dostosowań.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Uwaga: W przypadku korzystania z niestandardowego IProblemDetailsWriter należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem AddRazorPages, AddControllers, AddControllersWithViews lub AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Szczegóły problemu z oprogramowania pośredniczącego

Alternatywnym podejściem do używania ProblemDetailsOptions z CustomizeProblemDetails jest ustawienie ProblemDetails w oprogramowaniu pośredniczącym. Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

W poprzednim kodzie minimalne punkty końcowe interfejsu API /divide i /squareroot zwracają oczekiwaną niestandardową odpowiedź na problemy w przypadku błędnych danych wejściowych.

Punkty końcowe kontrolera interfejsu API zwracają domyślny komunikat o błędzie w przypadku błędnych danych wejściowych, zamiast niestandardowego komunikatu o błędzie. Zwracana jest domyślna odpowiedź na problem, ponieważ kontroler interfejsu API zapisał do strumienia odpowiedzi, Szczegóły problemu dla kodów stanu błędu, zanim IProblemDetailsService.WriteAsync zostanie wywołane, a odpowiedź nie zostanie zapisana ponownie.

Poniższe ValuesController zwraca BadRequestResult, co powoduje zapis w strumieniu odpowiedzi i w związku z tym uniemożliwia zwrócenie niestandardowej odpowiedzi opisującej problem.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Następujące Values3Controller zwraca ControllerBase.Problem, więc oczekiwany wynik problemu niestandardowego jest zwracany.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Generowanie pakietu ProblemDetails dla wyjątków

Rozważmy następującą aplikację:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

W środowiskach nieprogramowania, gdy wystąpi wyjątek, poniżej znajduje się standardowa odpowiedź ProblemDetails zwrócona do klienta:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

W przypadku większości aplikacji powyższy kod jest wymagany w przypadku wyjątków. Jednak w poniższej sekcji pokazano, jak uzyskać bardziej szczegółowe odpowiedzi na problemy.

Alternatywą dla niestandardowej strony obsługi wyjątków jest podanie lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu i napisanie odpowiedzi ze szczegółami problemu za pomocą polecenia IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Ostrzeżenie

Nie podawaj klientom poufnych informacji o błędach. Udostępnianie błędów stanowi zagrożenie dla bezpieczeństwa.

Alternatywną metodą generowania szczegółów problemu jest użycie pakietu NuGet innej firmy Hellang.Middleware.ProblemDetails , który może służyć do mapowania wyjątków i błędów klienta na szczegóły problemu.

Dodatkowe zasoby

Autor: Tom Dykstra

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz również Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core i Obsługa błędów w minimalnych interfejsach API.

Strona wyjątku dla deweloperów

Na Stronie wyjątków dewelopera wyświetlane są szczegółowe informacje o nieobsłużonych wyjątkach żądań. Aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy spełnione są oba warunki:

Strona wyjątków dla deweloperów działa we wczesnym etapie ciągu środkowego oprogramowania, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w późniejszych etapach oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, 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.

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ą
  • Pliki cookie, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów może nie zawierać żadnych informacji. Użyj rejestrowania, aby uzyskać pełne informacje o błędach.

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla środowiska Produkcyjnego, wywołaj UseExceptionHandler. Oprogramowanie pośredniczące obsługi wyjątków:

  • Przechwytuje i rejestruje nieobsługiwane wyjątki.
  • Ponownie wykonuje żądanie w alternatywnym potoku, używając wskazanej ścieżki. Żądanie nie jest wykonywane ponownie, jeśli odpowiedź została uruchomiona. Kod wygenerowany przez szablon ponownie wykonuje żądanie przy użyciu ścieżki /Error .

Ostrzeżenie

Jeśli alternatywny potok zgłasza wyjątek samodzielnie, Middleware obsługi wyjątków ponownie zgłasza oryginalny wyjątek.

Ponieważ to oprogramowanie pośrednie może ponownie wykonać potok żądań:

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wejście za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowanie ich przetwarzania na HttpContext aby uniknąć jego ponownego przetwarzania. W przypadku obsługi treści żądania oznacza to buforowanie lub przechowywanie w pamięci podręcznej wyników, takich jak czytnik formularzy.
  • W przypadku przeładowania UseExceptionHandler(IApplicationBuilder, String) używanego w szablonach, zmodyfikowana zostaje tylko ścieżka żądania, a dane trasy są usuwane. Żądania danych, takich jak nagłówki, metoda i elementy, są ponownie używane bez zmian.
  • Usługi o określonym zakresie pozostają takie same.

W poniższym przykładzie UseExceptionHandler dodano oprogramowanie pośredniczące obsługujące wyjątki w środowiskach nieprogramowania:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera klasę (PageModel) i stronę błędu (.cshtml) w folderze Pages. W przypadku aplikacji MVC szablon projektu zawiera metodę Error akcji i widok błędu Home dla kontrolera.

Średniowarstwowe oprogramowanie do obsługi wyjątków ponownie wykonuje żądanie przy użyciu oryginalnej metody HTTP. Jeśli punkt końcowy procedury obsługi błędów jest ograniczony do określonego zestawu metod HTTP, jest uruchamiany tylko dla tych metod HTTP. Na przykład akcja kontrolera MVC korzystająca z atrybutu [HttpGet] jest uruchamiana tylko dla żądań GET. Aby upewnić się, że wszystkie żądania docierają do niestandardowej strony obsługi błędów, nie ograniczaj ich do określonego zestawu metod HTTP.

Aby obsłużyć wyjątki inaczej na podstawie oryginalnej metody HTTP:

  • Dla Razor Pages utwórz wiele metod obsługi. Na przykład użyj OnGet do obsługi wyjątków GET oraz OnPost do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj [HttpGet] do obsługi wyjątków GET i używaj [HttpPost] do obsługi wyjątków POST.

Aby umożliwić nieuwierzytelnionym użytkownikom wyświetlanie niestandardowej strony obsługi błędów, upewnij się, że obsługuje dostęp anonimowy.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature do uzyskania dostępu do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature, aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Przekazywanie błędów jest zagrożeniem dla bezpieczeństwa.

Obsługa wyjątków za pomocą lambda

Alternatywą dla niestandardowej strony obsługi wyjątków jest podanie lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Poniższy kod używa wyrażenia lambda do obsługi wyjątków:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Ostrzeżenie

Nie udzielaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem bezpieczeństwa.

IExceptionHandler

IExceptionHandler to interfejs, który daje deweloperowi wywołanie zwrotne (callback) do obsługi znanych wyjątków w centralnym miejscu.

implementacje IExceptionHandler są rejestrowane przez wywołanie IServiceCollection.AddExceptionHandler<T> [IServiceCollection.AddExceptionHandler<T>]. Czas życia wystąpienia IExceptionHandler to singleton. Można dodać wiele implementacji i są one wywoływane w kolejności zarejestrowanej.

Jeśli program obsługi wyjątków obsługuje żądanie, może wrócić true do zatrzymania przetwarzania. Jeśli wyjątek nie jest obsługiwany przez żadną procedurę obsługi wyjątków, kontrolka powraca do domyślnego zachowania i opcji oprogramowania pośredniczącego. Różne metryki i dzienniki są emitowane dla obsługiwanych i nieobsługiwanych wyjątków.

W poniższym przykładzie pokazano implementację IExceptionHandler :

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

W poniższym przykładzie pokazano, jak zarejestrować implementację IExceptionHandler dla iniekcji zależności:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

Po uruchomieniu poprzedniego kodu w środowisku programistycznym:

  • Najpierw CustomExceptionHandler jest wywoływane w celu obsługi wyjątku.
  • Po rejestrowaniu wyjątku TryHandleException metoda zwraca falsewartość , więc zostanie wyświetlona strona wyjątku dewelopera.

W innych środowiskach:

  • CustomExceptionHandler jest wywoływany jako pierwszy w celu obsługiwania wyjątku.
  • Po rejestrowaniu wyjątku TryHandleException metoda zwraca falsewartość , więc zostanie wyświetlona /Error strona .

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony kodu błędu dla kodów stanu HTTP, takich jak 404 — Nie znaleziono. Gdy aplikacja ustawia kod stanu błędu HTTP 400-599, który nie ma treści, zwraca kod stanu i pustą treść odpowiedzi. Aby włączyć domyślne obsługiwacze tylko tekstowe dla typowych kodów stanu błędów, wywołaj metodę UseStatusCodePages w Program.cs.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Należy wywołać UseStatusCodePages przed uruchomieniem oprogramowania pośredniczącego obsługującego żądania. Na przykład wywołaj UseStatusCodePages przed oprogramowaniem pośredniczącym dla plików statycznych i dla punktów końcowych.

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Po UseStatusCodePages wywołaniu przeglądarka zwraca następującą odpowiedź:

Status Code: 404; Not Found

UseStatusCodePages nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Uwaga

Oprogramowanie pośredniczące strony kodów statusu nie przechwytuje wyjątki. Aby podać niestandardową stronę obsługi błędów, użyj strony obsługi wyjątków.

Użyj UseStatusCodePages z ciągiem formatu

Aby dostosować typ zawartości odpowiedzi i tekst, użyj przeciążenia UseStatusCodePages dla typu zawartości i ciągu formatu:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

W poprzednim kodzie {0} jest symbolem zastępczym kodu błędu.

UseStatusCodePages ciąg znaków formatujących nie jest zwykle stosowany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UseStatusCodePages z lambda

Aby określić niestandardowy kod obsługi błędów i zapisu odpowiedzi, użyj przeciążenia UseStatusCodePages, które przyjmuje wyrażenie lambda.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages z lambda nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UseStatusCodePagesWithRedirects (użyj stron kodu statusu z przekierowaniami)

UseStatusCodePagesWithRedirects Metoda rozszerzenia:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do punktu końcowego obsługi błędów podanego w szablonie adresu URL. Punkt końcowy obsługi błędów zwykle wyświetla informacje o błędzie i zwraca błąd HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Szablon adresu URL może zawierać {0} symbol zastępczy dla kodu statusu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), element ~ zostanie zastąpiony przez PathBase aplikacji. Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę dla punktu końcowego.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać ani zwracać oryginalnego kodu stanu przy początkowej odpowiedzi przekierowania.

UseStatusCodePagesWithReExecute

Metoda rozszerzenia UseStatusCodePagesWithReExecute:

  • Generuje treść odpowiedzi przez ponowne uruchomienie potoku żądania przy użyciu alternatywnej ścieżki.
  • Nie zmienia kodu stanu przed ani po ponownym wykonaniu linii przetwarzania.

Nowe uruchomienie potoku może zmienić kod statusu odpowiedzi, ponieważ nowy potok ma pełną kontrolę nad kodem statusu. Jeśli nowy potok nie zmieni kodu stanu, oryginalny kod stanu zostanie wysłany do klienta.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Jeśli określono punkt końcowy w aplikacji, utwórz widok MVC lub Razor stronę dla punktu końcowego.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj i zwróć oryginalny kod stanu z odpowiedzią.

Szablon adresu URL musi zaczynać się od / i może zawierać symbol zastępczy {0} kodu stanu. Aby przekazać kod stanu jako parametr ciągu zapytania, przekaż drugi argument do UseStatusCodePagesWithReExecute. Na przykład:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

Ponieważ to oprogramowanie pośredniczące może ponownie wykonać potok żądania:

  • Middleware musi obsługiwać ponowny dostęp za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub keszowanie ich przetwarzania na HttpContext aby uniknąć jego ponownego wykonania. W przypadku obsługi treści żądania oznacza to buforowanie lub zapisywanie wyników w pamięci podręcznej, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłącz strony kodów stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages].

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Kod obsługi wyjątków

Kod na stronach obsługi wyjątków może również zgłaszać wyjątki. Strony błędów produkcyjnych należy dokładnie przetestować i zachować szczególną ostrożność, aby uniknąć zgłaszania własnych wyjątków.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron ani procedur obsługi wyjątków. Odpowiedź musi zostać ukończona albo połączenie zostanie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer napotka wyjątek przed wysłaniem nagłówków odpowiedzi 500 - Internal Server Error, serwer wysyła odpowiedź bez treści odpowiedzi. Jeśli serwer przechwyci wyjątek po wysłaniu nagłówków odpowiedzi, zamknie połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące obsługujące wyjątki i filtry nie mają wpływu na to zachowanie.

Obsługa wyjątków przy uruchamianiu

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Host można skonfigurować do przechwytywania błędów startowych i przechwytywania szczegółowych błędów.

Warstwa hostingu może wyświetlić stronę błędu dotyczącego przechwyconego błędu uruchamiania tylko wtedy, gdy błąd wystąpi po powiązaniu adresu i portu hosta. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostingu rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

W przypadku uruchamiania na IIS (lub IIS Express albo Azure App Service), jeśli nie można uruchomić procesu, moduł ASP.NET Core zwraca błąd 502.5 - Process Failure. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Filtr AddDatabaseDeveloperPageExceptionFilter wyjątków na stronie dewelopera przechwytuje wyjątki dotyczące bazy danych, które można rozwiązać za pomocą migracji Entity Framework Core. W przypadku wystąpienia tych wyjątków odpowiedź HTML jest generowana ze szczegółami możliwych akcji w celu rozwiązania problemu. Ta strona jest włączona tylko w środowisku deweloperskim. Poniższy kod dodaje filtr wyjątków na stronie deweloperskiej bazy danych.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Filtry wyjątków są przydatne do wychwytywania wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne, jak wbudowane middleware obsługujący wyjątki. UseExceptionHandler Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

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 rozszerzenia AddProblemDetails(IServiceCollection) na IServiceCollection rejestruje domyślną implementację IProblemDetailsService.

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

Poniższy kod konfiguruje aplikację w celu wygenerowania odpowiedzi ze szczegółami problemu dla wszystkich odpowiedzi na błędy klienta HTTP i serwera, które nie mają jeszcze zawartości treści:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

W następnej sekcji pokazano, jak dostosować treść odpowiedzi dotyczącej szczegółów problemu.

Dostosowywanie szczegółów problemu

Automatyczne tworzenie obiektu ProblemDetails można dostosować przy użyciu dowolnej z następujących opcji:

  1. Użyj ProblemDetailsOptions.CustomizeProblemDetails
  2. Użyj niestandardowego IProblemDetailsWriter
  3. Wywołaj IProblemDetailsService w oprogramowaniu pośredniczącym

CustomizeProblemDetails operacja

Wygenerowane szczegóły problemu można dostosować przy użyciu polecenia CustomizeProblemDetails, a dostosowania są stosowane do wszystkich szczegółów problemu generowanego automatycznie.

Poniższy kod używa ProblemDetailsOptions metody do ustawienia :CustomizeProblemDetails

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Na przykład wynik punktu końcowego generuje następującą treść odpowiedzi ze szczegółami problemu:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

Zwyczaj IProblemDetailsWriter

Implementację IProblemDetailsWriter można utworzyć na potrzeby zaawansowanych dostosowań.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Uwaga: podczas korzystania z niestandardowego IProblemDetailsWriter należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem AddRazorPages, AddControllers, AddControllersWithViews lub AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Szczegóły problemu z oprogramowania pośredniczącego

Alternatywnym podejściem do używania ProblemDetailsOptions z CustomizeProblemDetails jest ustawienie ProblemDetails w oprogramowaniu pośredniczącym. Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

W poprzednim kodzie minimalne punkty końcowe /divide interfejsu API i /squareroot zwracają oczekiwaną niestandardową odpowiedź problemu na dane wejściowe błędu.

Punkty końcowe kontrolera interfejsu API zwracają domyślną odpowiedź na problem dla błędnych danych wejściowych, a nie na niestandardowy problem. Zwracana jest domyślna odpowiedź na problem, ponieważ kontroler API zapisał do strumienia odpowiedzi, Szczegóły problemu dla kodów stanu błędu, zanim IProblemDetailsService.WriteAsync zostanie wywołany, a odpowiedź nie zostanie ponownie zapisana.

Poniższe ValuesController polecenie zwraca wartość BadRequestResult, która zapisuje w strumieniu odpowiedzi i w związku z tym uniemożliwia zwracanie niestandardowej odpowiedzi na problem.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Następujące Values3Controller zwraca ControllerBase.Problem, więc zwracany jest oczekiwany wynik problemu niestandardowego.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Tworzenie ładunku ProblemDetails dla wyjątków

Rozważmy następującą aplikację:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

W środowiskach nieprogramowania, gdy wystąpi wyjątek, poniżej znajduje się standardowa odpowiedź ProblemDetails zwrócona do klienta:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

W przypadku większości aplikacji powyższy kod jest wymagany w przypadku wyjątków. Jednak w poniższej sekcji pokazano, jak uzyskać bardziej szczegółowe odpowiedzi na problemy.

Alternatywą dla niestandardowej strony obsługi wyjątków jest przekazanie lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu i napisanie odpowiedzi ze szczegółami problemu za pomocą polecenia IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Ostrzeżenie

Nie należy udostępniać klientom poufnych informacji o błędach. Udostępnianie błędów stanowi ryzyko dla bezpieczeństwa.

Alternatywną metodą generowania szczegółów problemu jest użycie pakietu NuGet innej firmy Hellang.Middleware.ProblemDetails , który może służyć do mapowania wyjątków i błędów klienta na szczegóły problemu.

Dodatkowe zasoby

Autor: Tom Dykstra

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz również Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core i Obsługa błędów w minimalnych interfejsach API.

Strona wyjątku dla deweloperów

Na stronie wyjątków programisty wyświetlane są szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Aplikacje ASP.NET Core domyślnie włączają stronę wyjątków dla deweloperów, gdy spełnione są oba warunki:

  • Uruchamianie w środowisku programistycznym.
  • Aplikacja utworzona przy użyciu bieżących szablonów, czyli przy użyciu aplikacji WebApplication.CreateBuilder. Aplikacje utworzone przy użyciu elementu WebHost.CreateDefaultBuilder muszą włączyć stronę wyjątku dla deweloperów, wywołując metodę app.UseDeveloperExceptionPage w pliku Configure.

Strona wyjątku dla deweloperów działa na początku potoku oprogramowania pośredniczącego, umożliwiając przechwytywanie nieobsługiwanych wyjątków zgłaszanych w późniejszych fazach oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, 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.

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

  • Śledzenie stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie gwarantuje dostarczenia żadnych informacji. Użyj logowania, aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla środowiska produkcyjnego, wywołaj UseExceptionHandler. To oprogramowanie pośredniczące do obsługi wyjątków:

  • Przechwytuje i rejestruje nieobsługiwane wyjątki.
  • Ponownie wykonuje żądanie w alternatywnym potoku przy użyciu wskazanej ścieżki. Żądanie nie jest ponownie wykonywane, jeśli odpowiedź już się zaczęła. Kod wygenerowany przez szablon ponownie wykonuje żądanie przy użyciu ścieżki /Error .

Ostrzeżenie

Jeśli alternatywny potok zgłasza własny wyjątek, Pośrednik Obsługi Wyjątków ponownie rzuca oryginalny wyjątek.

Ponieważ to oprogramowanie pośredniczące może ponownie wykonać szereg żądań:

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wysyłanie za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowanie ich przetwarzania na HttpContext aby uniknąć jego ponownego utworzenia. W przypadku obsługi treści żądania oznacza to buforowanie lub buforowanie pamięci podręcznej wyników, takich jak czytnik formularzy.
  • W przypadku użytego w szablonach przeciążenia UseExceptionHandler(IApplicationBuilder, String), modyfikowana jest tylko ścieżka żądania, a dane trasy są czyszczone. Żądania danych, takie jak nagłówki, metody i elementy, są ponownie używane bez zmian.
  • Usługi o określonym zakresie pozostają takie same.

W poniższym przykładzie UseExceptionHandler dodaje oprogramowanie pośredniczące do obsługi wyjątków w środowiskach nie-deweloperskich.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera stronę błędu (.cshtml) i klasę (ErrorModel) w folderze Pages. W przypadku aplikacji MVC szablon projektu zawiera metodę Error akcji i widok błędu Home dla kontrolera.

Oprogramowanie pośredniczące do obsługi wyjątków ponownie wykonuje żądanie przy użyciu oryginalnej metody HTTP . Jeśli punkt końcowy procedury obsługi błędów jest ograniczony do określonego zestawu metod HTTP, jest uruchamiany tylko dla tych metod HTTP. Na przykład akcja kontrolera MVC korzystająca z atrybutu [HttpGet] jest uruchamiana tylko dla żądań GET. Aby upewnić się, że wszystkie żądania docierają do niestandardowej strony obsługi błędów, nie ograniczaj ich do określonego zestawu metod HTTP.

Aby obsłużyć wyjątki inaczej na podstawie oryginalnej metody HTTP:

  • Dla Razor Pages utwórz wiele metod obsługi. Na przykład użyj OnGet do obsługi wyjątków GET i OnPost do obsługi wyjątków POST.
  • W przypadku wzorca MVC należy zastosować atrybuty czasownika HTTP do wielu akcji. Na przykład użyj [HttpGet] do obsługi wyjątków GET i [HttpPost] do obsługi wyjątków POST.

Aby umożliwić nieuwierzytelnionym użytkownikom wyświetlanie niestandardowej strony obsługi błędów, upewnij się, że obsługuje dostęp anonimowy.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w obsłudze błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature, aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Ostrzeżenie

Nie należy udostępniać klientom poufnych informacji o błędach. Pojawianie się błędów stanowi zagrożenie dla bezpieczeństwa.

Obsługiwacz wyjątków lambda

Alternatywą dla niestandardowej strony obsługi wyjątków jest podanie lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Poniższy kod używa wyrażenia lambda do obsługi wyjątków:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Udostępnianie błędów jest zagrożeniem bezpieczeństwa.

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony kodu stanu HTTP dla kodów błędów, takich jak 404 — Nie znaleziono. Gdy aplikacja ustawia kod stanu błędu HTTP 400-599, który nie ma treści, zwraca kod stanu i pustą treść odpowiedzi. Aby włączyć domyślne programy obsługi dla typowych kodów stanu błędów, które obsługują tylko tekst, wywołaj UseStatusCodePages w Program.cs.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Wywołaj UseStatusCodePages przed obsługą żądań przez oprogramowanie pośredniczące. Na przykład wywołaj UseStatusCodePages przed oprogramowaniem pośredniczącym dla plików statycznych i dla punktów końcowych.

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Po UseStatusCodePages wywołaniu przeglądarka zwraca następującą odpowiedź:

Status Code: 404; Not Found

UseStatusCodePages nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Uwaga

Oprogramowanie pośredniczące dla stron kodu stanu nie przechwytuje wyjątków. Aby podać niestandardową stronę obsługi błędów, użyj strony obsługi wyjątków.

UseStatusCodePages z ciągiem znaków formatujących

Aby dostosować typ zawartości i tekst odpowiedzi, użyj przeciążenia UseStatusCodePages, które przyjmuje typ zawartości oraz ciąg formatu:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

W poprzednim kodzie {0} jest symbolem zastępczym kodu błędu.

UseStatusCodePages łańcuch formatujący nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UseStatusCodePages z lambda

Aby określić niestandardowy kod obsługi błędów i generowania odpowiedzi, użyj przeciążenia UseStatusCodePages, które przyjmuje wyrażenie lambda.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages funkcja lambda nie jest zazwyczaj stosowana w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest użyteczny dla użytkowników.

UżyjStronKodówStatusuZPrzekierowaniami

Metoda rozszerzenia UseStatusCodePagesWithRedirects:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do punktu końcowego obsługi błędów podanego w szablonie adresu URL. Punkt końcowy obsługi błędów zwykle wyświetla informacje o błędzie i zwraca błąd HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Szablon adresu URL może zawierać {0} symbol zastępczy dla kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ zostanie zastąpiony przez PathBase aplikacji. Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę dla punktu końcowego.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać i zwracać oryginalnego kodu stanu w początkowej odpowiedzi przekierowania.

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Generuje treść odpowiedzi przez ponowne wykonanie potoku żądania przy użyciu alternatywnej ścieżki.
  • Nie zmienia kodu stanu przed lub po ponownym wykonaniu potoku.

Nowe wykonanie potoku może zmienić kod stanu odpowiedzi, ponieważ nowy potok ma pełną kontrolę nad kodem stanu. Jeśli nowy pipeline nie zmieni kodu stanu, pierwotny kod stanu zostanie wysłany do klienta.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Jeśli w aplikacji określono punkt końcowy, utwórz dla niego widok MVC lub stronę Razor.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj i zwróć oryginalny kod stanu wraz z odpowiedzią.

Szablon adresu URL musi zaczynać się od / i może zawierać symbol zastępczy {0} kodu stanu. Aby przekazać kod stanu jako parametr ciągu zapytania, przekaż drugi argument do UseStatusCodePagesWithReExecute. Na przykład:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = $"{statusCodeReExecuteFeature.OriginalPathBase}"
                                    + $"{statusCodeReExecuteFeature.OriginalPath}"
                                    + $"{statusCodeReExecuteFeature.OriginalQueryString}";

        }
    }
}

Ponieważ to oprogramowanie pośredniczące może ponownie wykonać potok żądania:

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wejście za pomocą tego samego żądania. Zwykle oznacza to albo wyczyszczenie ich stanu po wywołaniu _next, albo zapisanie wyników przetwarzania na HttpContext w celu uniknięcia ponownego wykonywania. W przypadku obsługi treści żądania oznacza to buforowanie lub przechowywanie wyników, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłącz strony kodu stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages].

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Kod obsługi wyjątków

Kod na stronach obsługi wyjątków może również zgłaszać wyjątki. Strony błędów produkcyjnych należy dokładnie przetestować i zachować szczególną ostrożność, aby uniknąć zgłaszania własnych wyjątków.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron wyjątków lub procedur obsługi. Odpowiedź musi zostać ukończona albo połączenie zostanie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer przechwytuje wyjątek przed wysłaniem 500 - Internal Server Error nagłówków odpowiedzi, serwer wysyła odpowiedź bez treści odpowiedzi. Jeśli serwer wykryje wyjątek po wysłaniu nagłówków odpowiedzi, zamknie połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Na to zachowanie nie mają wpływu niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące obsługujące wyjątki oraz filtry.

Obsługa wyjątków w startupie

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Host można skonfigurować do przechwytywania błędów uruchamiania i przechwytywania błędów szczegółowych.

Warstwa hostingu może wyświetlić stronę błędu dla błędu uruchamiania tylko wtedy, gdy błąd wystąpi po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostingu rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

W przypadku uruchamiania w usługach IIS (lub usługa aplikacji Azure) lub IIS Express błąd 502.5 - Niepowodzenie procesu jest zwracany przez moduł ASP.NET Core, jeśli nie można uruchomić procesu. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Filtr wyjątków dewelopera baz danych AddDatabaseDeveloperPageExceptionFilter przechwytuje bazodanowe wyjątki, które można rozwiązać za pomocą migracji Entity Framework Core. W przypadku wystąpienia tych wyjątków odpowiedź HTML jest generowana ze szczegółami możliwych akcji w celu rozwiązania problemu. Ta strona jest włączona tylko w środowisku deweloperskim. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Filtry wyjątków są przydatne do przechwytywania wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne, jak wbudowane middleware do obsługi wyjątków. UseExceptionHandler Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

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 rozszerzenia AddProblemDetails(IServiceCollection) rejestruje domyślną implementację IProblemDetailsService.

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

Poniższy kod konfiguruje aplikację w celu wygenerowania odpowiedzi ze szczegółami problemu dla wszystkich odpowiedzi na błędy klienta HTTP i serwera, które nie mają jeszcze zawartości treści:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

W następnej sekcji pokazano, jak dostosować treść odpowiedzi zawierającej szczegółowe informacje o problemie.

Dostosowywanie szczegółów problemu

Automatyczne tworzenie obiektu ProblemDetails można dostosować przy użyciu dowolnej z następujących opcji:

  1. Użyj ProblemDetailsOptions.CustomizeProblemDetails
  2. Użyj niestandardowego IProblemDetailsWriter
  3. Wywołaj IProblemDetailsService w oprogramowaniu pośredniczącym

CustomizeProblemDetails operacja

Wygenerowane szczegóły problemu można dostosować przy użyciu polecenia CustomizeProblemDetails, a dostosowania są stosowane do wszystkich szczegółów problemu generowanego automatycznie.

Poniższy kod używa ProblemDetailsOptions do ustawienia CustomizeProblemDetails:

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Przykładowo, wynik HTTP Status 400 Bad Request punktu końcowego generuje następującą treść odpowiedzi zawierającą szczegóły problemu:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

Zwyczaj IProblemDetailsWriter

Implementację IProblemDetailsWriter można utworzyć na potrzeby zaawansowanych dostosowań.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Uwaga: w przypadku używania niestandardowego IProblemDetailsWriter należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem AddRazorPages, AddControllers, AddControllersWithViews lub AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Szczegóły problemu z oprogramowania pośredniczącego

Alternatywą dla używania ProblemDetailsOptions z CustomizeProblemDetails jest ustawienie ProblemDetails w oprogramowaniu pośredniczącym. Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

W poprzednim kodzie minimalne punkty końcowe /divide interfejsu API i /squareroot zwracają oczekiwaną niestandardową odpowiedź na problem dla błędnych danych wejściowych.

Punkty końcowe kontrolera interfejsu API zwracają domyślną odpowiedź problemu, gdy wprowadzone są błędne dane, a nie niestandardową odpowiedź na problem. Zwracana jest domyślna odpowiedź na problem, ponieważ kontroler interfejsu API zapisał się do strumienia odpowiedzi, Szczegóły problemu dla kodów stanu błędu, zanim metoda IProblemDetailsService.WriteAsync zostanie wywołana, a odpowiedź nie zostanie zapisana ponownie.

Poniższe ValuesController polecenie zwraca wartość BadRequestResult, która zapisuje w strumieniu odpowiedzi i w związku z tym uniemożliwia zwracanie niestandardowej odpowiedzi na problem.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Następujące Values3Controller zwraca ControllerBase.Problem, dzięki czemu zwracany jest oczekiwany wynik niestandardowego problemu.

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Wygeneruj ładunek ProblemDetails dla wyjątków

Rozważmy następującą aplikację:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

W środowiskach nieprogramowania, gdy wystąpi wyjątek, poniżej znajduje się standardowa odpowiedź ProblemDetails zwrócona do klienta:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

W przypadku większości aplikacji powyższy kod jest wymagany w przypadku wyjątków. Jednak w poniższej sekcji pokazano, jak uzyskać bardziej szczegółowe odpowiedzi na problemy.

Alternatywą dla niestandardowej strony dla obsługi wyjątków jest podanie wyrażenia lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu i napisanie odpowiedzi ze szczegółami problemu za pomocą polecenia IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Ostrzeżenie

Nie należy przekazywać poufnych informacji o błędach klientom. Występowanie błędów stanowi zagrożenie dla bezpieczeństwa.

Alternatywną metodą generowania szczegółów problemu jest użycie pakietu NuGet innej firmy Hellang.Middleware.ProblemDetails , który może służyć do mapowania wyjątków i błędów klienta na szczegóły problemu.

Dodatkowe zasoby

Autor: Tom Dykstra

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core dla internetowych interfejsów API.

Strona wyjątku dla deweloperów

Strona wyjątków dla dewelopera wyświetla szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Domyślnie, aplikacje ASP.NET Core włączają stronę wyjątków dewelopera, gdy spełnione są oba te warunki.

Strona wyjątków dla deweloperów jest uruchamiana na wczesnym etapie potoku oprogramowania pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w późniejszych elementach tego potoku.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, 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.

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ą
  • Pliki cookie, jeśli istnieją
  • Nagłówki

Strona wyjątków dewelopera nie gwarantuje dostarczenia żadnych informacji. Użyj logowania, aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla Produkcja, wywołaj UseExceptionHandler. To oprogramowanie pośrednie do obsługi wyjątków

  • Przechwytuje i rejestruje nieobsługiwane wyjątki.
  • Ponownie wykonuje żądanie w innym potoku przy użyciu wskazanej ścieżki. Jeśli odpowiedź została rozpoczęta, żądanie nie jest ponownie wykonywane. Kod wygenerowany przez szablon ponownie wykonuje żądanie przy użyciu ścieżki /Error .

Ostrzeżenie

Jeśli alternatywny potok zgłasza wyjątek samodzielnie, oprogramowanie pośredniczące do obsługi wyjątków ponownie zgłasza oryginalny wyjątek.

W poniższym przykładzie UseExceptionHandler dodaje oprogramowanie pośredniczące do obsługi wyjątków w środowiskach innych niż programistyczne.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera stronę błędu (.cshtml) i klasę PageModel(ErrorModel) w folderze Pages. W przypadku aplikacji MVC szablon projektu zawiera metodę Error akcji i widok błędu Home dla kontrolera.

Oprogramowanie pośredniczące do obsługi wyjątków ponownie przetwarza żądanie za pomocą oryginalnej metody HTTP. Jeśli punkt końcowy procedury obsługi błędów jest ograniczony do określonego zestawu metod HTTP, jest uruchamiany tylko dla tych metod HTTP. Na przykład akcja kontrolera MVC korzystająca z atrybutu [HttpGet] jest uruchamiana tylko dla żądań GET. Aby upewnić się, że wszystkie żądania docierają do niestandardowej strony obsługi błędów, nie ograniczaj ich do określonego zestawu metod HTTP.

Aby obsłużyć wyjątki inaczej na podstawie oryginalnej metody HTTP:

  • Dla Razor Pages utwórz wiele metod obsługi. Na przykład użyj OnGet do obsługi wyjątków GET i OnPost do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj [HttpGet], aby obsługiwać wyjątki GET, i użyj [HttpPost] do obsługi wyjątków POST.

Aby umożliwić nieuwierzytelnionym użytkownikom wyświetlanie niestandardowej strony obsługi błędów, upewnij się, że obsługuje dostęp anonimowy.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi wyjątków. W poniższym przykładzie użyto IExceptionHandlerPathFeature , aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Serwowanie błędów stanowi zagrożenie bezpieczeństwa.

Obsługa wyjątków lambda

Alternatywą dla niestandardowej strony obsługi wyjątków jest podanie wyrażenia lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Poniższy kod używa wyrażenia lambda do obsługi wyjątków:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Ostrzeżenie

Nie należy przekazywać klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem bezpieczeństwa.

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony kodu statusu HTTP dla kodów błędów, takich jak 404 — Nie znaleziono. Gdy aplikacja ustawia kod stanu błędu HTTP 400-599, który nie ma treści, zwraca kod stanu i pustą treść odpowiedzi. Aby włączyć domyślne programy obsługi tekstu dla typowych kodów stanu błędów, wywołaj metodę UseStatusCodePages w pliku Program.cs.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Przed obsługą żądań należy wywołać UseStatusCodePages oprogramowanie pośredniczące. Na przykład wywołaj UseStatusCodePages przed oprogramowaniem pośredniczącym dla plików statycznych i punktów końcowych.

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Po UseStatusCodePages wywołaniu przeglądarka zwraca następującą odpowiedź:

Status Code: 404; Not Found

UseStatusCodePages nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Uwaga

Oprogramowanie pośredniczące stron kodu stanu nie przechwytuje wyjątków. Aby zapewnić niestandardową stronę obsługi błędów, użyj strony obsługi wyjątków.

UseStatusCodePages z łańcuchem formatu

Aby dostosować typ zawartości odpowiedzi i tekst, użyj przeciążenia UseStatusCodePages, które przyjmuje typ zawartości i ciąg formatowania.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

W poprzednim kodzie {0} jest symbolem zastępczym kodu błędu.

UseStatusCodePages ciąg formatu nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

UseStatusCodePages za pomocą lambda

Aby określić niestandardowy kod obsługi błędów i pisania odpowiedzi, użyj przeciążenia UseStatusCodePages , które przyjmuje wyrażenie lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages z wyrażeniem lambda nie jest zazwyczaj używane w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest użyteczny dla użytkowników.

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects Metoda rozszerzenia:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do punktu końcowego obsługi błędów podanego w szablonie adresu URL. Punkt końcowy obsługi błędów zwykle wyświetla informacje o błędzie i zwraca błąd HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Szablon adresu URL może zawierać symbol zastępczy {0} dla kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), element ~ jest zastępowany PathBase przez aplikację. Podczas określania punktu końcowego w aplikacji utwórz stronę dla punktu końcowego lub widok MVC.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać i zwracać oryginalnego kodu stanu z początkową odpowiedzią przekierowania.

UżyjStronKodówStatusuZPonownymWykonaniem

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca oryginalny kod stanu do klienta.
  • Generuje treść odpowiedzi poprzez ponowne wykonanie potoku danych żądania przy użyciu alternatywnej ścieżki.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Jeśli określono punkt końcowy w aplikacji, utwórz widok MVC lub Razor stronę odpowiadającą punktowi końcowemu.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj oryginalny kod stanu i zwróć go wraz z odpowiedzią.

Szablon adresu URL musi zaczynać się od / i może zawierać symbol zastępczy {0} kodu stanu. Aby przekazać kod stanu jako parametr ciągu zapytania, przekaż drugi argument do elementu UseStatusCodePagesWithReExecute. Na przykład:

app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

Wyłącz strony kodu stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages].

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Kod obsługi wyjątków

Kod na stronach obsługi wyjątków może również zgłaszać wyjątki. Strony błędów produkcyjnych należy dokładnie przetestować i zachować szczególną ostrożność, aby uniknąć zgłaszania własnych wyjątków.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron wyjątków lub procedur obsługi. Odpowiedź musi zostać ukończona, w przeciwnym razie połączenie zostanie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer przechwytuje wyjątek przed wysłaniem nagłówków odpowiedzi, serwer wysyła odpowiedź 500 - Internal Server Error bez treści. Jeśli serwer napotka wyjątek po wysłaniu nagłówków odpowiedzi, zamknie połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące obsługujące wyjątki i filtry nie mają wpływu na to zachowanie.

Obsługa wyjątków przy uruchomieniu

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Host można skonfigurować do przechwytywania błędów podczas uruchamiania i przechwytywania szczegółowych błędów.

Warstwa hostingu może wyświetlić stronę błędu z przechwyconego błędu uruchomienia tylko wtedy, gdy błąd wystąpi po związaniu adresu hosta lub portu. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostująca rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

W przypadku uruchamiania na IIS (lub Azure App Service) albo IIS Express, jeśli nie można uruchomić procesu, moduł ASP.NET Core zwraca błąd 502.5 - awaria procesu. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Filtr wyjątku strony dewelopera bazy danych AddDatabaseDeveloperPageExceptionFilter przechwytuje wyjątki związane z bazą danych, które można rozwiązać za pomocą migracji przy użyciu Entity Framework Core. W przypadku wystąpienia tych wyjątków odpowiedź HTML jest generowana ze szczegółami możliwych akcji w celu rozwiązania problemu. Ta strona jest włączona tylko w środowisku developerskim. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Filtry wyjątków są przydatne do wyłapywania wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne jak wbudowane oprogramowanie pośredniczące do obsługi wyjątków. UseExceptionHandler Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

Dodatkowe zasoby

Przez Kirk Larkin, Tom Dykstra i Steve Smith

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core dla internetowych interfejsów API.

Wyświetl lub pobierz kod przykładowy. (Jak pobrać). Karta sieciowa w narzędziach deweloperskich przeglądarki F12 jest przydatna podczas testowania przykładowej aplikacji.

Strona wyjątku dla deweloperów

Na stronie wyjątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Szablony ASP.NET Core generują następujący kod:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Powyższy wyróżniony kod umożliwia stronę wyjątków dewelopera, gdy aplikacja jest uruchomiona w środowisku deweloperskim.

Szablony umieszczają UseDeveloperExceptionPage się na wczesnym etapie potoku oprogramowania middleware, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane przez oprogramowanie pośredniczące, które następuje później.

Powyższy kod włącza Stronę wyjątków dewelopera tylko wtedy, gdy aplikacja działa w środowisku deweloperskim. Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, 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.

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ą
  • Pliki cookie, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie gwarantuje dostarczenia żadnych informacji. Użyj logowania, aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla środowiska produkcyjnego, wywołaj UseExceptionHandler. Środowisko pośredniczące do obsługi wyjątków:

  • Przechwytuje i rejestruje nieobsługiwane wyjątki.
  • Ponownie wykonuje żądanie w alternatywnym potoku przy użyciu wskazanej ścieżki. Żądanie nie jest wykonywane ponownie, jeśli odpowiedź się zaczęła. Kod wygenerowany przez szablon ponownie wykonuje żądanie przy użyciu ścieżki /Error .

Ostrzeżenie

Jeśli alternatywny potok zgłasza wyjątek samodzielnie, oprogramowanie pośredniczące obsługi wyjątków ponownie wywróci oryginalny wyjątek.

W poniższym przykładzie w środowiskach innych niż deweloperskie UseExceptionHandler dodaje oprogramowanie pośredniczące do obsługi wyjątków.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera stronę Błąd (.cshtml) i PageModel klasę (ErrorModel) w folderze Pages . W przypadku aplikacji MVC szablon projektu zawiera metodę Error akcji i widok błędu Home dla kontrolera.

Oprogramowanie pośredniczące do obsługi wyjątków ponownie wykonuje żądanie z wykorzystaniem oryginalnej metody HTTP. Jeśli punkt końcowy procedury obsługi błędów jest ograniczony do określonego zestawu metod HTTP, jest uruchamiany tylko dla tych metod HTTP. Na przykład akcja kontrolera MVC korzystająca z atrybutu [HttpGet] jest uruchamiana tylko dla żądań GET. Aby upewnić się, że wszystkie żądania docierają do niestandardowej strony obsługi błędów, nie ograniczaj ich do określonego zestawu metod HTTP.

Aby obsłużyć wyjątki inaczej na podstawie oryginalnej metody HTTP:

  • Utwórz wiele metod obsługi dla sekcji Razor Pages. Na przykład użyj OnGet do obsługi wyjątków GET i użyj OnPost do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj [HttpGet] do obsługi wyjątków GET i użyj [HttpPost] do obsługi wyjątków POST.

Aby umożliwić nieuwierzytelnionym użytkownikom wyświetlanie niestandardowej strony obsługi błędów, upewnij się, że obsługuje dostęp anonimowy.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature, aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. Poniższy kod dodaje ExceptionMessage do domyślnie wygenerowanego Pages/Error.cshtml.cs przez szablony ASP.NET Core.

[ResponseCache(Duration=0, Location=ResponseCacheLocation.None, NoStore=true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }
    private readonly ILogger<ErrorModel> _logger;

    public ErrorModel(ILogger<ErrorModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
        HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
            _logger.LogError(ExceptionMessage);
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem bezpieczeństwa.

Aby przetestować wyjątek w przykładowej aplikacji:

  • Ustaw środowisko na produkcyjne.
  • Usuń komentarze z webBuilder.UseStartup<Startup>(); w Program.cs.
  • Na stronie głównej wybierz pozycję , aby wywołać wyjątek.

Procedura obsługi wyjątków lambda

Alternatywą dla dedykowanej strony obsługi wyjątków jest przekazanie wyrażenia lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Poniższy kod używa wyrażenia lambda do obsługi wyjątków:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;;
                context.Response.ContentType = "text/html";

                await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
                await context.Response.WriteAsync("ERROR!<br><br>\r\n");

                var exceptionHandlerPathFeature =
                    context.Features.Get<IExceptionHandlerPathFeature>();

                if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
                {
                    await context.Response.WriteAsync(
                                              "File error thrown!<br><br>\r\n");
                }

                await context.Response.WriteAsync(
                                              "<a href=\"/\">Home</a><br>\r\n");
                await context.Response.WriteAsync("</body></html>\r\n");
                await context.Response.WriteAsync(new string(' ', 512)); 
            });
        });
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Ostrzeżenie

Nie należy serwować poufnych informacji o błędach z IExceptionHandlerFeature lub IExceptionHandlerPathFeature do klientów. Ujawnianie błędów jest zagrożeniem bezpieczeństwa.

Aby przetestować obsługę wyjątków lambda w przykładowej aplikacji:

  • Ustaw środowisko na produkcyjne.
  • Usuń komentarze z webBuilder.UseStartup<StartupLambda>(); w Program.cs.
  • Na stronie głównej wybierz pozycję , aby wywołać wyjątek.

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony dla kodów stanu HTTP, takich jak 404 — Nie znaleziono. Gdy aplikacja ustawia kod stanu błędu HTTP 400-599, który nie ma treści, zwraca kod stanu i pustą treść odpowiedzi. Aby udostępnić strony kodów statusu, użyj środowiska pośredniczącego. Aby włączyć domyślne programy obsługi tylko tekstowe dla typowych kodów stanu błędów, wywołaj metodę UseStatusCodePages w metodzie Startup.Configure :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages();

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Należy wywołać UseStatusCodePages przed przetwarzaniem żądań przez oprogramowanie pośredniczące. Na przykład wywołaj wywołanie UseStatusCodePages przed oprogramowaniem pośredniczącym plików statycznych i oprogramowaniem pośredniczącym punktów końcowych.

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Na przykład przejdź do Home/Privacy2. Po UseStatusCodePages wywołaniu przeglądarka zwraca następujące elementy:

Status Code: 404; Not Found

UseStatusCodePages nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Aby przetestować UseStatusCodePages w przykładowej aplikacji:

  • Ustaw środowisko na produkcyjne.
  • Usuń komentarze z webBuilder.UseStartup<StartupUseStatusCodePages>(); w Program.cs.
  • Wybierz linki na stronie głównej.

Uwaga

Oprogramowanie pośredniczące stron kodu stanu nie przechwytuje wyjątków. Aby podać niestandardową stronę obsługi błędów, użyj strony przeznaczonej do obsługi wyjątków.

UseStatusCodePages z ciągiem znaków formatujących

Aby dostosować typ zawartości odpowiedzi i tekst, użyj przeciążenia UseStatusCodePages, które akceptuje typ zawartości i ciąg formatu:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(
        "text/plain", "Status code page, status code: {0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

W poprzednim kodzie {0} jest symbolem zastępczym kodu błędu.

UseStatusCodePages z ciągiem formatu nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Aby przetestować UseStatusCodePages w aplikacji przykładowej , usuń komentarze z webBuilder.UseStartup<StartupFormat>(); w Program.cs.

UseStatusCodePages z funkcją lambda

Aby określić niestandardowy kod obsługi błędów i generowania odpowiedzi, użyj przeciążenia UseStatusCodePages, które przyjmuje wyrażenie lambda.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(async context =>
    {
        context.HttpContext.Response.ContentType = "text/plain";

        await context.HttpContext.Response.WriteAsync(
            "Status code page, status code: " +
            context.HttpContext.Response.StatusCode);
    });

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

UseStatusCodePages z lambda nie jest zwykle używany w środowisku produkcyjnym, ponieważ zwraca komunikat, który nie jest przydatny dla użytkowników.

Aby przetestować UseStatusCodePages w aplikacji przykładowej, usuń komentarze z webBuilder.UseStartup<StartupStatusLambda>(); w Program.cs.

UseStatusCodePagesWithRedirects

Metoda rozszerzenia UseStatusCodePagesWithRedirects:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do punktu końcowego obsługi błędów podanego w szablonie adresu URL. Punkt końcowy obsługi błędów zwykle wyświetla informacje o błędzie i zwraca błąd HTTP 200.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Szablon adresu URL może zawierać symbol zastępczy dla kodu stanu {0}, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ zostanie zastąpiony przez PathBase aplikacji. Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę dla punktu końcowego. Aby zapoznać się z przykładem Razor strony, zobacz Pages/MyStatusCode.cshtml w przykładowej aplikacji.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać i zwracać oryginalnego kodu stanu z pierwotną odpowiedzią przekierowania.

Aby przetestować UseStatusCodePages w przykładowej aplikacji, usuń komentarze z webBuilder.UseStartup<StartupSCredirect>(); w Program.cs.

UżyjUseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca do klienta pierwotny kod statusu.
  • Generuje treść odpowiedzi poprzez ponowne przetworzenie potoku żądania przy użyciu alternatywnej ścieżki.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithReExecute("/MyStatusCode2", "?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Jeśli w aplikacji określono punkt końcowy, utwórz widok MVC lub stronę Razor dla tego punktu końcowego. Upewnij się, że UseStatusCodePagesWithReExecute zostało umieszczone przed UseRouting , aby można było przekierować żądanie do strony stanu. Aby zapoznać się z Razor przykładem strony, zobacz Pages/MyStatusCode2.cshtml w przykładowej aplikacji.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj i zwróć oryginalny kod stanu z odpowiedzią.

Szablony adresów URL i ciągów zapytania mogą zawierać symbol zastępczy {0} dla kodu stanu. Szablon adresu URL musi zaczynać się od /.

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class MyStatusCode2Model : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string ErrorStatusCode { get; set; }

    public string OriginalURL { get; set; }
    public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);

    public void OnGet(string code)
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
        ErrorStatusCode = code;

        var statusCodeReExecuteFeature = HttpContext.Features.Get<
                                               IStatusCodeReExecuteFeature>();
        if (statusCodeReExecuteFeature != null)
        {
            OriginalURL =
                statusCodeReExecuteFeature.OriginalPathBase
                + statusCodeReExecuteFeature.OriginalPath
                + statusCodeReExecuteFeature.OriginalQueryString;
        }
    }
}

Aby zapoznać się z Razor przykładem strony, zobacz Pages/MyStatusCode2.cshtml w przykładowej aplikacji.

Aby przetestować UseStatusCodePages w aplikacji testowej, usuń komentarze z webBuilder.UseStartup<StartupSCreX>(); w Program.cs.

Wyłącz strony kodu stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages].

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

public void OnGet()
{
    // using Microsoft.AspNetCore.Diagnostics;
    var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature != null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Kod obsługi wyjątków

Kod na stronach obsługi wyjątków może również zgłaszać wyjątki. Strony błędów produkcyjnych należy dokładnie przetestować i zachować szczególną ostrożność, aby uniknąć zgłaszania własnych wyjątków.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron wyjątków lub procedur obsługi. Odpowiedź musi zostać zakończona lub połączenie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer przechwytuje wyjątek przed wysłaniem 500 - Internal Server Error nagłówków odpowiedzi, serwer wysyła odpowiedź bez treści odpowiedzi. Jeśli serwer przechwyci wyjątek po wysłaniu nagłówków odpowiedzi, zamyka połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące odpowiedzialne za obsługę wyjątków oraz filtry nie wpływają na to zachowanie.

Obsługa wyjątków podczas uruchamiania systemu

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Można skonfigurować serwer do przechwytywania błędów uruchamiania oraz szczegółowych błędów.

Warstwa hostingu może wyświetlić stronę błędu przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostingu rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

W przypadku uruchamiania w usługach IIS (lub Azure App Service lub IIS Express) zwracany jest błąd 502.5 - Proces nie powiódł się przez moduł ASP.NET Core, jeśli nie można uruchomić procesu. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Filtr wyjątków na stronie programisty bazy danych AddDatabaseDeveloperPageExceptionFilter wychwytuje wyjątki związane z bazą danych, które można rozwiązać za pomocą migracji w programie Entity Framework Core. W przypadku wystąpienia tych wyjątków odpowiedź HTML jest generowana ze szczegółami możliwych akcji w celu rozwiązania problemu. Ta strona jest włączona tylko w środowisku programistycznym. Następujący kod został wygenerowany przez szablony ASP.NET Core Razor Pages po określeniu poszczególnych kont użytkowników:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Filtry wyjątków są przydatne do wyłapywania wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne, jak wbudowane oprogramowanie pośredniczące do obsługi wyjątków. UseExceptionHandler Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

Dodatkowe zasoby

Przez Tom Dykstra i Steve Smith

W tym artykule opisano typowe podejścia do obsługi błędów w aplikacjach internetowych platformy ASP.NET Core. Zobacz Obsługa błędów w internetowych interfejsach API opartych na kontrolerze ASP.NET Core dla internetowych interfejsów API.

Wyświetl lub pobierz kod przykładowy. (Jak pobrać).

Strona wyjątku dla deweloperów

Na Stronie Wyjątku Dewelopera są wyświetlane szczegółowe informacje o wyjątkach żądań. Szablony ASP.NET Core generują następujący kod:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Powyższy kod włącza stronę z wyjątkiem programisty, gdy aplikacja jest uruchomiona w środowisku deweloperskim.

Szablony umieszczają UseDeveloperExceptionPage przed wszystkim oprogramowaniem pośredniczącym, dzięki czemu wyjątki są przechwytywane w następującym oprogramowaniu pośredniczącym.

Powyższy kod włącza Stronę Wyjątków Dewelopera tylko wtedy, gdy aplikacja jest uruchomiona w środowisku deweloperskim. Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, 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.

Strona wyjątków programisty zawiera następujące informacje o wyjątku i żądaniu:

  • Śledzenie stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Nagłówki

Strona obsługi wyjątków

Aby skonfigurować niestandardową stronę obsługi błędów dla środowiska produkcyjnego, użyj oprogramowania pośredniczącego do obsługi wyjątków. Oprogramowanie pośredniczące:

  • Przechwytuje i rejestruje wyjątki.
  • Powtarza wykonanie żądania w alternatywnym potoku dla wskazanej strony lub kontrolera. Żądanie nie jest wykonywane ponownie, jeśli odpowiedź się rozpoczęła. Wygenerowany kod szablonu ponownie wykonuje żądanie do /Error.

W poniższym przykładzie, UseExceptionHandler dodaje oprogramowanie pośredniczące obsługi wyjątków w środowiskach niedeveloperskich:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

Szablon Razor aplikacji Pages zawiera stronę błędu (.cshtml) oraz klasę PageModel (ErrorModel) w folderze Pages. W przypadku aplikacji MVC szablon projektu zawiera metodę akcji "Error" i widok "Error" w kontrolerze Home.

Nie oznaczaj metody akcji obsługującej błędy atrybutami metody HTTP, takimi jak HttpGet. Bezpośrednie czasowniki blokują niektórym żądaniom dotarcie do metody. Zezwalaj na anonimowy dostęp do metody, jeśli nieuwierzytelniony użytkownicy powinni zobaczyć widok błędu.

Uzyskiwanie dostępu do wyjątku

Użyj IExceptionHandlerPathFeature, aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w kontrolerze lub na stronie obsługi błędów.

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem bezpieczeństwa.

Aby wyzwolić poprzednią stronę obsługi wyjątków, ustaw środowisko na produkcyjne i wymusić wyjątek.

Obsługa wyjątków lambda

Alternatywą dla niestandardowej strony obsługi wyjątków jest dostarczenie wyrażenia lambda do UseExceptionHandler. Użycie lambda umożliwia dostęp do błędu przed zwróceniem odpowiedzi.

Oto przykład użycia wyrażenia lambda do obsługi wyjątków:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
   app.UseExceptionHandler(errorApp =>
   {
        errorApp.Run(async context =>
        {
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            context.Response.ContentType = "text/html";

            await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
            await context.Response.WriteAsync("ERROR!<br><br>\r\n");

            var exceptionHandlerPathFeature = 
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
            }

            await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
            await context.Response.WriteAsync("</body></html>\r\n");
            await context.Response.WriteAsync(new string(' ', 512)); // IE padding
        });
    });
    app.UseHsts();
}

W poprzednim kodzie dodano await context.Response.WriteAsync(new string(' ', 512));, aby przeglądarka Internet Explorer wyświetlała komunikat o błędzie zamiast domyślnego komunikatu przeglądarki IE. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub.

Ostrzeżenie

Nie udostępniaj klientom poufnych informacji o błędach pochodzących z IExceptionHandlerFeature lub IExceptionHandlerPathFeature. Obsługa błędów jest zagrożeniem bezpieczeństwa.

Aby zobaczyć wynik działania obsługi wyjątków przy pomocy lambda w przykładowej aplikacji , użyj dyrektyw preprocesora ProdEnvironment i ErrorHandlerLambda, a następnie wybierz Wywołaj wyjątek na stronie głównej.

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony dla kodów stanu HTTP, takich jak 404 — Nie znaleziono. Aplikacja zwraca kod stanu i pustą treść odpowiedzi. Aby zapewnić strony kodów statusu, należy użyć oprogramowania pośredniczącego do obsługi Stron Kodów Statusu.

Oprogramowanie pośredniczące jest udostępniane przez pakiet Microsoft.AspNetCore.Diagnostics .

Aby włączyć domyślne obsługi tekstowe dla typowych kodów statusu błędów, wywołaj metodę UseStatusCodePages w metodzie Startup.Configure.

app.UseStatusCodePages();

Wywołaj UseStatusCodePages przed oprogramowaniem pośredniczącym do obsługi żądań (na przykład pośrednikiem plików statycznych i pośrednikiem MVC).

Jeśli UseStatusCodePages nie jest używany, przejście do adresu URL bez punktu końcowego zwraca komunikat o błędzie zależny od przeglądarki wskazujący, że nie można odnaleźć punktu końcowego. Na przykład przejdź do Home/Privacy2. Po UseStatusCodePages wywołaniu przeglądarka zwraca następujące elementy:

Status Code: 404; Not Found

UseStatusCodePages z ciągiem formatującym

Aby dostosować typ zawartości odpowiedzi oraz tekst, użyj przeciążenia UseStatusCodePages, które przyjmuje typ zawartości i ciąg formatu:

app.UseStatusCodePages(
    "text/plain", "Status code page, status code: {0}");

UseStatusCodePages z wyrażeniem lambda

Aby określić niestandardowy kod obsługi błędów i pisania odpowiedzi, użyj przeciążenia UseStatusCodePages , które przyjmuje wyrażenie lambda:

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";

    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

UseStatusCodePagesWithRedirects

UseStatusCodePagesWithRedirects Metoda rozszerzenia:

  • Wysyła do klienta kod stanu 302 — Znaleziono.
  • Przekierowuje klienta do lokalizacji podanej w szablonie adresu URL.
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

Szablon adresu URL może zawierać {0} symbol zastępczy kodu stanu, jak pokazano w przykładzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ zostanie zastąpiony przez aplikację PathBase. Jeśli wskażesz punkt końcowy w aplikacji, utwórz widok MVC lub stronę dla Razor punktu końcowego. Razor Przykład strony można znaleźć Pages/StatusCode.cshtml w przykładowej aplikacji.

Ta metoda jest często używana, gdy aplikacja:

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachować i zwracać oryginalnego kodu statusu z pierwotną odpowiedzią przekierowania.

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca oryginalny kod stanu do klienta.
  • Generuje treść odpowiedzi, ponownie uruchamiając potok żądania przy użyciu alternatywnej ścieżki.
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");

Jeśli wskażesz punkt końcowy w aplikacji, utwórz widok MVC lub stronę dla punktu końcowego. Upewnij się, że UseStatusCodePagesWithReExecute zostało umieszczone przed UseRouting , aby można było przekierować żądanie do strony stanu. Aby zobaczyć przykład strony Razor, przejdź do Pages/StatusCode.cshtml w przykładowej aplikacji.

Ta metoda jest często używana, gdy aplikacja powinna:

  • Przetwarzanie żądania bez przekierowywania do innego punktu końcowego. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla pierwotnie żądany punkt końcowy.
  • Zachowaj i zwróć oryginalny kod stanu z odpowiedzią.

Szablony adresów URL i ciągów zapytania mogą zawierać symbol zastępczy ({0}) dla kodu stanu. Szablon adresu URL musi zaczynać się od ukośnika (/). W przypadku używania symbolu zastępczego w ścieżce upewnij się, że punkt końcowy (strona lub kontroler) może przetworzyć segment ścieżki. Na przykład Razor strona przeznaczona do obsługi błędów powinna zaakceptować opcjonalną wartość segmentu ścieżki z dyrektywą @page:

@page "{code?}"

Punkt końcowy, który przetwarza błąd, może uzyskać oryginalny adres URL, który wygenerował błąd, jak pokazano w poniższym przykładzie:

var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
    OriginalURL =
        statusCodeReExecuteFeature.OriginalPathBase
        + statusCodeReExecuteFeature.OriginalPath
        + statusCodeReExecuteFeature.OriginalQueryString;
}

Nie oznaczaj metody akcji z obsługą błędów za pomocą atrybutów metody HTTP, takich jak HttpGet. Wyraźne czasowniki uniemożliwiają niektórym żądaniom dotarcie do metody. Zezwalaj na anonimowy dostęp do metody, jeśli nieuwierzytelniony użytkownicy powinni zobaczyć widok błędu.

Wyłącz strony kodu stanu

Aby wyłączyć strony kodu stanu dla kontrolera MVC lub metody akcji, użyj atrybutu [SkipStatusCodePages] .

Aby wyłączyć strony kodu stanu dla określonych żądań w Razor metodzie obsługi stron lub w kontrolerze MVC, użyj polecenia IStatusCodePagesFeature:

var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature != null)
{
    statusCodePagesFeature.Enabled = false;
}

Kod obsługi wyjątków

Kod na stronach obsługi wyjątków może zgłaszać wyjątki. Często dobrym pomysłem jest utworzenie stron błędów produkcyjnych składających się z czysto statycznej zawartości.

Nagłówki odpowiedzi

Po wysłaniu nagłówków odpowiedzi:

  • Aplikacja nie może zmienić kodu stanu odpowiedzi.
  • Nie można uruchomić żadnych stron wyjątków ani mechanizmów obsługi. Odpowiedź musi zostać ukończona, w przeciwnym razie połączenie zostanie przerwane.

Obsługa wyjątków serwera

Oprócz logiki obsługi wyjątków w aplikacji implementacja serwera HTTP może obsługiwać niektóre wyjątki. Jeśli serwer przechwytuje wyjątek przed wysłaniem nagłówków odpowiedzi, serwer wysyła odpowiedź 500 — wewnętrzny błąd serwera bez treści odpowiedzi. Jeśli serwer przechwytuje wyjątek po wysłaniu nagłówków odpowiedzi, serwer zamknie połączenie. Żądania, które nie są obsługiwane przez aplikację, są obsługiwane przez serwer. Każdy wyjątek, który występuje, gdy serwer obsługuje żądanie, jest obsługiwany przez obsługę wyjątków serwera. Niestandardowe strony błędów aplikacji, oprogramowanie pośredniczące do obsługi wyjątków oraz filtry nie wpływają na to zachowanie.

Obsługa wyjątków rozruchu

Tylko warstwa hostingu może obsługiwać wyjątki, które mają miejsce podczas uruchamiania aplikacji. Host można skonfigurować do przechwytywania błędów uruchamiania i szczegółowych błędów.

Warstwa hostingu może wyświetlić stronę z przechwyconym błędem podczas uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta lub portu. Jeśli powiązanie zakończy się niepowodzeniem:

  • Warstwa hostingu rejestruje wyjątek krytyczny.
  • Proces dotnet ulega awarii.
  • Nie jest wyświetlana strona błędu, gdy serwer HTTP ma wartość Kestrel.

pl-PL: W przypadku uruchamiania na IIS (lub App Service Azure) lub IIS Express, błąd 502.5 - Proces niepowodzenie jest zwracany przez moduł ASP.NET Core, jeśli nie można uruchomić procesu. Aby uzyskać więcej informacji, zobacz Rozwiązywanie problemów z programem ASP.NET Core w usłudze aplikacja systemu Azure i usługach IIS.

Strona błędu bazy danych

Oprogramowanie pośredniczące strony błędu bazy danych przechwytuje wyjątki związane z bazą danych, które można rozwiązać przy użyciu migracji programu Entity Framework. W przypadku wystąpienia tych wyjątków odpowiedź HTML ze szczegółami możliwych akcji w celu rozwiązania problemu jest generowana. Ta strona powinna być włączona tylko w środowisku deweloperskim. Włącz stronę, dodając kod do elementu Startup.Configure:

if (env.IsDevelopment())
{
    app.UseDatabaseErrorPage();
}

UseDatabaseErrorPage wymaga pakietu NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Filtry wyjątków

W aplikacjach MVC filtry wyjątków można skonfigurować globalnie lub na kontrolerze lub dla poszczególnych akcji. W Razor aplikacjach Pages można je skonfigurować globalnie lub na model strony. Te filtry obsługują wszelkie nieobsługiwane wyjątki występujące podczas wykonywania akcji kontrolera lub innego filtru. Aby uzyskać więcej informacji, zobacz Filtry w ASP.NET Core.

Napiwek

Filtry wyjątków są przydatne do wyłapywania wyjątków występujących w akcjach MVC, ale nie są tak elastyczne jak middleware obsługi wyjątków. Zalecamy używanie oprogramowania pośredniczącego. Używaj filtrów tylko wtedy, gdy musisz wykonywać obsługę błędów inaczej w zależności od wybranej akcji MVC.

Błędy stanu modelu

Aby uzyskać informacje na temat obsługi błędów stanu modelu, zobacz Powiązanie modelu i Walidacja modelu.

Dodatkowe zasoby