Gestire gli errori in ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere Criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Tom Dykstra

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere anche Gestire gli errori nelle API Web basate su controller di base ASP.NET e Gestire gli errori nelle API minime.

Per Blazor indicazioni sulla gestione degli errori, che aggiunge o sostituisce le indicazioni contenute in questo articolo, vedere Gestire gli errori nelle app ASP.NET CoreBlazor.

Pagina delle eccezioni per gli sviluppatori

Nella pagina Eccezioni sviluppatore vengono visualizzate informazioni dettagliate sulle eccezioni di richiesta non gestite. DeveloperExceptionPageMiddleware Usa per acquisire eccezioni sincrone e asincrone dalla pipeline HTTP e per generare risposte di errore. La pagina delle eccezioni dello sviluppatore viene eseguita nelle prime fasi della pipeline middleware, in modo che possa rilevare eccezioni non gestite generate nel middleware che segue.

ASP.NET App core abilitano la pagina delle eccezioni per sviluppatori per impostazione predefinita quando entrambe:

Le app create usando i modelli precedenti, ovvero usando WebHost.CreateDefaultBuilder, possono abilitare la pagina delle eccezioni per sviluppatori chiamando app.UseDeveloperExceptionPage.

Avviso

Non abilitare la pagina eccezioni per sviluppatori a meno che l'app non sia in esecuzione nell'ambiente di sviluppo. Non condividere pubblicamente informazioni dettagliate sulle eccezioni quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Delle eccezioni per sviluppatori può includere le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie, se presenti
  • Intestazioni
  • Metadati dell'endpoint, se presenti

La pagina Delle eccezioni per gli sviluppatori non fornisce alcuna informazione. Usare Registrazione per informazioni complete sull'errore.

L'immagine seguente mostra una pagina di eccezioni per sviluppatori di esempio con animazione per visualizzare le schede e le informazioni visualizzate:

Pagina delle eccezioni per gli sviluppatori animata per visualizzare ogni scheda selezionata.

In risposta a una richiesta con un'intestazione Accept: text/plain , la pagina eccezioni sviluppatore restituisce testo normale anziché HTML. Ad esempio:

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, chiamare UseExceptionHandler. Questo middleware per la gestione delle eccezioni:

  • Rileva e registra eccezioni non gestite.
  • Esegue nuovamente la richiesta in una pipeline alternativa usando il percorso indicato. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta usando il /Error percorso.

Avviso

Se la pipeline alternativa genera un'eccezione propria, il middleware di gestione delle eccezioni genera nuovamente l'eccezione originale.

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • Per l'overload UseExceptionHandler(IApplicationBuilder, String) usato nei modelli, viene modificato solo il percorso della richiesta e i dati della route vengono cancellati. I dati delle richieste, ad esempio intestazioni, metodo ed elementi, vengono riutilizzati così come sono.
  • I servizi con ambito rimangono invariati.

Nell'esempio seguente viene UseExceptionHandler aggiunto il middleware di gestione delle eccezioni in ambienti non di sviluppo:

var app = builder.Build();

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un Error metodo di azione e una visualizzazione Errore per il Home controller.

Il middleware di gestione delle eccezioni esegue nuovamente la richiesta usando il metodo HTTP originale . Se un endpoint del gestore errori è limitato a un set specifico di metodi HTTP, viene eseguito solo per tali metodi HTTP. Ad esempio, un'azione del controller MVC che usa l'attributo [HttpGet] viene eseguita solo per le richieste GET. Per assicurarsi che tutte le richieste raggiungano la pagina di gestione degli errori personalizzata, non limitarle a un set specifico di metodi HTTP.

Per gestire le eccezioni in modo diverso in base al metodo HTTP originale:

  • Per Razor Pages creare più metodi del gestore. Ad esempio, usare OnGet per gestire le eccezioni GET e usare OnPost per gestire le eccezioni POST.
  • Per MVC, applicare attributi verbi HTTP a più azioni. Ad esempio, usare [HttpGet] per gestire le eccezioni GET e usare [HttpPost] per gestire le eccezioni POST.

Per consentire agli utenti non autenticati di visualizzare la pagina di gestione degli errori personalizzata, assicurarsi che supporti l'accesso anonimo.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un gestore errori. Nell'esempio seguente viene IExceptionHandlerPathFeature usato per ottenere altre informazioni sull'eccezione generata:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Il codice seguente usa un'espressione lambda per la gestione delle eccezioni:

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

Un altro modo per usare un'espressione lambda consiste nell'impostare il codice di stato in base al tipo di eccezione, come nell'esempio seguente:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

IExceptionHandler

IExceptionHandler è un'interfaccia che offre allo sviluppatore un callback per la gestione delle eccezioni note in una posizione centrale.

IExceptionHandler le implementazioni vengono registrate chiamando IServiceCollection.AddExceptionHandler<T>. La durata di un'istanza IExceptionHandler è singleton. È possibile aggiungere più implementazioni e vengono chiamate nell'ordine registrato.

Se un gestore eccezioni gestisce una richiesta, può tornare true a interrompere l'elaborazione. Se un'eccezione non viene gestita da alcun gestore eccezioni, il controllo esegue il fallback al comportamento predefinito e alle opzioni del middleware. Metriche e log diversi vengono generati per le eccezioni gestite e non gestite.

L'esempio seguente illustra un'implementazione 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);
        }
    }
}

L'esempio seguente illustra come registrare un'implementazione IExceptionHandler per l'inserimento delle dipendenze:

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

Quando il codice precedente viene eseguito nell'ambiente di sviluppo:

  • Viene CustomExceptionHandler chiamato prima di tutto per gestire un'eccezione.
  • Dopo la registrazione dell'eccezione, il TryHandleAsync metodo restituisce false, in modo che venga visualizzata la pagina delle eccezioni per gli sviluppatori.

In altri ambienti:

  • Viene CustomExceptionHandler chiamato prima di tutto per gestire un'eccezione.
  • Dopo la registrazione dell'eccezione, il TryHandleAsync metodo restituisce false, in modo che venga visualizzata la /Error pagina .

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non fornisce una tabella codici di stato per i codici di stato degli errori HTTP, ad esempio 404 - Non trovato. Quando l'app imposta un codice di stato di errore HTTP 400-599 che non ha un corpo, restituisce il codice di stato e un corpo della risposta vuoto. Per abilitare i gestori predefiniti solo testo per i codici di stato di errore comuni, chiamare UseStatusCodePages in Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste. Ad esempio, chiamare UseStatusCodePages prima del middleware dei file statici e del middleware degli endpoint.

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Quando UseStatusCodePages viene chiamato, il browser restituisce la risposta seguente:

Status Code: 404; Not Found

UseStatusCodePages non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile per gli utenti.

Nota

Il middleware delle tabelle codici di stato non intercetta le eccezioni. Per fornire una pagina di gestione degli errori personalizzata, usare la pagina del gestore eccezioni.

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

Nel codice {0} precedente è un segnaposto per il codice di errore.

UseStatusCodePages con una stringa di formato non viene in genere usata nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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 con un'espressione lambda non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePagesWithRedirects

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client all'endpoint di gestione degli errori fornito nel modello di URL. L'endpoint di gestione degli errori visualizza in genere informazioni sugli errori e restituisce HTTP 200.
var app = builder.Build();

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

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

Il modello di URL può includere un {0} segnaposto per il codice di stato, come illustrato nel codice precedente. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Quando si specifica un endpoint nell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
  • Non modifica il codice di stato prima o dopo l'esecuzione di nuovo della pipeline.

La nuova esecuzione della pipeline può modificare il codice di stato della risposta, perché la nuova pipeline ha il controllo completo del codice di stato. Se la nuova pipeline non modifica il codice di stato, il codice di stato originale verrà inviato al client.

var app = builder.Build();

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

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

Se viene specificato un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

Il modello di URL deve iniziare con / e può includere un segnaposto {0} per il codice di stato. Per passare il codice di stato come parametro di stringa di query, passare un secondo argomento in UseStatusCodePagesWithReExecute. Ad esempio:

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

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

        }
    }
}

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • I servizi con ambito rimangono invariati.

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages].

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice nelle pagine di gestione delle eccezioni può anche generare eccezioni. Le pagine di errore di produzione devono essere testate accuratamente e prestare particolare attenzione per evitare di generare eccezioni proprie.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni in un'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se il server rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una 500 - Internal Server Error risposta senza un corpo della risposta. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste non gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il filtro AddDatabaseDeveloperPageExceptionFilter eccezioni della pagina Sviluppo database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework Core. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle possibili azioni per risolvere il problema. Questa pagina è abilitata solo nell'ambiente di sviluppo. Il codice seguente aggiunge il filtro eccezioni della pagina Sviluppo database:

var builder = WebApplication.CreateBuilder(args);

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

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono eventuali eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro. Per altre informazioni, vedere Filtri in ASP.NET Core.

I filtri eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione delle eccezioni predefinito, UseExceptionHandler. È consigliabile usare UseExceptionHandler, a meno che non sia necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Dettagli del problema

I dettagli del problema non sono l'unico formato di risposta per descrivere un errore dell'API HTTP, ma vengono comunemente usati per segnalare errori per le API HTTP.

Il servizio dettagli problema implementa l'interfaccia , che supporta la IProblemDetailsService creazione dei dettagli del problema in ASP.NET Core. Il AddProblemDetails(IServiceCollection) metodo di estensione in IServiceCollection registra l'implementazione predefinita IProblemDetailsService .

In ASP.NET app Core, il middleware seguente genera risposte HTTP con dettagli del problema quando AddProblemDetails viene chiamato, tranne quando l'intestazione HTTP della Accept richiesta non include uno dei tipi di contenuto supportati dal registrato IProblemDetailsWriter (impostazione predefinita: application/json):

Il codice seguente configura l'app per generare una risposta ai dettagli del problema per tutte le risposte di errore del client HTTP e del server che non hanno ancora contenuto del corpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

Nella sezione successiva viene illustrato come personalizzare il corpo della risposta ai dettagli del problema.

Personalizzare i dettagli del problema

La creazione automatica di un ProblemDetails oggetto può essere personalizzata usando una delle opzioni seguenti:

  1. Utilizzare ProblemDetailsOptions.CustomizeProblemDetails.
  2. Usare un oggetto personalizzato IProblemDetailsWriter
  3. Chiamare in IProblemDetailsService un middleware

CustomizeProblemDetails operazione

I dettagli del problema generati possono essere personalizzati usando CustomizeProblemDetailse le personalizzazioni vengono applicate a tutti i dettagli del problema generati automaticamente.

Il codice seguente usa ProblemDetailsOptions per impostare 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();

Ad esempio, un HTTP Status 400 Bad Request risultato dell'endpoint produce il corpo della risposta ai dettagli del problema seguente:

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

IProblemDetailsWriter personalizzato

È possibile creare un'implementazione IProblemDetailsWriter per le personalizzazioni avanzate.

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

Nota: quando si usa un oggetto personalizzato IProblemDetailsWriter, l'oggetto personalizzato IProblemDetailsWriter deve essere registrato prima di chiamare AddRazorPages, AddControllersAddControllersWithViews, o 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();

Dettagli del problema dal middleware

Un approccio alternativo all'uso ProblemDetailsOptions con CustomizeProblemDetails consiste nell'impostare il ProblemDetails middleware. Una risposta ai dettagli del problema può essere scritta chiamando 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();

Nel codice precedente, gli endpoint /divide API minimi e /squareroot restituiscono la risposta del problema personalizzata prevista all'input dell'errore.

Gli endpoint del controller API restituiscono la risposta del problema predefinita all'input dell'errore, non la risposta personalizzata al problema. Viene restituita la risposta del problema predefinita perché il controller API ha scritto nel flusso di risposta, Dettagli del problema per i codici di stato degli errori, prima IProblemDetailsService.WriteAsync di chiamare e la risposta non viene scritta di nuovo.

Di seguito ValuesController viene restituito BadRequestResult, che scrive nel flusso di risposta e pertanto impedisce la restituzione della risposta personalizzata al problema.

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

}

Di seguito Values3Controller viene restituito ControllerBase.Problem il risultato del problema personalizzato previsto:

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

}

Generare un payload ProblemDetails per le eccezioni

Si consideri l'app seguente:

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

In ambienti non di sviluppo, quando si verifica un'eccezione, di seguito è riportata una risposta Standard ProblemDetails restituita al client:

{
"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"
}

Per la maggior parte delle app, il codice precedente è tutto ciò che è necessario per le eccezioni. Tuttavia, la sezione seguente illustra come ottenere risposte più dettagliate ai problemi.

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore e la scrittura di una risposta ai dettagli del problema con 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();

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Un approccio alternativo alla generazione dei dettagli del problema consiste nell'usare il pacchetto NuGet di terze parti Hellang.Middleware.ProblemDetails che può essere usato per eseguire il mapping delle eccezioni e degli errori del client ai dettagli del problema.

Risorse aggiuntive

Tom Dykstra

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere anche Gestire gli errori nelle API Web basate su controller di base ASP.NET e Gestire gli errori nelle API minime.

Pagina delle eccezioni per gli sviluppatori

Nella pagina Eccezioni sviluppatore vengono visualizzate informazioni dettagliate sulle eccezioni di richiesta non gestite. ASP.NET App core abilitano la pagina delle eccezioni per sviluppatori per impostazione predefinita quando entrambe:

  • Esecuzione nell'ambiente di sviluppo.
  • App creata con i modelli correnti, ovvero usando WebApplication.CreateBuilder. Le app create usando WebHost.CreateDefaultBuilder devono abilitare la pagina delle eccezioni per sviluppatori chiamando app.UseDeveloperExceptionPage in Configure.

La pagina delle eccezioni dello sviluppatore viene eseguita nelle prime fasi della pipeline middleware, in modo che possa rilevare eccezioni non gestite generate nel middleware che segue.

Le informazioni dettagliate sulle eccezioni non devono essere visualizzate pubblicamente quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Delle eccezioni per sviluppatori può includere le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie, se presenti
  • Intestazioni

La pagina Delle eccezioni per gli sviluppatori non fornisce alcuna informazione. Usare Registrazione per informazioni complete sull'errore.

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, chiamare UseExceptionHandler. Questo middleware per la gestione delle eccezioni:

  • Rileva e registra eccezioni non gestite.
  • Esegue nuovamente la richiesta in una pipeline alternativa usando il percorso indicato. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta usando il /Error percorso.

Avviso

Se la pipeline alternativa genera un'eccezione propria, il middleware di gestione delle eccezioni genera nuovamente l'eccezione originale.

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • Per l'overload UseExceptionHandler(IApplicationBuilder, String) usato nei modelli, viene modificato solo il percorso della richiesta e i dati della route vengono cancellati. I dati delle richieste, ad esempio intestazioni, metodo ed elementi, vengono riutilizzati così come sono.
  • I servizi con ambito rimangono invariati.

Nell'esempio seguente viene UseExceptionHandler aggiunto il middleware di gestione delle eccezioni in ambienti non di sviluppo:

var app = builder.Build();

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un Error metodo di azione e una visualizzazione Errore per il Home controller.

Il middleware di gestione delle eccezioni esegue nuovamente la richiesta usando il metodo HTTP originale . Se un endpoint del gestore errori è limitato a un set specifico di metodi HTTP, viene eseguito solo per tali metodi HTTP. Ad esempio, un'azione del controller MVC che usa l'attributo [HttpGet] viene eseguita solo per le richieste GET. Per assicurarsi che tutte le richieste raggiungano la pagina di gestione degli errori personalizzata, non limitarle a un set specifico di metodi HTTP.

Per gestire le eccezioni in modo diverso in base al metodo HTTP originale:

  • Per Razor Pages creare più metodi del gestore. Ad esempio, usare OnGet per gestire le eccezioni GET e usare OnPost per gestire le eccezioni POST.
  • Per MVC, applicare attributi verbi HTTP a più azioni. Ad esempio, usare [HttpGet] per gestire le eccezioni GET e usare [HttpPost] per gestire le eccezioni POST.

Per consentire agli utenti non autenticati di visualizzare la pagina di gestione degli errori personalizzata, assicurarsi che supporti l'accesso anonimo.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un gestore errori. Nell'esempio seguente viene IExceptionHandlerPathFeature usato per ottenere altre informazioni sull'eccezione generata:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Il codice seguente usa un'espressione lambda per la gestione delle eccezioni:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

IExceptionHandler

IExceptionHandler è un'interfaccia che offre allo sviluppatore un callback per la gestione delle eccezioni note in una posizione centrale.

IExceptionHandler le implementazioni vengono registrate chiamando IServiceCollection.AddExceptionHandler<T>. La durata di un'istanza IExceptionHandler è singleton. È possibile aggiungere più implementazioni e vengono chiamate nell'ordine registrato.

Se un gestore eccezioni gestisce una richiesta, può tornare true a interrompere l'elaborazione. Se un'eccezione non viene gestita da alcun gestore eccezioni, il controllo esegue il fallback al comportamento predefinito e alle opzioni del middleware. Metriche e log diversi vengono generati per le eccezioni gestite e non gestite.

L'esempio seguente illustra un'implementazione 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);
        }
    }
}

L'esempio seguente illustra come registrare un'implementazione IExceptionHandler per l'inserimento delle dipendenze:

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

Quando il codice precedente viene eseguito nell'ambiente di sviluppo:

  • Viene CustomExceptionHandler chiamato prima di tutto per gestire un'eccezione.
  • Dopo la registrazione dell'eccezione, il TryHandleException metodo restituisce false, in modo che venga visualizzata la pagina delle eccezioni per gli sviluppatori.

In altri ambienti:

  • Viene CustomExceptionHandler chiamato prima di tutto per gestire un'eccezione.
  • Dopo la registrazione dell'eccezione, il TryHandleException metodo restituisce false, in modo che venga visualizzata la /Error pagina .

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non fornisce una tabella codici di stato per i codici di stato degli errori HTTP, ad esempio 404 - Non trovato. Quando l'app imposta un codice di stato di errore HTTP 400-599 che non ha un corpo, restituisce il codice di stato e un corpo della risposta vuoto. Per abilitare i gestori predefiniti solo testo per i codici di stato di errore comuni, chiamare UseStatusCodePages in Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste. Ad esempio, chiamare UseStatusCodePages prima del middleware dei file statici e del middleware degli endpoint.

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Quando UseStatusCodePages viene chiamato, il browser restituisce la risposta seguente:

Status Code: 404; Not Found

UseStatusCodePages non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile per gli utenti.

Nota

Il middleware delle tabelle codici di stato non intercetta le eccezioni. Per fornire una pagina di gestione degli errori personalizzata, usare la pagina del gestore eccezioni.

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

Nel codice {0} precedente è un segnaposto per il codice di errore.

UseStatusCodePages con una stringa di formato non viene in genere usata nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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 con un'espressione lambda non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePagesWithRedirects

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client all'endpoint di gestione degli errori fornito nel modello di URL. L'endpoint di gestione degli errori visualizza in genere informazioni sugli errori e restituisce HTTP 200.
var app = builder.Build();

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

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

Il modello di URL può includere un {0} segnaposto per il codice di stato, come illustrato nel codice precedente. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Quando si specifica un endpoint nell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
  • Non modifica il codice di stato prima o dopo l'esecuzione di nuovo della pipeline.

La nuova esecuzione della pipeline può modificare il codice di stato della risposta, perché la nuova pipeline ha il controllo completo del codice di stato. Se la nuova pipeline non modifica il codice di stato, il codice di stato originale verrà inviato al client.

var app = builder.Build();

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

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

Se viene specificato un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

Il modello di URL deve iniziare con / e può includere un segnaposto {0} per il codice di stato. Per passare il codice di stato come parametro di stringa di query, passare un secondo argomento in UseStatusCodePagesWithReExecute. Ad esempio:

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

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

        }
    }
}

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • I servizi con ambito rimangono invariati.

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages].

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice nelle pagine di gestione delle eccezioni può anche generare eccezioni. Le pagine di errore di produzione devono essere testate accuratamente e prestare particolare attenzione per evitare di generare eccezioni proprie.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni in un'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se il server rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una 500 - Internal Server Error risposta senza un corpo della risposta. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste non gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il filtro AddDatabaseDeveloperPageExceptionFilter eccezioni della pagina Sviluppo database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework Core. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle possibili azioni per risolvere il problema. Questa pagina è abilitata solo nell'ambiente di sviluppo. Il codice seguente aggiunge il filtro eccezioni della pagina Sviluppo database:

var builder = WebApplication.CreateBuilder(args);

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

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono eventuali eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro. Per altre informazioni, vedere Filtri in ASP.NET Core.

I filtri eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione delle eccezioni predefinito, UseExceptionHandler. È consigliabile usare UseExceptionHandler, a meno che non sia necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Dettagli del problema

I dettagli del problema non sono l'unico formato di risposta per descrivere un errore dell'API HTTP, ma vengono comunemente usati per segnalare errori per le API HTTP.

Il servizio dettagli problema implementa l'interfaccia , che supporta la IProblemDetailsService creazione dei dettagli del problema in ASP.NET Core. Il AddProblemDetails(IServiceCollection) metodo di estensione in IServiceCollection registra l'implementazione predefinita IProblemDetailsService .

In ASP.NET app Core, il middleware seguente genera risposte HTTP con dettagli del problema quando AddProblemDetails viene chiamato, tranne quando l'intestazione HTTP della Accept richiesta non include uno dei tipi di contenuto supportati dal registrato IProblemDetailsWriter (impostazione predefinita: application/json):

Il codice seguente configura l'app per generare una risposta ai dettagli del problema per tutte le risposte di errore del client HTTP e del server che non hanno ancora un contenuto del corpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

Nella sezione successiva viene illustrato come personalizzare il corpo della risposta ai dettagli del problema.

Personalizzare i dettagli del problema

La creazione automatica di un ProblemDetails oggetto può essere personalizzata usando una delle opzioni seguenti:

  1. Utilizzare ProblemDetailsOptions.CustomizeProblemDetails.
  2. Usare un oggetto personalizzato IProblemDetailsWriter
  3. Chiamare in IProblemDetailsService un middleware

CustomizeProblemDetails operazione

I dettagli del problema generati possono essere personalizzati usando CustomizeProblemDetailse le personalizzazioni vengono applicate a tutti i dettagli del problema generati automaticamente.

Il codice seguente usa ProblemDetailsOptions per impostare 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();

Ad esempio, un HTTP Status 400 Bad Request risultato dell'endpoint produce il corpo della risposta ai dettagli del problema seguente:

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

IProblemDetailsWriter personalizzato

È possibile creare un'implementazione IProblemDetailsWriter per le personalizzazioni avanzate.

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

Nota: quando si usa un oggetto personalizzato IProblemDetailsWriter, l'oggetto personalizzato IProblemDetailsWriter deve essere registrato prima di chiamare AddRazorPages, AddControllersAddControllersWithViews, o 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();

Dettagli del problema dal middleware

Un approccio alternativo all'uso ProblemDetailsOptions con CustomizeProblemDetails consiste nell'impostare il ProblemDetails middleware. Una risposta ai dettagli del problema può essere scritta chiamando 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();

Nel codice precedente, gli endpoint /divide API minimi e /squareroot restituiscono la risposta del problema personalizzata prevista all'input dell'errore.

Gli endpoint del controller API restituiscono la risposta del problema predefinita all'input dell'errore, non la risposta personalizzata al problema. Viene restituita la risposta del problema predefinita perché il controller API ha scritto nel flusso di risposta, Dettagli del problema per i codici di stato degli errori, prima IProblemDetailsService.WriteAsync di chiamare e la risposta non viene scritta di nuovo.

Di seguito ValuesController viene restituito BadRequestResult, che scrive nel flusso di risposta e pertanto impedisce la restituzione della risposta personalizzata al problema.

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

}

Di seguito Values3Controller viene restituito ControllerBase.Problem il risultato del problema personalizzato previsto:

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

}

Generare un payload ProblemDetails per le eccezioni

Si consideri l'app seguente:

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

In ambienti non di sviluppo, quando si verifica un'eccezione, di seguito è riportata una risposta Standard ProblemDetails restituita al client:

{
"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"
}

Per la maggior parte delle app, il codice precedente è tutto ciò che è necessario per le eccezioni. Tuttavia, la sezione seguente illustra come ottenere risposte più dettagliate ai problemi.

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore e la scrittura di una risposta ai dettagli del problema con 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();

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Un approccio alternativo alla generazione dei dettagli del problema consiste nell'usare il pacchetto NuGet di terze parti Hellang.Middleware.ProblemDetails che può essere usato per eseguire il mapping delle eccezioni e degli errori del client ai dettagli del problema.

Risorse aggiuntive

Tom Dykstra

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere anche Gestire gli errori nelle API Web basate su controller di base ASP.NET e Gestire gli errori nelle API minime.

Pagina delle eccezioni per gli sviluppatori

Nella pagina Eccezioni sviluppatore vengono visualizzate informazioni dettagliate sulle eccezioni di richiesta non gestite. ASP.NET App core abilitano la pagina delle eccezioni per sviluppatori per impostazione predefinita quando entrambe:

  • Esecuzione nell'ambiente di sviluppo.
  • App creata con i modelli correnti, ovvero usando WebApplication.CreateBuilder. Le app create usando WebHost.CreateDefaultBuilder devono abilitare la pagina delle eccezioni per sviluppatori chiamando app.UseDeveloperExceptionPage in Configure.

La pagina delle eccezioni dello sviluppatore viene eseguita nelle prime fasi della pipeline middleware, in modo che possa rilevare eccezioni non gestite generate nel middleware che segue.

Le informazioni dettagliate sulle eccezioni non devono essere visualizzate pubblicamente quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Delle eccezioni per sviluppatori può includere le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie, se presenti
  • Intestazioni

La pagina Delle eccezioni per gli sviluppatori non fornisce alcuna informazione. Usare Registrazione per informazioni complete sull'errore.

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, chiamare UseExceptionHandler. Questo middleware per la gestione delle eccezioni:

  • Rileva e registra eccezioni non gestite.
  • Esegue nuovamente la richiesta in una pipeline alternativa usando il percorso indicato. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta usando il /Error percorso.

Avviso

Se la pipeline alternativa genera un'eccezione propria, il middleware di gestione delle eccezioni genera nuovamente l'eccezione originale.

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • Per l'overload UseExceptionHandler(IApplicationBuilder, String) usato nei modelli, viene modificato solo il percorso della richiesta e i dati della route vengono cancellati. I dati delle richieste, ad esempio intestazioni, metodo ed elementi, vengono riutilizzati così come sono.
  • I servizi con ambito rimangono invariati.

Nell'esempio seguente viene UseExceptionHandler aggiunto il middleware di gestione delle eccezioni in ambienti non di sviluppo:

var app = builder.Build();

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un Error metodo di azione e una visualizzazione Errore per il Home controller.

Il middleware di gestione delle eccezioni esegue nuovamente la richiesta usando il metodo HTTP originale . Se un endpoint del gestore errori è limitato a un set specifico di metodi HTTP, viene eseguito solo per tali metodi HTTP. Ad esempio, un'azione del controller MVC che usa l'attributo [HttpGet] viene eseguita solo per le richieste GET. Per assicurarsi che tutte le richieste raggiungano la pagina di gestione degli errori personalizzata, non limitarle a un set specifico di metodi HTTP.

Per gestire le eccezioni in modo diverso in base al metodo HTTP originale:

  • Per Razor Pages creare più metodi del gestore. Ad esempio, usare OnGet per gestire le eccezioni GET e usare OnPost per gestire le eccezioni POST.
  • Per MVC, applicare attributi verbi HTTP a più azioni. Ad esempio, usare [HttpGet] per gestire le eccezioni GET e usare [HttpPost] per gestire le eccezioni POST.

Per consentire agli utenti non autenticati di visualizzare la pagina di gestione degli errori personalizzata, assicurarsi che supporti l'accesso anonimo.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un gestore errori. Nell'esempio seguente viene IExceptionHandlerPathFeature usato per ottenere altre informazioni sull'eccezione generata:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Il codice seguente usa un'espressione lambda per la gestione delle eccezioni:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non fornisce una tabella codici di stato per i codici di stato degli errori HTTP, ad esempio 404 - Non trovato. Quando l'app imposta un codice di stato di errore HTTP 400-599 che non ha un corpo, restituisce il codice di stato e un corpo della risposta vuoto. Per abilitare i gestori predefiniti solo testo per i codici di stato di errore comuni, chiamare UseStatusCodePages in Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste. Ad esempio, chiamare UseStatusCodePages prima del middleware dei file statici e del middleware degli endpoint.

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Quando UseStatusCodePages viene chiamato, il browser restituisce la risposta seguente:

Status Code: 404; Not Found

UseStatusCodePages non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile per gli utenti.

Nota

Il middleware delle tabelle codici di stato non intercetta le eccezioni. Per fornire una pagina di gestione degli errori personalizzata, usare la pagina del gestore eccezioni.

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

Nel codice {0} precedente è un segnaposto per il codice di errore.

UseStatusCodePages con una stringa di formato non viene in genere usata nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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 con un'espressione lambda non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePagesWithRedirects

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client all'endpoint di gestione degli errori fornito nel modello di URL. L'endpoint di gestione degli errori visualizza in genere informazioni sugli errori e restituisce HTTP 200.
var app = builder.Build();

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

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

Il modello di URL può includere un {0} segnaposto per il codice di stato, come illustrato nel codice precedente. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Quando si specifica un endpoint nell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
  • Non modifica il codice di stato prima o dopo l'esecuzione di nuovo della pipeline.

La nuova esecuzione della pipeline può modificare il codice di stato della risposta, perché la nuova pipeline ha il controllo completo del codice di stato. Se la nuova pipeline non modifica il codice di stato, il codice di stato originale verrà inviato al client.

var app = builder.Build();

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

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

Se viene specificato un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

Il modello di URL deve iniziare con / e può includere un segnaposto {0} per il codice di stato. Per passare il codice di stato come parametro di stringa di query, passare un secondo argomento in UseStatusCodePagesWithReExecute. Ad esempio:

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

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

        }
    }
}

Poiché questo middleware può eseguire nuovamente la pipeline di richiesta:

  • I middleware devono gestire la reentrancy con la stessa richiesta. Questo significa in genere pulire il proprio stato dopo aver chiamato _next o memorizzare nella cache l'elaborazione HttpContext su per evitare di ripeterlo. Quando si usa il corpo della richiesta, ciò significa memorizzare nel buffer o memorizzare nella cache i risultati, ad esempio il lettore di moduli.
  • I servizi con ambito rimangono invariati.

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages].

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice nelle pagine di gestione delle eccezioni può anche generare eccezioni. Le pagine di errore di produzione devono essere testate accuratamente e prestare particolare attenzione per evitare di generare eccezioni proprie.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni in un'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se il server rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una 500 - Internal Server Error risposta senza un corpo della risposta. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste non gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il filtro AddDatabaseDeveloperPageExceptionFilter eccezioni della pagina Sviluppo database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework Core. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle possibili azioni per risolvere il problema. Questa pagina è abilitata solo nell'ambiente di sviluppo. Il codice seguente aggiunge il filtro eccezioni della pagina Sviluppo database:

var builder = WebApplication.CreateBuilder(args);

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

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono eventuali eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro. Per altre informazioni, vedere Filtri in ASP.NET Core.

I filtri eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione delle eccezioni predefinito, UseExceptionHandler. È consigliabile usare UseExceptionHandler, a meno che non sia necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Dettagli del problema

I dettagli del problema non sono l'unico formato di risposta per descrivere un errore dell'API HTTP, ma vengono comunemente usati per segnalare errori per le API HTTP.

Il servizio dettagli problema implementa l'interfaccia , che supporta la IProblemDetailsService creazione dei dettagli del problema in ASP.NET Core. Il AddProblemDetails(IServiceCollection) metodo di estensione in IServiceCollection registra l'implementazione predefinita IProblemDetailsService .

In ASP.NET app Core, il middleware seguente genera risposte HTTP con dettagli del problema quando AddProblemDetails viene chiamato, tranne quando l'intestazione HTTP della Accept richiesta non include uno dei tipi di contenuto supportati dal registrato IProblemDetailsWriter (impostazione predefinita: application/json):

Il codice seguente configura l'app per generare una risposta ai dettagli del problema per tutte le risposte di errore del client HTTP e del server che non hanno ancora un contenuto del corpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

Nella sezione successiva viene illustrato come personalizzare il corpo della risposta ai dettagli del problema.

Personalizzare i dettagli del problema

La creazione automatica di un ProblemDetails oggetto può essere personalizzata usando una delle opzioni seguenti:

  1. Utilizzare ProblemDetailsOptions.CustomizeProblemDetails.
  2. Usare un oggetto personalizzato IProblemDetailsWriter
  3. Chiamare in IProblemDetailsService un middleware

CustomizeProblemDetails operazione

I dettagli del problema generati possono essere personalizzati usando CustomizeProblemDetailse le personalizzazioni vengono applicate a tutti i dettagli del problema generati automaticamente.

Il codice seguente usa ProblemDetailsOptions per impostare 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();

Ad esempio, un HTTP Status 400 Bad Request risultato dell'endpoint produce il corpo della risposta ai dettagli del problema seguente:

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

IProblemDetailsWriter personalizzato

È possibile creare un'implementazione IProblemDetailsWriter per le personalizzazioni avanzate.

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

Nota: quando si usa un oggetto personalizzato IProblemDetailsWriter, l'oggetto personalizzato IProblemDetailsWriter deve essere registrato prima di chiamare AddRazorPages, AddControllersAddControllersWithViews, o 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();

Dettagli del problema dal middleware

Un approccio alternativo all'uso ProblemDetailsOptions con CustomizeProblemDetails consiste nell'impostare il ProblemDetails middleware. Una risposta ai dettagli del problema può essere scritta chiamando 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();

Nel codice precedente, gli endpoint /divide API minimi e /squareroot restituiscono la risposta del problema personalizzata prevista all'input dell'errore.

Gli endpoint del controller API restituiscono la risposta del problema predefinita all'input dell'errore, non la risposta personalizzata al problema. Viene restituita la risposta del problema predefinita perché il controller API ha scritto nel flusso di risposta, Dettagli del problema per i codici di stato degli errori, prima IProblemDetailsService.WriteAsync di chiamare e la risposta non viene scritta di nuovo.

Di seguito ValuesController viene restituito BadRequestResult, che scrive nel flusso di risposta e pertanto impedisce la restituzione della risposta personalizzata al problema.

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

}

Di seguito Values3Controller viene restituito ControllerBase.Problem il risultato del problema personalizzato previsto:

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

}

Generare un payload ProblemDetails per le eccezioni

Si consideri l'app seguente:

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

In ambienti non di sviluppo, quando si verifica un'eccezione, di seguito è riportata una risposta Standard ProblemDetails restituita al client:

{
"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"
}

Per la maggior parte delle app, il codice precedente è tutto ciò che è necessario per le eccezioni. Tuttavia, la sezione seguente illustra come ottenere risposte più dettagliate ai problemi.

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore e la scrittura di una risposta ai dettagli del problema con 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();

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Un approccio alternativo alla generazione dei dettagli del problema consiste nell'usare il pacchetto NuGet di terze parti Hellang.Middleware.ProblemDetails che può essere usato per eseguire il mapping delle eccezioni e degli errori del client ai dettagli del problema.

Risorse aggiuntive

Tom Dykstra

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere Gestire gli errori nelle API Web basate su controller di base ASP.NET per le API Web.

Pagina delle eccezioni per gli sviluppatori

Nella pagina Eccezioni sviluppatore vengono visualizzate informazioni dettagliate sulle eccezioni di richiesta non gestite. ASP.NET App core abilitano la pagina delle eccezioni per sviluppatori per impostazione predefinita quando entrambe:

  • Esecuzione nell'ambiente di sviluppo.
  • App creata con i modelli correnti, ovvero usando WebApplication.CreateBuilder. Le app create usando WebHost.CreateDefaultBuilder devono abilitare la pagina delle eccezioni per sviluppatori chiamando app.UseDeveloperExceptionPage in Configure.

La pagina delle eccezioni dello sviluppatore viene eseguita nelle prime fasi della pipeline middleware, in modo che possa rilevare eccezioni non gestite generate nel middleware che segue.

Le informazioni dettagliate sulle eccezioni non devono essere visualizzate pubblicamente quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Delle eccezioni per sviluppatori può includere le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie, se presenti
  • Intestazioni

La pagina Delle eccezioni per gli sviluppatori non fornisce alcuna informazione. Usare Registrazione per informazioni complete sull'errore.

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, chiamare UseExceptionHandler. Questo middleware per la gestione delle eccezioni:

  • Rileva e registra eccezioni non gestite.
  • Esegue nuovamente la richiesta in una pipeline alternativa usando il percorso indicato. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta usando il /Error percorso.

Avviso

Se la pipeline alternativa genera un'eccezione propria, il middleware di gestione delle eccezioni genera nuovamente l'eccezione originale.

Nell'esempio seguente viene UseExceptionHandler aggiunto il middleware di gestione delle eccezioni in ambienti non di sviluppo:

var app = builder.Build();

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un Error metodo di azione e una visualizzazione Errore per il Home controller.

Il middleware di gestione delle eccezioni esegue nuovamente la richiesta usando il metodo HTTP originale . Se un endpoint del gestore errori è limitato a un set specifico di metodi HTTP, viene eseguito solo per tali metodi HTTP. Ad esempio, un'azione del controller MVC che usa l'attributo [HttpGet] viene eseguita solo per le richieste GET. Per assicurarsi che tutte le richieste raggiungano la pagina di gestione degli errori personalizzata, non limitarle a un set specifico di metodi HTTP.

Per gestire le eccezioni in modo diverso in base al metodo HTTP originale:

  • Per Razor Pages creare più metodi del gestore. Ad esempio, usare OnGet per gestire le eccezioni GET e usare OnPost per gestire le eccezioni POST.
  • Per MVC, applicare attributi verbi HTTP a più azioni. Ad esempio, usare [HttpGet] per gestire le eccezioni GET e usare [HttpPost] per gestire le eccezioni POST.

Per consentire agli utenti non autenticati di visualizzare la pagina di gestione degli errori personalizzata, assicurarsi che supporti l'accesso anonimo.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un gestore errori. Nell'esempio seguente viene IExceptionHandlerPathFeature usato per ottenere altre informazioni sull'eccezione generata:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Il codice seguente usa un'espressione lambda per la gestione delle eccezioni:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non fornisce una tabella codici di stato per i codici di stato degli errori HTTP, ad esempio 404 - Non trovato. Quando l'app imposta un codice di stato di errore HTTP 400-599 che non ha un corpo, restituisce il codice di stato e un corpo della risposta vuoto. Per abilitare i gestori predefiniti solo testo per i codici di stato di errore comuni, chiamare UseStatusCodePages in Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste. Ad esempio, chiamare UseStatusCodePages prima del middleware dei file statici e del middleware degli endpoint.

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Quando UseStatusCodePages viene chiamato, il browser restituisce la risposta seguente:

Status Code: 404; Not Found

UseStatusCodePages non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile per gli utenti.

Nota

Il middleware delle tabelle codici di stato non intercetta le eccezioni. Per fornire una pagina di gestione degli errori personalizzata, usare la pagina del gestore eccezioni.

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

Nel codice {0} precedente è un segnaposto per il codice di errore.

UseStatusCodePages con una stringa di formato non viene in genere usata nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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 con un'espressione lambda non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

UseStatusCodePagesWithRedirects

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client all'endpoint di gestione degli errori fornito nel modello di URL. L'endpoint di gestione degli errori visualizza in genere informazioni sugli errori e restituisce HTTP 200.
var app = builder.Build();

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

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

Il modello di URL può includere un {0} segnaposto per il codice di stato, come illustrato nel codice precedente. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Quando si specifica un endpoint nell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Restituisce il codice di stato originale al client.
  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
var app = builder.Build();

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

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

Se viene specificato un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

Il modello di URL deve iniziare con / e può includere un segnaposto {0} per il codice di stato. Per passare il codice di stato come parametro di stringa di query, passare un secondo argomento in UseStatusCodePagesWithReExecute. Ad esempio:

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

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages].

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice nelle pagine di gestione delle eccezioni può anche generare eccezioni. Le pagine di errore di produzione devono essere testate accuratamente e prestare particolare attenzione per evitare di generare eccezioni proprie.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni in un'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se il server rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una 500 - Internal Server Error risposta senza un corpo della risposta. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste non gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il filtro AddDatabaseDeveloperPageExceptionFilter eccezioni della pagina Sviluppo database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework Core. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle possibili azioni per risolvere il problema. Questa pagina è abilitata solo nell'ambiente di sviluppo. Il codice seguente aggiunge il filtro eccezioni della pagina Sviluppo database:

var builder = WebApplication.CreateBuilder(args);

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

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono eventuali eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro. Per altre informazioni, vedere Filtri in ASP.NET Core.

I filtri eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione delle eccezioni predefinito, UseExceptionHandler. È consigliabile usare UseExceptionHandler, a meno che non sia necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Risorse aggiuntive

Di Kirk Larkin, Tom Dykstra e Steve Smith

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere Gestire gli errori nelle API Web basate su controller di base ASP.NET per le API Web.

Visualizzare o scaricare il codice di esempio. (Come scaricare). La scheda di rete negli strumenti di sviluppo del browser F12 è utile durante il test dell'app di esempio.

Pagina delle eccezioni per gli sviluppatori

Nella pagina Eccezioni sviluppatore vengono visualizzate informazioni dettagliate sulle eccezioni di richiesta non gestite. I modelli ASP.NET Core generano il codice seguente:

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

Il codice evidenziato precedente abilita la pagina delle eccezioni per sviluppatori quando l'app è in esecuzione nell'ambiente di sviluppo.

I modelli vengono inseriti UseDeveloperExceptionPage nelle prime fasi della pipeline middleware in modo che possano intercettare eccezioni non gestite generate nel middleware che segue.

Il codice precedente abilita la pagina eccezioni per sviluppatori solo quando l'app viene eseguita nell'ambiente di sviluppo. Le informazioni dettagliate sulle eccezioni non devono essere visualizzate pubblicamente quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Delle eccezioni per sviluppatori può includere le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie se presenti
  • Intestazioni

La pagina Delle eccezioni per gli sviluppatori non fornisce alcuna informazione. Usare Registrazione per informazioni complete sull'errore.

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, chiamare UseExceptionHandler. Questo middleware per la gestione delle eccezioni:

  • Rileva e registra eccezioni non gestite.
  • Esegue nuovamente la richiesta in una pipeline alternativa usando il percorso indicato. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta usando il /Error percorso.

Avviso

Se la pipeline alternativa genera un'eccezione propria, il middleware di gestione delle eccezioni genera nuovamente l'eccezione originale.

Nell'esempio seguente viene UseExceptionHandler aggiunto il middleware di gestione delle eccezioni in ambienti non di sviluppo:

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un Error metodo di azione e una visualizzazione Errore per il Home controller.

Il middleware di gestione delle eccezioni esegue nuovamente la richiesta usando il metodo HTTP originale . Se un endpoint del gestore errori è limitato a un set specifico di metodi HTTP, viene eseguito solo per tali metodi HTTP. Ad esempio, un'azione del controller MVC che usa l'attributo [HttpGet] viene eseguita solo per le richieste GET. Per assicurarsi che tutte le richieste raggiungano la pagina di gestione degli errori personalizzata, non limitarle a un set specifico di metodi HTTP.

Per gestire le eccezioni in modo diverso in base al metodo HTTP originale:

  • Per Razor Pages creare più metodi del gestore. Ad esempio, usare OnGet per gestire le eccezioni GET e usare OnPost per gestire le eccezioni POST.
  • Per MVC, applicare attributi verbi HTTP a più azioni. Ad esempio, usare [HttpGet] per gestire le eccezioni GET e usare [HttpPost] per gestire le eccezioni POST.

Per consentire agli utenti non autenticati di visualizzare la pagina di gestione degli errori personalizzata, assicurarsi che supporti l'accesso anonimo.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un gestore errori. Il codice seguente aggiunge ExceptionMessage all'impostazione predefinita Pages/Error.cshtml.cs generata dai modelli 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";
        }
    }
}

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Per testare l'eccezione nell'app di esempio:

  • Impostare l'ambiente sull'ambiente di produzione.
  • Rimuovere i commenti da webBuilder.UseStartup<Startup>(); in Program.cs.
  • Selezionare Attiva un'eccezione nella home pagina.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Il codice seguente usa un'espressione lambda per la gestione delle eccezioni:

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

Avviso

Non fornire informazioni sugli errori sensibili da IExceptionHandlerFeature o IExceptionHandlerPathFeature ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Per testare l'espressione lambda di gestione delle eccezioni nell'app di esempio:

  • Impostare l'ambiente sull'ambiente di produzione.
  • Rimuovere i commenti da webBuilder.UseStartup<StartupLambda>(); in Program.cs.
  • Selezionare Attiva un'eccezione nella home pagina.

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non fornisce una tabella codici di stato per i codici di stato degli errori HTTP, ad esempio 404 - Non trovato. Quando l'app imposta un codice di stato di errore HTTP 400-599 che non ha un corpo, restituisce il codice di stato e un corpo della risposta vuoto. Per fornire tabelle codici di stato, usare il middleware delle tabelle codici di stato. Per abilitare i gestori di solo testo predefiniti per i codici di stato di errore comuni, chiamare UseStatusCodePages nel metodo 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();
    });
}

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste. Ad esempio, chiamare UseStatusCodePages prima del middleware dei file statici e del middleware degli endpoint.

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Ad esempio, passando a Home/Privacy2. Quando UseStatusCodePages viene chiamato, il browser restituisce:

Status Code: 404; Not Found

UseStatusCodePages non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile per gli utenti.

Per eseguire il test UseStatusCodePages nell'app di esempio:

  • Impostare l'ambiente sull'ambiente di produzione.
  • Rimuovere i commenti da webBuilder.UseStartup<StartupUseStatusCodePages>(); in Program.cs.
  • Selezionare i collegamenti nella home pagina nella home pagina.

Nota

Il middleware delle tabelle codici di stato non intercetta le eccezioni. Per fornire una pagina di gestione degli errori personalizzata, usare la pagina del gestore eccezioni.

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

Nel codice {0} precedente è un segnaposto per il codice di errore.

UseStatusCodePages con una stringa di formato non viene in genere usata nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

Per eseguire il test UseStatusCodePages nell'app di esempio, rimuovere i commenti da webBuilder.UseStartup<StartupFormat>(); in Program.cs.

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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 con un'espressione lambda non viene in genere usato nell'ambiente di produzione perché restituisce un messaggio che non è utile agli utenti.

Per eseguire il test UseStatusCodePages nell'app di esempio, rimuovere i commenti da webBuilder.UseStartup<StartupStatusLambda>(); in Program.cs.

UseStatusCodePagesWithRedirects

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client all'endpoint di gestione degli errori fornito nel modello di URL. L'endpoint di gestione degli errori visualizza in genere informazioni sugli errori e restituisce 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();
    });
}

Il modello di URL può includere un {0} segnaposto per il codice di stato, come illustrato nel codice precedente. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Quando si specifica un endpoint nell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint. Per un Razor esempio di Pages, vedere Pages/MyStatusCode.cshtml nell'app di esempio.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

Per eseguire il test UseStatusCodePages nell'app di esempio, rimuovere i commenti da webBuilder.UseStartup<StartupSCredirect>(); in Program.cs.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Restituisce il codice di stato originale al client.
  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
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();
    });
}

Se viene specificato un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint. Assicurarsi UseStatusCodePagesWithReExecute di essere inseriti prima UseRouting in modo che la richiesta possa essere reindirizzata alla pagina di stato. Per un Razor esempio di Pages, vedere Pages/MyStatusCode2.cshtml nell'app di esempio.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

I modelli di URL e stringa di query possono includere un segnaposto {0} per il codice di stato. Il modello di URL deve iniziare con /.

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

Per un Razor esempio di Pages, vedere Pages/MyStatusCode2.cshtml nell'app di esempio.

Per eseguire il test UseStatusCodePages nell'app di esempio, rimuovere i commenti da webBuilder.UseStartup<StartupSCreX>(); in Program.cs.

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages].

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice nelle pagine di gestione delle eccezioni può anche generare eccezioni. Le pagine di errore di produzione devono essere testate accuratamente e prestare particolare attenzione per evitare di generare eccezioni proprie.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni in un'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se il server rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una 500 - Internal Server Error risposta senza un corpo della risposta. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste non gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il filtro AddDatabaseDeveloperPageExceptionFilter eccezioni della pagina Sviluppo database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework Core. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle possibili azioni per risolvere il problema. Questa pagina è abilitata solo nell'ambiente di sviluppo. Il codice seguente è stato generato dai modelli ASP.NET Core Razor Pages quando sono stati specificati singoli account utente:

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

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono eventuali eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro. Per altre informazioni, vedere Filtri in ASP.NET Core.

I filtri eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione delle eccezioni predefinito, UseExceptionHandler. È consigliabile usare UseExceptionHandler, a meno che non sia necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

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

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Risorse aggiuntive

Di Tom Dykstra e Steve Smith

Questo articolo illustra gli approcci comuni alla gestione degli errori nelle app Web di ASP.NET Core. Vedere Gestire gli errori nelle API Web basate su controller di base ASP.NET per le API Web.

Visualizzare o scaricare il codice di esempio. (Come scaricare un esempio.)

Pagina delle eccezioni per gli sviluppatori

In Pagina delle eccezioni per gli sviluppatori sono disponibili informazioni dettagliate sulle eccezioni delle richieste. I modelli ASP.NET Core generano il codice seguente:

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

Il codice precedente abilita la pagina delle eccezioni per sviluppatori quando l'app è in esecuzione nell'ambiente di sviluppo.

I modelli vengono inseriti UseDeveloperExceptionPage prima di qualsiasi middleware in modo che le eccezioni vengano rilevate nel middleware che segue.

Il codice precedente abilita la pagina eccezioni per sviluppatori solo quando l'app è in esecuzione nell'ambiente di sviluppo. Le informazioni dettagliate sulle eccezioni non devono essere visualizzate pubblicamente quando l'app viene eseguita nell'ambiente di produzione. Per altre informazioni sulla configurazione degli ambienti, vedere Usare più ambienti in ASP.NET Core.

La pagina Eccezioni per sviluppatori include le informazioni seguenti sull'eccezione e sulla richiesta:

  • Analisi dello stack
  • Parametri della stringa di query, se presenti
  • Cookie se presenti
  • Intestazioni

Pagina del gestore di eccezioni

Per configurare una pagina di gestione degli errori personalizzata per l'ambiente di produzione, usare il middleware di gestione delle eccezioni. Il middleware:

  • Intercetta e registra le eccezioni.
  • Esegue nuovamente la richiesta in una pipeline alternativa per la pagina o il controller indicati. La richiesta non viene eseguita nuovamente se la risposta è stata avviata. Il codice generato dal modello esegue nuovamente la richiesta a /Error.

Nell'esempio seguente UseExceptionHandler aggiunge il middleware di gestione delle eccezioni in ambienti non di sviluppo:

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

Il Razor modello di app Pages fornisce una pagina Di errore () e PageModel una classe (.cshtmlErrorModel) nella cartella Pages. Per un'app MVC, il modello di progetto include un metodo di azione Errore e una visualizzazione Errore nel Home controller.

Non contrassegnare il metodo di azione del gestore degli errori con attributi del metodo HTTP, ad esempio HttpGet. I verbi espliciti impediscono ad alcune richieste di raggiungere il metodo. Consentire l'accesso anonimo al metodo se gli utenti non autenticati dovrebbero visualizzare la visualizzazione degli errori.

Accedere all'eccezione

Usare IExceptionHandlerPathFeature per accedere all'eccezione e al percorso della richiesta originale in un controller o in una pagina del gestore degli errori:

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

Avviso

Non fornire informazioni sugli errori sensibili ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Per attivare la pagina di gestione delle eccezioni precedente, impostare l'ambiente sulle produzioni e forzare un'eccezione.

Espressione lambda per il gestore di eccezioni

Un'alternativa a una pagina di gestione delle eccezioni personalizzata consiste nel fornire un'espressione lambda a UseExceptionHandler. L'uso di un'espressione lambda consente l'accesso all'errore prima di restituire la risposta.

Di seguito è riportato un esempio dell'uso di un'espressione lambda per la gestione delle eccezioni:

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

Nel codice precedente viene await context.Response.WriteAsync(new string(' ', 512)); aggiunto in modo che il browser Internet Explorer visualizzi il messaggio di errore anziché un messaggio di errore di Internet Explorer. Per altre informazioni, vedere questo problema in GitHub.

Avviso

Non fornire informazioni sugli errori sensibili da IExceptionHandlerFeature o IExceptionHandlerPathFeature ai client. Fornire informazioni sugli errori rappresenta un rischio di sicurezza.

Per visualizzare il risultato dell'espressione lambda di gestione delle eccezioni nell'app di esempio, usare le ProdEnvironment direttive del preprocessore e ErrorHandlerLambda e selezionare Attiva un'eccezione nella home pagina.

UseStatusCodePages

Per impostazione predefinita, un'app ASP.NET Core non offre una tabella codici di stato per i codici di stato HTTP, ad esempio 404 - Non trovato. L'app restituisce un codice di stato e un corpo della risposta vuoto. Per specificare le tabelle codici di stato, usare il middleware delle tabelle codici di stato.

Il middleware viene reso disponibile dal pacchetto Microsoft.AspNetCore.Diagnostics .

Per abilitare i gestori di solo testo predefiniti per i codici di stato di errore comuni, chiamare UseStatusCodePages nel metodo Startup.Configure:

app.UseStatusCodePages();

Chiamare UseStatusCodePages prima del middleware di gestione delle richieste (ad esempio, middleware dei file statici e middleware MVC).

Quando UseStatusCodePages non viene usato, passare a un URL senza un endpoint restituisce un messaggio di errore dipendente dal browser che indica che l'endpoint non è stato trovato. Ad esempio, passando a Home/Privacy2. Quando UseStatusCodePages viene chiamato, il browser restituisce:

Status Code: 404; Not Found

UseStatusCodePages con stringa di formato

Per personalizzare il tipo di contenuto della risposta e il testo, usare l'overload di UseStatusCodePages che accetta un tipo di contenuto e una stringa di formato:

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

UseStatusCodePages con espressione lambda

Per specificare la gestione degli errori personalizzata e il codice per la scrittura delle risposte, usare l'overload di UseStatusCodePages che accetta un'espressione 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

Metodo di estensione UseStatusCodePagesWithRedirects:

  • Invia un codice di stato 302 - Trovato al client.
  • Reindirizza il client al percorso specificato nel modello di URL.
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

Il modello di URL può includere un segnaposto {0} per il codice di stato, come illustrato nell'esempio. Se il modello di URL inizia con ~ (tilde), viene ~ sostituito da PathBase. Se si punta a un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint. Per un Razor esempio di Pages, vedere Pages/StatusCode.cshtml nell'app di esempio.

Questo metodo viene usato comunemente quando l'app:

  • Deve reindirizzare il client a un endpoint diverso, in genere nei casi in cui un'altra app elabora l'errore. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint reindirizzato.
  • Non deve conservare e restituire il codice di stato originale con la risposta di reindirizzamento iniziale.

UseStatusCodePagesWithReExecute

Metodo di estensione UseStatusCodePagesWithReExecute:

  • Restituisce il codice di stato originale al client.
  • Genera il corpo della risposta eseguendo nuovamente la pipeline delle richieste con un percorso alternativo.
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");

Se si punta a un endpoint all'interno dell'app, creare una visualizzazione o Razor una pagina MVC per l'endpoint. Assicurarsi UseStatusCodePagesWithReExecute di essere inseriti prima UseRouting in modo che la richiesta possa essere reindirizzata alla pagina di stato. Per un Razor esempio di Pages, vedere Pages/StatusCode.cshtml nell'app di esempio.

Questo metodo viene usato comunemente quando l'app deve:

  • Elaborare la richiesta senza il reindirizzamento a un endpoint diverso. Per le app Web, la barra degli indirizzi del browser del client riflette l'endpoint richiesto in origine.
  • Conservare e restituire il codice di stato originale con la risposta.

I modelli di URL e di stringa di query potrebbero includere un segnaposto ({0}) per il codice di stato. Il modello di URL deve iniziare con una barra (/). Quando si usa un segnaposto nel percorso, verificare che l'endpoint (pagina o controller) possa elaborare il segmento del percorso. Ad esempio, una Razor pagina per gli errori deve accettare il valore del segmento di percorso facoltativo con la @page direttiva :

@page "{code?}"

L'endpoint che elabora l'errore può ottenere l'URL originale che ha generato l'errore, come illustrato nell'esempio seguente:

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

Non contrassegnare il metodo di azione del gestore degli errori con attributi del metodo HTTP, ad esempio HttpGet. I verbi espliciti impediscono ad alcune richieste di raggiungere il metodo. Consentire l'accesso anonimo al metodo se gli utenti non autenticati dovrebbero visualizzare la visualizzazione degli errori.

Disabilitare le tabelle codici di stato

Per disabilitare le tabelle codici di stato per un controller MVC o un metodo di azione, usare l'attributo [SkipStatusCodePages] .

Per disabilitare le tabelle codici di stato per richieste specifiche in un Razor metodo del gestore Pages o in un controller MVC, usare IStatusCodePagesFeature:

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

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

Codice di gestione delle eccezioni

Il codice in pagine di gestione delle eccezioni può generare eccezioni. È spesso consigliabile che le pagine di errore di produzione contengano esclusivamente contenuto statico.

Intestazioni della risposta

Dopo l'invio delle intestazioni per una risposta:

  • L'app non può modificare il codice di stato della risposta.
  • Non è possibile eseguire pagine o gestori delle eccezioni. La risposta deve essere completata o la connessione deve essere interrotta.

Gestione delle eccezioni del server

Oltre alla logica di gestione delle eccezioni nell'app, l'implementazione del server HTTP può gestire alcune eccezioni. Se rileva un'eccezione prima dell'invio delle intestazioni di risposta, il server invia una risposta 500 - Errore interno del server senza corpo. Se il server rileva un'eccezione dopo l'invio delle intestazioni di risposta, il server chiude la connessione. Le richieste che non sono gestite dall'app vengono gestite dal server. Qualsiasi eccezione che si verifica quando il server sta gestendo la richiesta viene gestita dalla gestione delle eccezioni del server. Eventuali pagine di errore personalizzate dell'app, middleware di gestione delle eccezioni e filtri non hanno effetto su questo comportamento.

Gestione delle eccezioni durante l'avvio

Solo il livello di hosting può gestire le eccezioni che si verificano durante l'avvio dell'app. L'host può essere configurato per acquisire gli errori di avvio e acquisire errori dettagliati.

Il livello di hosting può visualizzare una pagina di errore per un errore di avvio acquisito solo se l'errore si verifica dopo l'associazione indirizzo host/porta. Se si verifica un errore di associazione:

  • Il livello di hosting registra un'eccezione critica.
  • Si verifica un arresto anomalo del processo di dotnet.
  • Non viene visualizzata alcuna pagina di errore quando il server HTTP è Kestrel.

Se durante l'esecuzione in IIS (o servizio app di Azure) o in IIS Express il processo non può essere avviato, viene restituito l'errore 502.5 Errore del processo dal modulo ASP.NET Core. Per altre informazioni, vedere Risolvere i problemi relativi a ASP.NET Core nel servizio app Azure e IIS.

Pagina di errore di database

Il middleware della pagina degli errori del database acquisisce le eccezioni correlate al database che possono essere risolte usando le migrazioni di Entity Framework. Quando si verificano queste eccezioni, viene generata una risposta HTML con i dettagli delle azioni possibili per risolvere il problema. Questa pagina deve essere abilitata solo nell'ambiente di sviluppo. Abilitare la pagina aggiungendo codice Startup.Configure:

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

UseDatabaseErrorPage richiede il pacchetto NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

Filtri eccezioni

Nelle app MVC i filtri delle eccezioni possono essere configurati a livello globale o per ogni controller o azione singolarmente. Nelle Razor app Pages possono essere configurate a livello globale o per modello di pagina. Questi filtri gestiscono tutte le eccezioni non gestite che si verificano durante l'esecuzione di un'azione del controller o di un altro filtro Per altre informazioni, vedere Filtri in ASP.NET Core.

Suggerimento

I filtri delle eccezioni sono utili per intercettare le eccezioni che si verificano all'interno di azioni MVC, ma non sono flessibili come il middleware di gestione degli errori. È consigliabile usare il middleware. Usare i filtri solo quando è necessario eseguire la gestione degli errori in modo diverso in base all'azione MVC scelta.

Errori di stato del modello

Per informazioni su come gestire gli errori dello stato del modello, vedere Associazione di modelli e Convalida del modello.

Risorse aggiuntive