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

Uwaga

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

Ważne

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

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

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 ASP.NET Core internetowych interfejsów API i Obsługa błędów w minimalnych aplikacjach interfejsu API.

Strona wyjątku dla deweloperów

Na stronie wyjątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

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

Strona wyjątku dla deweloperów jest uruchamiana na wczesnym etapie potoku oprogramowania pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w następujący sposób oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

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

Na poniższej ilustracji przedstawiono przykładową stronę wyjątku dla deweloperów z wyświetlonymi metadanymi routingu wybranego i punktu końcowego:

Strona wyjątku dewelopera z wybraną trasą i wyświetlonymi metadanymi punktu końcowego

Strona wyjątku dla deweloperów nie jest gwarantowana, aby podać żadne informacje. Użyj rejestrowania , aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

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

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

Ostrzeżenie

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

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

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

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

var app = builder.Build();

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

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

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

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

  • W obszarze Razor Pages utwórz wiele metod obsługi. Na przykład użyj polecenia OnGet , aby obsługiwać wyjątki GET i używać OnPost ich do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj polecenia [HttpGet] , aby obsługiwać wyjątki GET i używać [HttpPost] ich 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature metody , aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony:

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

Ostrzeżenie

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

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

Ostrzeżenie

Nie należy udostępniać 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ługiwane wyjątki.

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:

  • Element jest wywoływany CustomExceptionHandler jako pierwszy 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:

  • Element jest wywoływany CustomExceptionHandler jako pierwszy 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.

Uwaga

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

UseStatusCodePages z ciągiem formatu

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

var app = builder.Build();

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

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

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

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

UseStatusCodePages z lambda

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

var app = builder.Build();

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

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

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

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

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ć {0} symbol zastępczy kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ element zostanie zastąpiony PathBaseprzez aplikację . Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę punktu końcowego.

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

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

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

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

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

var app = builder.Build();

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

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

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

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

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

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

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

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

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wysyłanie za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowaniu ich przetwarzania na obiekcie, HttpContext aby uniknąć jego ponownego utworzenia. W przypadku obsługi treści żądania oznacza to buforowanie lub buforowanie wyników, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłączanie stron kodu stanu

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

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

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

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

Kod obsługi wyjątków

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

Nagłówki odpowiedzi

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

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

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

Filtr 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 deweloperów. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

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

Filtry wyjątków

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

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

Błędy stanu modelu

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

Szczegóły problemu

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

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

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

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 szczegółów problemu.

Dostosowywanie szczegółów problemu

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

  1. Korzystanie z polecenia ProblemDetailsOptions.CustomizeProblemDetails
  2. Używanie niestandardowego IProblemDetailsWriter
  3. Wywoływanie IProblemDetailsService oprogramowania pośredniczącego

CustomizeProblemDetails Operacji

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

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

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Na przykład wynik punktu końcowego generuje następującą HTTP Status 400 Bad Request treść odpowiedzi szczegółów problemu:

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

Niestandardowe 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 IProblemDetailsWriterobiektu należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem metody AddRazorPages, AddControllers, AddControllersWithViewslub 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 z ProblemDetailsOptionsCustomizeProblemDetails programem jest ustawienie oprogramowania pośredniczącego ProblemDetails w programie . Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

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

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

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

Tworzenie ładunku ProblemDetails dla wyjątków

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

Ostrzeżenie

Nie należy udostępniać 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 ASP.NET Core internetowych interfejsów API i Obsługa błędów w minimalnych aplikacjach interfejsu API.

Strona wyjątku dla deweloperów

Na stronie wyjątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

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

Strona wyjątku dla deweloperów jest uruchamiana na wczesnym etapie potoku oprogramowania pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w następujący sposób oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie jest gwarantowana, aby podać żadne informacje. Użyj rejestrowania , aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

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

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

Ostrzeżenie

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

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

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

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

var app = builder.Build();

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

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

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

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

  • W obszarze Razor Pages utwórz wiele metod obsługi. Na przykład użyj polecenia OnGet , aby obsługiwać wyjątki GET i używać OnPost ich do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj polecenia [HttpGet] , aby obsługiwać wyjątki GET i używać [HttpPost] ich 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature metody , aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony:

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

Ostrzeżenie

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

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

Ostrzeżenie

Nie należy udostępniać 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ługiwane wyjątki.

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:

  • Element jest wywoływany CustomExceptionHandler jako pierwszy 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:

  • Element jest wywoływany CustomExceptionHandler jako pierwszy 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.

Uwaga

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

UseStatusCodePages z ciągiem formatu

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

var app = builder.Build();

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

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

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

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

UseStatusCodePages z lambda

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

var app = builder.Build();

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

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

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

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

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ć {0} symbol zastępczy kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ element zostanie zastąpiony PathBaseprzez aplikację . Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę punktu końcowego.

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

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

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

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

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

var app = builder.Build();

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

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

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

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

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

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

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

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

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wysyłanie za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowaniu ich przetwarzania na obiekcie, HttpContext aby uniknąć jego ponownego utworzenia. W przypadku obsługi treści żądania oznacza to buforowanie lub buforowanie wyników, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłączanie stron kodu stanu

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

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

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

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

Kod obsługi wyjątków

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

Nagłówki odpowiedzi

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

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

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

Filtr 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 deweloperów. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

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

Filtry wyjątków

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

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

Błędy stanu modelu

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

Szczegóły problemu

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

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

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

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 szczegółów problemu.

Dostosowywanie szczegółów problemu

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

  1. Korzystanie z polecenia ProblemDetailsOptions.CustomizeProblemDetails
  2. Używanie niestandardowego IProblemDetailsWriter
  3. Wywoływanie IProblemDetailsService oprogramowania pośredniczącego

CustomizeProblemDetails Operacji

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

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

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Na przykład wynik punktu końcowego generuje następującą HTTP Status 400 Bad Request treść odpowiedzi szczegółów problemu:

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

Niestandardowe 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 IProblemDetailsWriterobiektu należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem metody AddRazorPages, AddControllers, AddControllersWithViewslub 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 z ProblemDetailsOptionsCustomizeProblemDetails programem jest ustawienie oprogramowania pośredniczącego ProblemDetails w programie . Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

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

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

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

Tworzenie ładunku ProblemDetails dla wyjątków

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

Ostrzeżenie

Nie należy udostępniać 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 ASP.NET Core internetowych interfejsów API i Obsługa błędów w minimalnych aplikacjach interfejsu API.

Strona wyjątku dla deweloperów

Na stronie wyjątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

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

Strona wyjątku dla deweloperów jest uruchamiana na wczesnym etapie potoku oprogramowania pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w następujący sposób oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie jest gwarantowana, aby podać żadne informacje. Użyj rejestrowania , aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

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

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

Ostrzeżenie

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

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

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

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

var app = builder.Build();

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

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

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

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

  • W obszarze Razor Pages utwórz wiele metod obsługi. Na przykład użyj polecenia OnGet , aby obsługiwać wyjątki GET i używać OnPost ich do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj polecenia [HttpGet] , aby obsługiwać wyjątki GET i używać [HttpPost] ich 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature metody , aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony:

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

Ostrzeżenie

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

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

Ostrzeżenie

Nie należy udostępniać 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.

Uwaga

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

UseStatusCodePages z ciągiem formatu

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

var app = builder.Build();

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

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

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

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

UseStatusCodePages z lambda

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

var app = builder.Build();

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

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

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

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

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ć {0} symbol zastępczy kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ element zostanie zastąpiony PathBaseprzez aplikację . Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę punktu końcowego.

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

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

UseStatusCodePagesWithReExecute

UseStatusCodePagesWithReExecute Metoda rozszerzenia:

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

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

var app = builder.Build();

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

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

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

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

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

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

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

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

  • Oprogramowanie pośredniczące musi obsługiwać ponowne wysyłanie za pomocą tego samego żądania. Zwykle oznacza to czyszczenie ich stanu po wywołaniu _next lub buforowaniu ich przetwarzania na obiekcie, HttpContext aby uniknąć jego ponownego utworzenia. W przypadku obsługi treści żądania oznacza to buforowanie lub buforowanie wyników, takich jak czytnik formularzy.
  • Usługi o określonym zakresie pozostają takie same.

Wyłączanie stron kodu stanu

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

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

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

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

Kod obsługi wyjątków

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

Nagłówki odpowiedzi

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

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

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

Filtr 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 deweloperów. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

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

Filtry wyjątków

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

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

Błędy stanu modelu

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

Szczegóły problemu

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

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

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

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 szczegółów problemu.

Dostosowywanie szczegółów problemu

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

  1. Korzystanie z polecenia ProblemDetailsOptions.CustomizeProblemDetails
  2. Używanie niestandardowego IProblemDetailsWriter
  3. Wywoływanie IProblemDetailsService oprogramowania pośredniczącego

CustomizeProblemDetails Operacji

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

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

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Na przykład wynik punktu końcowego generuje następującą HTTP Status 400 Bad Request treść odpowiedzi szczegółów problemu:

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

Niestandardowe 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 IProblemDetailsWriterobiektu należy zarejestrować niestandardowy IProblemDetailsWriter przed wywołaniem metody AddRazorPages, AddControllers, AddControllersWithViewslub 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 z ProblemDetailsOptionsCustomizeProblemDetails programem jest ustawienie oprogramowania pośredniczącego ProblemDetails w programie . Odpowiedź na szczegóły problemu może zostać napisana przez wywołanie metody IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

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

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

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

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

        return Ok(Numerator / Denominator);
    }

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

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

}

Tworzenie ładunku ProblemDetails dla wyjątków

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

Ostrzeżenie

Nie należy udostępniać 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 internetowych platformy ASP.NET Core dla internetowych interfejsów API.

Strona wyjątku dla deweloperów

Na stronie wyjątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. aplikacje ASP.NET Core domyślnie włączają stronę wyjątku dla deweloperów, gdy obie te aplikacje:

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

Strona wyjątku dla deweloperów jest uruchamiana na wczesnym etapie potoku oprogramowania pośredniczącego, dzięki czemu może przechwytywać nieobsługiwane wyjątki zgłaszane w następujący sposób oprogramowania pośredniczącego.

Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie jest gwarantowana, aby podać żadne informacje. Użyj rejestrowania , aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

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

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

Ostrzeżenie

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

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

var app = builder.Build();

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

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

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

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

  • W obszarze Razor Pages utwórz wiele metod obsługi. Na przykład użyj polecenia OnGet , aby obsługiwać wyjątki GET i używać OnPost ich do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj polecenia [HttpGet] , aby obsługiwać wyjątki GET i używać [HttpPost] ich 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. W poniższym przykładzie użyto IExceptionHandlerPathFeature metody , aby uzyskać więcej informacji na temat wyjątku, który został zgłoszony:

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

Ostrzeżenie

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

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

Ostrzeżenie

Nie należy udostępniać 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.

Uwaga

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

UseStatusCodePages z ciągiem formatu

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

var app = builder.Build();

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

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

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

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

UseStatusCodePages z lambda

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

var app = builder.Build();

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

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

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

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

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ć {0} symbol zastępczy kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ element zostanie zastąpiony PathBaseprzez aplikację . Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę punktu końcowego.

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

  • Powinien przekierować klienta do innego punktu końcowego, zwykle w przypadkach, gdy inna aplikacja przetwarza błąd. W przypadku aplikacji internetowych pasek adresu przeglądarki klienta odzwierciedla przekierowany punkt końcowy.
  • Nie należy zachowywać i zwracać oryginalnego kodu stanu z początkową 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 . Na przykład:

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

Wyłączanie stron kodu stanu

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

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

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

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

Kod obsługi wyjątków

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

Nagłówki odpowiedzi

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

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

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

Filtr 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 deweloperów. Poniższy kod dodaje filtr wyjątku strony dewelopera bazy danych:

var builder = WebApplication.CreateBuilder(args);

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

Filtry wyjątków

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

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

Błędy stanu modelu

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

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 internetowych 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ątku dewelopera są wyświetlane szczegółowe informacje o nieobsługiwanych wyjątkach żądań. Szablony ASP.NET Core generują następujący kod:

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

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

    app.UseRouting();

    app.UseAuthorization();

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

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

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

Powyższy kod włącza stronę wyjątku dewelopera tylko wtedy, gdy aplikacja działa w środowisku dewelopera. Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli istnieją
  • Nagłówki

Strona wyjątku dla deweloperów nie jest gwarantowana, aby podać żadne informacje. Użyj rejestrowania , aby uzyskać pełne informacje o błędzie.

Strona obsługi wyjątków

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

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

Ostrzeżenie

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

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

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

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

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

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

  • W obszarze Razor Pages utwórz wiele metod obsługi. Na przykład użyj polecenia OnGet , aby obsługiwać wyjątki GET i używać OnPost ich do obsługi wyjątków POST.
  • W przypadku wzorca MVC zastosuj atrybuty czasownika HTTP do wielu akcji. Na przykład użyj polecenia [HttpGet] , aby obsługiwać wyjątki GET i używać [HttpPost] ich 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania w procedurze obsługi błędów. Poniższy kod dodaje ExceptionMessage domyślny Pages/Error.cshtml.cs kod wygenerowany przez szablony ASP.NET Core:

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

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

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

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

Ostrzeżenie

Nie należy udostępniać 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 środowisko produkcyjne.
  • Usuń komentarze z webBuilder.UseStartup<Startup>(); elementu w pliku Program.cs.
  • Wybierz pozycję Wyzwól wyjątek na stronie głównej.

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

Ostrzeżenie

Nie należy obsługiwać poufnych informacji o błędach od IExceptionHandlerFeature klientów lub IExceptionHandlerPathFeature 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 środowisko produkcyjne.
  • Usuń komentarze z webBuilder.UseStartup<StartupLambda>(); elementu w pliku Program.cs.
  • Wybierz pozycję Wyzwól wyjątek na stronie głównej.

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 kodu stanu, użyj oprogramowania pośredniczącego stron kodu stanu. 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 folderu 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 aplikację przykładową:

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

Uwaga

Oprogramowanie pośredniczące stron kodu stanu nie przechwytuje wyjątków. Aby podać niestandardową stronę obsługi błędów, użyj strony 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ć aplikację przykładową, usuń komentarze z webBuilder.UseStartup<StartupFormat>(); elementu w programie Program.cs.UseStatusCodePages

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ć aplikację przykładową, usuń komentarze z webBuilder.UseStartup<StartupStatusLambda>(); elementu w programie Program.cs.UseStatusCodePages

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ć {0} symbol zastępczy kodu stanu, jak pokazano w poprzednim kodzie. Jeśli szablon adresu URL zaczyna się od ~ (tylda), ~ element zostanie zastąpiony PathBaseprzez aplikację . Podczas określania punktu końcowego w aplikacji utwórz widok MVC lub Razor stronę punktu końcowego. Aby zapoznać się z Razor przykładem strony, zobacz Pages/MyStatusCode.cshtml w przykładowej aplikacji.

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

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

Aby przetestować aplikację przykładową, usuń komentarze z webBuilder.UseStartup<StartupSCredirect>(); elementu w programie Program.cs.UseStatusCodePages

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} 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ć aplikację przykładową, usuń komentarze z webBuilder.UseStartup<StartupSCreX>(); elementu w programie Program.cs.UseStatusCodePages

Wyłączanie stron kodu stanu

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

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

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

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

Kod obsługi wyjątków

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

Nagłówki odpowiedzi

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

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

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

Filtr 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 deweloperów. 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 w przypadku podlewek wyjątków występujących w ramach akcji MVC, ale nie są tak elastyczne, jak wbudowane oprogramowanie pośredniczące obsługujące wyjątki. UseExceptionHandler Zalecamy użycie polecenia UseExceptionHandler, chyba że musisz wykonać obsługę błędów inaczej w zależności od wybranej akcji MVC.

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 internetowych 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 umożliwia stronę wyjątku dewelopera, gdy aplikacja jest uruchomiona w środowisku dewelopera.

Szablony umieszczają UseDeveloperExceptionPage przed wszelkim oprogramowaniem pośredniczącym, dzięki czemu wyjątki są przechwytywane w poniższym oprogramowaniem pośredniczącym.

Powyższy kod włącza stronę wyjątku dewelopera tylko wtedy, gdy aplikacja jest uruchomiona w środowisku dewelopera. Szczegółowe informacje o wyjątku nie powinny być wyświetlane publicznie, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

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

  • Ślad stosu
  • Parametry ciągu zapytania, jeśli istnieją
  • Cookies, jeśli istnieją
  • Nagłówki

Strona obsługi wyjątków

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

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

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

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

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

Nie oznaczaj metody akcji procedury obsługi błędów z atrybutami metody HTTP, takimi jak HttpGet. Jawne czasowniki uniemożliwiają dotarcie niektórych żądań 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 polecenia , aby uzyskać dostęp do wyjątku i oryginalnej ścieżki żądania na kontrolerze lub stronie procedury obsługi błędów:

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

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

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

Ostrzeżenie

Nie należy udostępniać 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.

Procedura 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 jest dodawany, await context.Response.WriteAsync(new string(' ', 512)); aby przeglądarka Internet Explorer wyświetlała komunikat o błędzie, a nie komunikat o błędzie programu IE. Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub.

Ostrzeżenie

Nie należy obsługiwać poufnych informacji o błędach od IExceptionHandlerFeature klientów lub IExceptionHandlerPathFeature do klientów. Obsługa błędów jest zagrożeniem bezpieczeństwa.

Aby wyświetlić wynik wyjątku obsługi lambda w przykładowej aplikacji, użyj ProdEnvironment dyrektyw preprocesora i ErrorHandlerLambda i wybierz pozycję Wyzwól wyjątek na stronie głównej.

UseStatusCodePages

Domyślnie aplikacja ASP.NET Core nie udostępnia strony kodowej stanu kodów stanu HTTP, takich jak 404 — Nie znaleziono. Aplikacja zwraca kod stanu i pustą treść odpowiedzi. Aby udostępnić strony kodu stanu, użyj oprogramowania pośredniczącego Strony kodu stanu.

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 przed UseStatusCodePages żądaniem obsługi oprogramowania pośredniczącego (na przykład statyczne oprogramowanie pośredniczące plików i oprogramowanie pośredniczące 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 folderu 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), ~ element zostanie zastąpiony PathBaseprzez aplikację . Jeśli wskażesz punkt końcowy w aplikacji, utwórz widok MVC lub Razor stronę 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 z początkową 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 Razor stronę 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 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 procedury obsługi błędów z atrybutami metody HTTP, takimi jak HttpGet. Jawne czasowniki uniemożliwiają dotarcie niektórych żądań do metody . Zezwalaj na anonimowy dostęp do metody, jeśli nieuwierzytelniony użytkownicy powinni zobaczyć widok błędu.

Wyłączanie stron 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ć ukończona lub przerwane połączenie.

Obsługa wyjątków serwera

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

Obsługa wyjątków uruchamiania

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 przechwyconego błędu uruchamiania tylko wtedy, gdy błąd występuje po powiązaniu adresu hosta/portu. Jeśli powiązanie zakończy się niepowodzeniem:

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

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

Strona błędu bazy danych

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

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

UseDatabaseErrorPagewymaga pakietu NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Filtry wyjątków

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

Napiwek

Filtry wyjątków są przydatne w przypadku podlewek wyjątków występujących w akcjach MVC, ale nie są tak elastyczne, jak oprogramowanie pośredniczące 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