Udostępnij za pomocą


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

Note

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Warning

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

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 interfejsach API platformy ASP.NET Core.

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.

Warning

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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers
  • 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. 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 .

Warning

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 [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.";
        }
    }
}

Warning

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

Warning

Nie udostępniaj 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 do obsługi znanych wyjątków w centralnej lokalizacji. Interfejs zawiera jedną metodę , TryHandleAsyncktóra odbiera parametr HttpContext i Exception .

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.

Oprogramowanie pośredniczące do obsługi wyjątków iteruje przez zarejestrowane procedury obsługi wyjątków w kolejności, aż jedna z nich zwróci true z TryHandleAsync, co oznacza, że wyjątek został obsłużony. Jeśli program obsługi wyjątków obsługuje wyjątek, może powró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.

Począwszy od platformy .NET 10, domyślne zachowanie polega na tłumieniu emisji danych diagnostycznych takich jak logi i metryki dla obsługiwanych wyjątków (gdy TryHandleAsync zwraca truewartość). Różni się to od wcześniejszych wersji (.NET 8 i 9), gdzie diagnostyka była zawsze emitowana niezależnie od tego, czy wyjątek był obsługiwany. Zachowanie domyślne można zmienić, ustawiając ustawienie SuppressDiagnosticsCallback.

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:

  • 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 /Error strona .

SuppressDiagnosticsCallback

Od .NET 10 można kontrolować, czy oprogramowanie pośredniczące do obsługi wyjątków zapisuje diagnostykę dla obsługiwanych wyjątków poprzez skonfigurowanie właściwości SuppressDiagnosticsCallback w ExceptionHandlerOptions. Ten callback odbiera kontekst wyjątku i pozwala określić, czy diagnostyka powinna być ignorowana na podstawie określonego wyjątku czy żądania.

Aby przywrócić zachowanie wersji .NET 8 i 9, gdzie diagnostyka zawsze jest generowana dla obsługiwanych wyjątków, ustaw, aby wywołanie zwrotne zawsze zwracało false:

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    SuppressDiagnosticsCallback = context => false
});

Możesz również warunkowo pominąć diagnostykę na podstawie typu wyjątku lub innego kontekstu:

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    SuppressDiagnosticsCallback = context => context.Exception is ArgumentException
});

Jeśli wyjątek nie jest obsługiwany przez żadną IExceptionHandler implementację (wszystkie procedury obsługi zwracają false z TryHandleAsync), kontrola przechodzi do domyślnego zachowania i opcji z middleware, a diagnostyka jest generowana zgodnie ze standardowym zachowaniem middleware.

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.

Note

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.

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), ~ 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.

UseStatusCodePagesWithReExecute

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. 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 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.
  • 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 Accept (domyślnie: ):

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

Warning

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem 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

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 interfejsach API platformy ASP.NET Core.

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.

Warning

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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers
  • 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. 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 .

Warning

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 [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.";
        }
    }
}

Warning

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

Warning

Nie udostępniaj 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 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:

  • 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 /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.

Note

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.

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), ~ 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.

UseStatusCodePagesWithReExecute

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. 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 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.
  • 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 Accept (domyślnie: ):

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

Warning

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem 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 interfejsach API platformy ASP.NET Core.

Strona wyjątku dla deweloperów

Na Stronie wyjątków dewelopera wyświetlane są szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Aplikacje ASP.NET Core domyślnie włączają stronę z wyjątkiem dla programistów, gdy spełnione są oba następujące 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ą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.

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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers

Strona wyjątków dla deweloperów nie gwarantuje podania żadnych informacji. Użyj logowania, 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. 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 .

Warning

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 [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.";
        }
    }
}

Warning

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

Warning

Nie udostępniaj 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>]. 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 TryHandleException metoda zwraca falsewartość , więc zostanie wyświetlona strona wyjątku dewelopera.

W innych środowiskach:

  • Funkcja CustomExceptionHandler jest wywoływana jako pierwsza w celu obsługi 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 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.

Note

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.

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), ~ 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.

UseStatusCodePagesWithReExecute

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. 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 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.
  • 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 Accept (domyślnie: ):

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

Warning

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem 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 interfejsach API platformy ASP.NET Core.

Strona wyjątku dla deweloperów

Na Stronie wyjątków dewelopera wyświetlane są szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Aplikacje ASP.NET Core domyślnie włączają stronę z wyjątkiem dla programistów, gdy spełnione są oba następujące 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ą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.

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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers

Strona wyjątków dla deweloperów nie gwarantuje podania żadnych informacji. Użyj logowania, 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. 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 .

Warning

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 [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.";
        }
    }
}

Warning

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

Warning

Nie udostępniaj 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 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.

Note

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.

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), ~ 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.

UseStatusCodePagesWithReExecute

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. 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 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.
  • 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 Accept (domyślnie: ):

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

Warning

Nie udostępniaj klientom poufnych informacji o błędach. Obsługa błędów jest zagrożeniem 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 interfejsach API platformy ASP.NET Core dla internetowych interfejsów API .

Strona wyjątku dla deweloperów

Na Stronie wyjątków dewelopera wyświetlane są szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Aplikacje ASP.NET Core domyślnie włączają stronę z wyjątkiem dla programistów, gdy spełnione są oba następujące 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ą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.

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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers

Strona wyjątków dla deweloperów nie gwarantuje podania żadnych informacji. Użyj logowania, 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. 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 .

Warning

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

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 [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.";
        }
    }
}

Warning

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

Warning

Nie udostępniaj 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 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.

Note

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.

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), ~ 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.

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca oryginalny kod stanu do klienta.
  • Generuje treść odpowiedzi przez ponowne wykonanie potoku żą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ę 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. 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 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.

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 interfejsach API platformy 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ątków dewelopera wyświetlane są 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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers

Strona wyjątków dla deweloperów nie gwarantuje podania żadnych informacji. Użyj logowania, 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. 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 .

Warning

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

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

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ę 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 [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. 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";
        }
    }
}

Warning

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.

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:

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

Warning

Nie należy serwować poufnych informacji o błędach z lub IExceptionHandlerFeature do klientów. Obsługa 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 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 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();
    });
}

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. 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.

Note

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:

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 ciąg 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 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:

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

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.
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 {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. 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 przy początkowej odpowiedzi przekierowania.

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

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca oryginalny kod stanu do klienta.
  • Generuje treść odpowiedzi przez ponowne wykonanie 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 określono punkt końcowy w aplikacji, utwórz widok MVC lub Razor 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 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 przykładowej , 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 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. 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 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.

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 interfejsach API platformy 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 ASP.NET Podstawowe środowiska uruchomieniowe.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Pliki cookie, jeśli istnieją
  • Headers

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ę zaczęł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";
        }
    }
}

Warning

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.

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.

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.

Warning

Nie należy serwować poufnych informacji o błędach z lub IExceptionHandlerFeature do klientów. 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 programy obsługi tylko tekstowe dla typowych kodów stanu 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 formatu

Aby dostosować typ zawartości odpowiedzi i 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 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 element aplikacji 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 zachowywać i zwracać oryginalnego kodu stanu przy początkowej odpowiedzi przekierowania.

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

  • Zwraca oryginalny kod stanu do klienta.
  • Generuje treść odpowiedzi przez ponowne wykonanie potoku żą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 Razor punktu końcowego. Upewnij się, że UseStatusCodePagesWithReExecute zostało umieszczone przed UseRouting , aby można było przekierować żądanie do strony stanu. Razor Przykład strony można znaleźć 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 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.

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 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 — wewnętrzny błąd serwera 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 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

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.

Tip

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