Gestire gli errori nelle API Web ASP.NET Core

Questo articolo descrive come gestire gli errori e personalizzare la gestione degli errori con le API Web di base di ASP.NET.

Pagina delle eccezioni per gli sviluppatori

La pagina Eccezioni sviluppatore mostra analisi dettagliate dello stack per gli errori del server. DeveloperExceptionPageMiddleware Usa per acquisire eccezioni sincrone e asincrone dalla pipeline HTTP e per generare risposte di errore. Si consideri ad esempio l'azione controller seguente, che genera un'eccezione:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Quando la pagina eccezioni per sviluppatori rileva un'eccezione non gestita, genera una risposta di testo normale predefinita simile all'esempio seguente:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Se il client richiede una risposta in formato HTML, la pagina eccezioni per sviluppatori genera una risposta simile all'esempio seguente:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Per richiedere una risposta in formato HTML, impostare l'intestazione della Accept richiesta HTTP su text/html.

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.

Gestore di eccezioni

Negli ambienti non di sviluppo usare il middleware di gestione delle eccezioni per produrre un payload di errore:

  1. In Program.cschiamare UseExceptionHandler per aggiungere il middleware di gestione delle eccezioni:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Configurare un'azione del controller per rispondere alla /error route:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

L'azione precedente HandleError invia un payload conforme a RFC 7807 al client.

Avviso

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

Per le API Web che usano Swagger/OpenAPI, contrassegnare l'azione del gestore degli errori con l'attributo [ApiExplorer Impostazioni] e impostarne la IgnoreApi proprietà su true. Questa configurazione dell'attributo esclude l'azione del gestore errori dalla specifica OpenAPI dell'app:

[ApiExplorerSettings(IgnoreApi = true)]

Consentire l'accesso anonimo al metodo se gli utenti non autenticati dovrebbero visualizzare l'errore.

Il middleware di gestione delle eccezioni può essere usato anche nell'ambiente di sviluppo per produrre un formato di payload coerente in tutti gli ambienti:

  1. In Program.csregistrare istanze middleware di gestione delle eccezioni specifiche dell'ambiente:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    Nel codice precedente il middleware viene registrato con:

    • Route di nell'ambiente di /error-development sviluppo.
    • Route di /error in ambienti non di sviluppo.

  2. Aggiungere azioni del controller per le route sviluppo e non di sviluppo:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Usare le eccezioni per modificare la risposta

Il contenuto della risposta può essere modificato dall'esterno del controller usando un'eccezione personalizzata e un filtro azione:

  1. Creare un tipo di eccezione noto denominato HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Creare un filtro di azione denominato HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    Il filtro precedente specifica un Order valore intero massimo meno 10. Ciò Order consente l'esecuzione di altri filtri alla fine della pipeline.

  3. In Program.csaggiungere il filtro azione alla raccolta di filtri:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Risposta all'errore di convalida

Per i controller API Web, MVC risponde con un ValidationProblemDetails tipo di risposta quando la convalida del modello ha esito negativo. MVC usa i risultati di per costruire la risposta di InvalidModelStateResponseFactory errore per un errore di convalida. Nell'esempio seguente la factory predefinita viene sostituita con un'implementazione che supporta anche la formattazione delle risposte come XML, in Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Risposta di errore del client

Un risultato di errore viene definito come risultato con un codice di stato HTTP pari a 400 o superiore. Per i controller API Web, MVC trasforma un risultato di errore per produrre un oggetto ProblemDetails.

La creazione automatica di un per ProblemDetails i codici di stato degli errori è abilitata per impostazione predefinita, ma le risposte agli errori possono essere configurate in uno dei modi seguenti:

  1. Usare il servizio dettagli del problema
  2. Implementare ProblemDetailsFactory
  3. Usare ApiBehaviorOptions.ClientErrorMapping

Risposta dei dettagli del problema predefinita

Il file seguente Program.cs è stato generato dai modelli di applicazione Web per i controller API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Si consideri il controller seguente, che restituisce BadRequest quando l'input non è valido:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

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

Quando si applica una delle condizioni seguenti, viene generata una risposta ai dettagli del problema con il codice precedente:

  • L'endpoint /api/values2/divide viene chiamato con un denominatore zero.
  • L'endpoint /api/values2/squareroot viene chiamato con una radice e minore di zero.

Il corpo della risposta dei dettagli del problema predefinito include i valori , titlee status seguentitype:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

Servizio dettagli problema

ASP.NET Core supporta la creazione di dettagli del problema per le API HTTP tramite .IProblemDetailsService Per altre informazioni, vedere il servizio Dettagli problema.

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:

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

Si consideri il controller API della sezione precedente, che restituisce BadRequest quando l'input non è valido:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

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

Quando si applica una delle condizioni seguenti, viene generata una risposta ai dettagli del problema con il codice precedente:

  • Viene fornito un input non valido.
  • L'URI non ha alcun endpoint corrispondente.
  • Si verifica un'eccezione non gestita.

La creazione automatica di ProblemDetails per i codici di stato di errore è disabilitata quando la proprietà SuppressMapClientErrors è impostata su true:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Usando il codice precedente, quando un controller API restituisce BadRequest, viene restituito uno stato di risposta HTTP 400 senza corpo della risposta. SuppressMapClientErrors impedisce la creazione di una ProblemDetails risposta, anche quando si chiama WriteAsync un endpoint del controller API. WriteAsync viene spiegato più avanti in questo articolo.

La sezione successiva illustra come personalizzare il corpo della risposta ai dettagli del problema, usando CustomizeProblemDetails, per restituire una risposta più utile. Per altre opzioni di personalizzazione, vedere Personalizzazione dei dettagli del problema.

Personalizzare i dettagli del problema con CustomizeProblemDetails

Il codice seguente usa ProblemDetailsOptions per impostare CustomizeProblemDetails:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
        options.CustomizeProblemDetails = (context) =>
        {

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

                context.ProblemDetails.Type = details.Type;
                context.ProblemDetails.Title = "Bad Input";
                context.ProblemDetails.Detail = details.Detail;
            }
        }
    );

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

Controller API aggiornato:

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

}

Il codice seguente contiene e MathErrorFeatureMathErrorType, che vengono usati con l'esempio precedente:

// Custom Http Request Feature
class MathErrorFeature
{
    public MathErrorType MathError { get; set; }
}

// Custom math errors
enum MathErrorType
{
    DivisionByZeroError,
    NegativeRadicandError
}

Quando si applica una delle condizioni seguenti, viene generata una risposta ai dettagli del problema con il codice precedente:

  • L'endpoint /divide viene chiamato con un denominatore zero.
  • L'endpoint /squareroot viene chiamato con una radice e minore di zero.
  • L'URI non ha alcun endpoint corrispondente.

Il corpo della risposta ai dettagli del problema contiene quanto segue quando uno squareroot dei due endpoint viene chiamato con una radice e minore di zero:

{
  "type": "https://en.wikipedia.org/wiki/Square_root",
  "title": "Bad Input",
  "status": 400,
  "detail": "Negative or complex numbers are not allowed."
}

Visualizzare o scaricare codice di esempio

Implementare ProblemDetailsFactory

MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory per produrre tutte le istanze di ProblemDetails e ValidationProblemDetails. Questa factory viene usata per:

Per personalizzare la risposta ai dettagli del problema, registrare un'implementazione personalizzata di ProblemDetailsFactory in Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Utilizzare ApiBehaviorOptions.ClientErrorMapping

Usare la proprietà ClientErrorMapping per configurare il contenuto della risposta ProblemDetails. Ad esempio, il codice seguente in Program.cs aggiorna la Link proprietà per le risposte 404:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Risorse aggiuntive

Questo articolo descrive come gestire gli errori e personalizzare la gestione degli errori con le API Web di base di ASP.NET.

Pagina delle eccezioni per gli sviluppatori

La pagina Eccezioni sviluppatore mostra analisi dettagliate dello stack per gli errori del server. DeveloperExceptionPageMiddleware Usa per acquisire eccezioni sincrone e asincrone dalla pipeline HTTP e per generare risposte di errore. Si consideri ad esempio l'azione controller seguente, che genera un'eccezione:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

Quando la pagina eccezioni per sviluppatori rileva un'eccezione non gestita, genera una risposta di testo normale predefinita simile all'esempio seguente:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

Se il client richiede una risposta in formato HTML, la pagina eccezioni per sviluppatori genera una risposta simile all'esempio seguente:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

Per richiedere una risposta in formato HTML, impostare l'intestazione della Accept richiesta HTTP su text/html.

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.

Gestore di eccezioni

Negli ambienti non di sviluppo usare il middleware di gestione delle eccezioni per produrre un payload di errore:

  1. In Program.cschiamare UseExceptionHandler per aggiungere il middleware di gestione delle eccezioni:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Configurare un'azione del controller per rispondere alla /error route:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

L'azione precedente HandleError invia un payload conforme a RFC 7807 al client.

Avviso

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

Per le API Web che usano Swagger/OpenAPI, contrassegnare l'azione del gestore degli errori con l'attributo [ApiExplorer Impostazioni] e impostarne la IgnoreApi proprietà su true. Questa configurazione dell'attributo esclude l'azione del gestore errori dalla specifica OpenAPI dell'app:

[ApiExplorerSettings(IgnoreApi = true)]

Consentire l'accesso anonimo al metodo se gli utenti non autenticati dovrebbero visualizzare l'errore.

Il middleware di gestione delle eccezioni può essere usato anche nell'ambiente di sviluppo per produrre un formato di payload coerente in tutti gli ambienti:

  1. In Program.csregistrare istanze middleware di gestione delle eccezioni specifiche dell'ambiente:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    Nel codice precedente il middleware viene registrato con:

    • Route di nell'ambiente di /error-development sviluppo.
    • Route di /error in ambienti non di sviluppo.

  2. Aggiungere azioni del controller per le route sviluppo e non di sviluppo:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

Usare le eccezioni per modificare la risposta

Il contenuto della risposta può essere modificato dall'esterno del controller usando un'eccezione personalizzata e un filtro azione:

  1. Creare un tipo di eccezione noto denominato HttpResponseException:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. Creare un filtro di azione denominato HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    Il filtro precedente specifica un Order valore intero massimo meno 10. Ciò Order consente l'esecuzione di altri filtri alla fine della pipeline.

  3. In Program.csaggiungere il filtro azione alla raccolta di filtri:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

Risposta all'errore di convalida

Per i controller API Web, MVC risponde con un ValidationProblemDetails tipo di risposta quando la convalida del modello ha esito negativo. MVC usa i risultati di per costruire la risposta di InvalidModelStateResponseFactory errore per un errore di convalida. Nell'esempio seguente la factory predefinita viene sostituita con un'implementazione che supporta anche la formattazione delle risposte come XML, in Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

Risposta di errore del client

Un risultato di errore viene definito come risultato con un codice di stato HTTP pari a 400 o superiore. Per i controller API Web, MVC trasforma un risultato di errore per produrre un oggetto ProblemDetails.

La risposta di errore può essere configurata in uno dei modi seguenti:

  1. Implementare ProblemDetailsFactory
  2. Usare ApiBehaviorOptions.ClientErrorMapping

Implementare ProblemDetailsFactory

MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory per produrre tutte le istanze di ProblemDetails e ValidationProblemDetails. Questa factory viene usata per:

Per personalizzare la risposta ai dettagli del problema, registrare un'implementazione personalizzata di ProblemDetailsFactory in Program.cs:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

Utilizzare ApiBehaviorOptions.ClientErrorMapping

Usare la proprietà ClientErrorMapping per configurare il contenuto della risposta ProblemDetails. Ad esempio, il codice seguente in Program.cs aggiorna la Link proprietà per le risposte 404:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Middleware personalizzato per gestire le eccezioni

Le impostazioni predefinite nel middleware di gestione delle eccezioni funzionano bene per la maggior parte delle app. Per le app che richiedono una gestione specializzata delle eccezioni, è consigliabile personalizzare il middleware di gestione delle eccezioni.

Generare un payload ProblemDetails per le eccezioni

ASP.NET Core non genera un payload di errore standardizzato quando si verifica un'eccezione non gestita. Per gli scenari in cui è consigliabile restituire una risposta Standard ProblemDetails al client, è possibile usare il middleware ProblemDetails per eseguire il mapping delle eccezioni e delle risposte 404 a un payload ProblemDetails . Il middleware di gestione delle eccezioni può essere usato anche per restituire un ProblemDetails payload per le eccezioni non gestite.

Risorse aggiuntive

Questo articolo descrive come gestire e personalizzare la gestione degli errori con le API Web di ASP.NET Core.

Visualizzare o scaricare il codice di esempio (Come scaricare)

Pagina delle eccezioni per gli sviluppatori

La pagina Delle eccezioni per sviluppatori è uno strumento utile per ottenere analisi dettagliate dello stack per gli errori del server. DeveloperExceptionPageMiddleware Usa per acquisire eccezioni sincrone e asincrone dalla pipeline HTTP e per generare risposte di errore. Per illustrare, considerare l'azione controller seguente:

[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
    if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException(
            $"We don't offer a weather forecast for {city}.", nameof(city));
    }
    
    return GetWeather().First();
}

Eseguire il comando seguente curl per testare l'azione precedente:

curl -i https://localhost:5001/weatherforecast/chicago

La pagina Delle eccezioni per sviluppatori visualizza una risposta in testo normale se il client non richiede l'output in formato HTML. Viene visualizzato l'output seguente:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT

System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
   at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1

Per visualizzare invece una risposta in formato HTML, impostare l'intestazione Accept della richiesta HTTP sul text/html tipo di supporto. Ad esempio:

curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago

Si consideri l'estratto seguente dalla risposta HTTP:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

La risposta in formato HTML diventa utile quando si esegue il test tramite strumenti come Postman. L'acquisizione di schermata seguente mostra sia il testo normale che le risposte in formato HTML in Postman:

Test the Developer Exception Page in Postman.

Avviso

Abilitare la pagina delle eccezioni per gli sviluppatori solo quando l'app è 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.

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 di azione. Consentire l'accesso anonimo al metodo se gli utenti non autenticati dovrebbero visualizzare l'errore.

Gestore di eccezioni

Negli ambienti non di sviluppo, il middleware di gestione delle eccezioni può essere usato per produrre un payload di errore:

  1. In Startup.Configurerichiamare UseExceptionHandler per usare il middleware:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  2. Configurare un'azione del controller per rispondere alla /error route:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

L'azione precedente Error invia un payload conforme a RFC 7807 al client.

Il middleware di gestione delle eccezioni può anche fornire un output negoziato con contenuto più dettagliato nell'ambiente di sviluppo locale. Usare la procedura seguente per produrre un formato di payload coerente in ambienti di sviluppo e produzione:

  1. In Startup.Configureregistrare istanze middleware di gestione delle eccezioni specifiche dell'ambiente:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    

    Nel codice precedente il middleware viene registrato con:

    • Route di nell'ambiente di /error-local-development sviluppo.
    • Route di /error in ambienti che non sono Sviluppo.

  2. Applicare il routing degli attributi alle azioni del controller:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            if (webHostEnvironment.EnvironmentName != "Development")
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
            return Problem(
                detail: context.Error.StackTrace,
                title: context.Error.Message);
        }
    
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

    Il codice precedente chiama ControllerBase.Problem per creare una ProblemDetails risposta.

Usare le eccezioni per modificare la risposta

Il contenuto della risposta può essere modificato dall'esterno del controller. In ASP.NET API Web 4.x, un modo per eseguire questa operazione consiste nell'usare il HttpResponseException tipo . ASP.NET Core non include un tipo equivalente. È possibile aggiungere il supporto per HttpResponseException con la procedura seguente:

  1. Creare un tipo di eccezione noto denominato HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Creare un filtro di azione denominato HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException exception)
            {
                context.Result = new ObjectResult(exception.Value)
                {
                    StatusCode = exception.Status,
                };
                context.ExceptionHandled = true;
            }
        }
    }
    

    Il filtro precedente specifica un Order valore intero massimo meno 10. Ciò Order consente l'esecuzione di altri filtri alla fine della pipeline.

  3. In Startup.ConfigureServicesaggiungere il filtro azione alla raccolta di filtri:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    

Risposta all'errore di convalida

Per i controller API Web, MVC risponde con un ValidationProblemDetails tipo di risposta quando la convalida del modello ha esito negativo. MVC usa i risultati di per costruire la risposta di InvalidModelStateResponseFactory errore per un errore di convalida. Nell'esempio seguente viene usata la factory per modificare il tipo di risposta predefinito in SerializableError in Startup.ConfigureServices:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });

Risposta di errore del client

Un risultato di errore viene definito come risultato con un codice di stato HTTP pari a 400 o superiore. Per i controller API Web, MVC trasforma un risultato di errore in un risultato con ProblemDetails.

La risposta di errore può essere configurata in uno dei modi seguenti:

  1. Implementare ProblemDetailsFactory
  2. Usare ApiBehaviorOptions.ClientErrorMapping

Implementare ProblemDetailsFactory

MVC usa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory per produrre tutte le istanze di ProblemDetails e ValidationProblemDetails. Questa factory viene usata per:

Per personalizzare la risposta ai dettagli del problema, registrare un'implementazione personalizzata di ProblemDetailsFactory in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection serviceCollection)
{
    services.AddControllers();
    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

Usare ApiBehaviorOptions.ClientErrorMapping

Usare la proprietà ClientErrorMapping per configurare il contenuto della risposta ProblemDetails. Ad esempio, il codice seguente in Startup.ConfigureServices aggiorna la type proprietà per le risposte 404:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
        options.DisableImplicitFromServicesParameters = true;
    });

Middleware personalizzato per gestire le eccezioni

Le impostazioni predefinite nel middleware di gestione delle eccezioni funzionano bene per la maggior parte delle app. Per le app che richiedono una gestione specializzata delle eccezioni, è consigliabile personalizzare il middleware di gestione delle eccezioni.

Creazione di un payload ProblemDetails per le eccezioni

ASP.NET Core non genera un payload di errore standardizzato quando si verifica un'eccezione non gestita. Per gli scenari in cui è consigliabile restituire una risposta Standard ProblemDetails al client, è possibile usare il middleware ProblemDetails per eseguire il mapping delle eccezioni e delle risposte 404 a un payload ProblemDetails . Il middleware di gestione delle eccezioni può essere usato anche per restituire un ProblemDetails payload per le eccezioni non gestite.