Obsługa błędów w internetowych interfejsach API platformy ASP.NET Core

W tym artykule opisano sposób obsługi błędów i dostosowywania obsługi błędów za pomocą internetowych interfejsów API platformy ASP.NET Core.

Strona wyjątku dla deweloperów

Na stronie wyjątku dla deweloperów są wyświetlane szczegółowe ślady stosu błędów serwera. Używa DeveloperExceptionPageMiddleware go do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP i generowania odpowiedzi o błędach. Rozważmy na przykład następującą akcję kontrolera, która zgłasza wyjątek:

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

Gdy strona wyjątku dewelopera wykryje nieobsługiwany wyjątek, generuje domyślną odpowiedź w postaci zwykłego tekstu podobną do następującego przykładu:

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

...

Jeśli klient żąda odpowiedzi w formacie HTML, strona wyjątku dewelopera generuje odpowiedź podobną do następującego przykładu:

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

...

Aby zażądać odpowiedzi w formacie HTML, ustaw Accept nagłówek żądania HTTP na text/htmlwartość .

Ostrzeżenie

Nie włączaj strony wyjątku dla deweloperów, chyba że aplikacja jest uruchomiona w środowisku dewelopera. Nie udostępniaj publicznie szczegółowych informacji o wyjątkach, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

Procedura obsługi wyjątków

W środowiskach nieprodukcyjnych użyj oprogramowania pośredniczącego obsługi wyjątków, aby wygenerować ładunek błędu:

  1. W Program.cspliku wywołaj metodę UseExceptionHandler , aby dodać oprogramowanie pośredniczące obsługi wyjątków:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Skonfiguruj akcję kontrolera w celu reagowania /error na trasę:

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

HandleError Poprzednia akcja wysyła ładunek zgodny ze specyfikacją RFC 7807 do klienta.

Ostrzeżenie

Nie oznaczaj metody akcji procedury obsługi błędów z atrybutami metody HTTP, takimi jak HttpGet. Jawne czasowniki uniemożliwiają dotarcie niektórych żądań do metody akcji.

W przypadku internetowych interfejsów API, które używają struktury Swagger /OpenAPI, oznacz akcję obsługi błędów za pomocą atrybutu [ApiExplorer Ustawienia] i ustaw jego IgnoreApi właściwość na true. Ta konfiguracja atrybutu wyklucza akcję obsługi błędów ze specyfikacji interfejsu OpenAPI aplikacji:

[ApiExplorerSettings(IgnoreApi = true)]

Zezwalaj na dostęp anonimowy do metody, jeśli użytkownicy nieuwierzytelnieni powinni zobaczyć błąd.

Oprogramowanie pośredniczące obsługi wyjątków może być również używane w środowisku programistycznym do tworzenia spójnego formatu ładunku we wszystkich środowiskach:

  1. W Program.csprogramie zarejestruj wystąpienia oprogramowania pośredniczącego obsługi wyjątków specyficzne dla środowiska:

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

    W poprzednim kodzie oprogramowanie pośredniczące jest zarejestrowane za pomocą:

    • Trasa /error-development w środowisku programistycznym.
    • Trasa /error w środowiskach nieprogramowania.

  2. Dodaj akcje kontrolera zarówno dla tras programistycznych, jak i nieprogramowania:

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

Modyfikowanie odpowiedzi przy użyciu wyjątków

Zawartość odpowiedzi można zmodyfikować spoza kontrolera przy użyciu wyjątku niestandardowego i filtru akcji:

  1. Utwórz dobrze znany typ wyjątku o nazwie 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. Utwórz filtr akcji o nazwie 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;
            }
        }
    }
    

    Powyższy filtr określa maksymalną Order wartość całkowitą minus 10. Umożliwia to Order uruchamianie innych filtrów na końcu potoku.

  3. W Program.cspliku dodaj filtr akcji do kolekcji filtrów:

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

Odpowiedź na błąd niepowodzenia walidacji

W przypadku kontrolerów internetowego interfejsu API mvC odpowiada za pomocą typu odpowiedzi, gdy walidacja modelu zakończy się niepowodzeniem ValidationProblemDetails . MvC używa wyników polecenia InvalidModelStateResponseFactory , aby skonstruować odpowiedź o błędzie dla błędu weryfikacji. Poniższy przykład zastępuje domyślną fabrykę implementacją, która obsługuje również formatowanie odpowiedzi jako XML w pliku 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();

Odpowiedź na błąd klienta

Wynik błędu jest definiowany w wyniku z kodem stanu HTTP 400 lub wyższym. W przypadku kontrolerów internetowego interfejsu API MVC przekształca wynik błędu w celu wygenerowania elementu ProblemDetails.

Automatyczne tworzenie ProblemDetails kodu stanu błędu jest domyślnie włączone, ale odpowiedzi na błędy można skonfigurować na jeden z następujących sposobów:

  1. Korzystanie z usługi szczegółów problemu
  2. Implementowanie elementu ProblemDetailsFactory
  3. Korzystanie z interfejsu ApiBehaviorOptions.ClientErrorMapping

Domyślna odpowiedź na szczegóły problemu

Program.cs Następujący plik został wygenerowany przez szablony aplikacji internetowej dla kontrolerów interfejsu API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Rozważmy następujący kontroler, który zwraca BadRequest wartość, gdy dane wejściowe są nieprawidłowe:

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

Odpowiedź ze szczegółami problemu jest generowana przy użyciu poprzedniego kodu, gdy ma zastosowanie dowolny z następujących warunków:

  • Punkt /api/values2/divide końcowy jest wywoływany z mianownikiem zerowym.
  • Punkt /api/values2/squareroot końcowy jest wywoływany z promieniem mniejszym niż zero.

Domyślna treść odpowiedzi szczegółów problemu ma następujące typewartości , titlei status :

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

Usługa szczegółów problemu

ASP.NET Core obsługuje tworzenie szczegółów problemu dla interfejsów API HTTP przy użyciu polecenia IProblemDetailsService. Aby uzyskać więcej informacji, zobacz usługę Szczegóły problemu.

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

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

Rozważ kontroler interfejsu API z poprzedniej sekcji, który zwraca BadRequest wartość, gdy dane wejściowe są nieprawidłowe:

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

Odpowiedź ze szczegółami problemu jest generowana przy użyciu poprzedniego kodu, gdy ma zastosowanie dowolny z następujących warunków:

  • Podano nieprawidłowe dane wejściowe.
  • Identyfikator URI nie ma pasującego punktu końcowego.
  • Występuje nieobsługiwany wyjątek.

Automatyczne tworzenie informacji ProblemDetails dla kodu stanu błędu jest wyłączone, gdy właściwość SuppressMapClientErrors jest ustawiona na 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();

Korzystając z powyższego kodu, gdy kontroler interfejsu API zwraca BadRequestwartość , zwracany jest stan odpowiedzi HTTP 400 bez treści odpowiedzi. SuppressMapClientErrorsProblemDetails uniemożliwia utworzenie odpowiedzi, nawet podczas wywoływania WriteAsync punktu końcowego kontrolera interfejsu API. WriteAsync w dalszej części tego artykułu wyjaśniono.

W następnej sekcji pokazano, jak dostosować treść odpowiedzi szczegółów problemu przy użyciu polecenia CustomizeProblemDetails, aby zwrócić bardziej pomocną odpowiedź. Aby uzyskać więcej opcji dostosowywania, zobacz Dostosowywanie szczegółów problemu.

Dostosowywanie szczegółów problemu za pomocą polecenia CustomizeProblemDetails

Poniższy kod używa ProblemDetailsOptions metody do ustawienia :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();

Zaktualizowany kontroler interfejsu API:

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

}

Poniższy kod zawiera elementy MathErrorFeature i MathErrorType, które są używane z poprzednim przykładem:

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

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

Odpowiedź ze szczegółami problemu jest generowana przy użyciu poprzedniego kodu, gdy ma zastosowanie dowolny z następujących warunków:

  • Punkt /divide końcowy jest wywoływany z mianownikiem zerowym.
  • Punkt /squareroot końcowy jest wywoływany z promieniem mniejszym niż zero.
  • Identyfikator URI nie ma pasującego punktu końcowego.

Treść odpowiedzi szczegółów problemu zawiera następujące elementy, gdy jeden squareroot z punktów końcowych jest wywoływany z promieniem mniejszym niż zero:

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

Wyświetlanie lub pobieranie przykładowego kodu

Zaimplementować ProblemDetailsFactory

Funkcja MVC używa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory metody do tworzenia wszystkich wystąpień elementów ProblemDetails i ValidationProblemDetails. Ta fabryka jest używana do:

Aby dostosować odpowiedź ze szczegółami problemu, zarejestruj niestandardową implementację ProblemDetailsFactory elementu w pliku Program.cs:

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

Korzystanie z polecenia ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Użyj właściwości , aby skonfigurować zawartość ProblemDetails odpowiedzi. Na przykład następujący kod w Program.cs pliku aktualizuje Link właściwość 404 odpowiedzi:

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

Dodatkowe zasoby

W tym artykule opisano sposób obsługi błędów i dostosowywania obsługi błędów za pomocą internetowych interfejsów API platformy ASP.NET Core.

Strona wyjątku dla deweloperów

Na stronie wyjątku dla deweloperów są wyświetlane szczegółowe ślady stosu błędów serwera. Używa DeveloperExceptionPageMiddleware go do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP i generowania odpowiedzi o błędach. Rozważmy na przykład następującą akcję kontrolera, która zgłasza wyjątek:

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

Gdy strona wyjątku dewelopera wykryje nieobsługiwany wyjątek, generuje domyślną odpowiedź w postaci zwykłego tekstu podobną do następującego przykładu:

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

...

Jeśli klient żąda odpowiedzi w formacie HTML, strona wyjątku dewelopera generuje odpowiedź podobną do następującego przykładu:

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

...

Aby zażądać odpowiedzi w formacie HTML, ustaw Accept nagłówek żądania HTTP na text/htmlwartość .

Ostrzeżenie

Nie włączaj strony wyjątku dla deweloperów, chyba że aplikacja jest uruchomiona w środowisku dewelopera. Nie udostępniaj publicznie szczegółowych informacji o wyjątkach, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

Procedura obsługi wyjątków

W środowiskach nieprodukcyjnych użyj oprogramowania pośredniczącego obsługi wyjątków, aby wygenerować ładunek błędu:

  1. W Program.cspliku wywołaj metodę UseExceptionHandler , aby dodać oprogramowanie pośredniczące obsługi wyjątków:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Skonfiguruj akcję kontrolera w celu reagowania /error na trasę:

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

HandleError Poprzednia akcja wysyła ładunek zgodny ze specyfikacją RFC 7807 do klienta.

Ostrzeżenie

Nie oznaczaj metody akcji procedury obsługi błędów z atrybutami metody HTTP, takimi jak HttpGet. Jawne czasowniki uniemożliwiają dotarcie niektórych żądań do metody akcji.

W przypadku internetowych interfejsów API, które używają struktury Swagger /OpenAPI, oznacz akcję obsługi błędów za pomocą atrybutu [ApiExplorer Ustawienia] i ustaw jego IgnoreApi właściwość na true. Ta konfiguracja atrybutu wyklucza akcję obsługi błędów ze specyfikacji interfejsu OpenAPI aplikacji:

[ApiExplorerSettings(IgnoreApi = true)]

Zezwalaj na dostęp anonimowy do metody, jeśli użytkownicy nieuwierzytelnieni powinni zobaczyć błąd.

Oprogramowanie pośredniczące obsługi wyjątków może być również używane w środowisku programistycznym do tworzenia spójnego formatu ładunku we wszystkich środowiskach:

  1. W Program.csprogramie zarejestruj wystąpienia oprogramowania pośredniczącego obsługi wyjątków specyficzne dla środowiska:

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

    W poprzednim kodzie oprogramowanie pośredniczące jest zarejestrowane za pomocą:

    • Trasa /error-development w środowisku programistycznym.
    • Trasa /error w środowiskach nieprogramowania.

  2. Dodaj akcje kontrolera zarówno dla tras programistycznych, jak i nieprogramowania:

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

Modyfikowanie odpowiedzi przy użyciu wyjątków

Zawartość odpowiedzi można zmodyfikować spoza kontrolera przy użyciu wyjątku niestandardowego i filtru akcji:

  1. Utwórz dobrze znany typ wyjątku o nazwie 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. Utwórz filtr akcji o nazwie 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;
            }
        }
    }
    

    Powyższy filtr określa maksymalną Order wartość całkowitą minus 10. Umożliwia to Order uruchamianie innych filtrów na końcu potoku.

  3. W Program.cspliku dodaj filtr akcji do kolekcji filtrów:

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

Odpowiedź na błąd niepowodzenia walidacji

W przypadku kontrolerów internetowego interfejsu API mvC odpowiada za pomocą typu odpowiedzi, gdy walidacja modelu zakończy się niepowodzeniem ValidationProblemDetails . MvC używa wyników polecenia InvalidModelStateResponseFactory , aby skonstruować odpowiedź o błędzie dla błędu weryfikacji. Poniższy przykład zastępuje domyślną fabrykę implementacją, która obsługuje również formatowanie odpowiedzi jako XML w pliku 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();

Odpowiedź na błąd klienta

Wynik błędu jest definiowany w wyniku z kodem stanu HTTP 400 lub wyższym. W przypadku kontrolerów internetowego interfejsu API MVC przekształca wynik błędu w celu wygenerowania elementu ProblemDetails.

Odpowiedź o błędzie można skonfigurować na jeden z następujących sposobów:

  1. Implementowanie elementu ProblemDetailsFactory
  2. Korzystanie z interfejsu ApiBehaviorOptions.ClientErrorMapping

Zaimplementować ProblemDetailsFactory

Funkcja MVC używa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory metody do tworzenia wszystkich wystąpień elementów ProblemDetails i ValidationProblemDetails. Ta fabryka jest używana do:

Aby dostosować odpowiedź ze szczegółami problemu, zarejestruj niestandardową implementację ProblemDetailsFactory elementu w pliku Program.cs:

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

Korzystanie z polecenia ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Użyj właściwości , aby skonfigurować zawartość ProblemDetails odpowiedzi. Na przykład następujący kod w Program.cs pliku aktualizuje Link właściwość 404 odpowiedzi:

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

Niestandardowe oprogramowanie pośredniczące do obsługi wyjątków

Wartości domyślne w obsłudze oprogramowania pośredniczącego obsługującego wyjątki działają dobrze w przypadku większości aplikacji. W przypadku aplikacji wymagających wyspecjalizowanej obsługi wyjątków rozważ dostosowanie oprogramowania pośredniczącego obsługującego wyjątki.

Tworzenie ładunku ProblemDetails dla wyjątków

ASP.NET Core nie generuje standardowego ładunku błędu, gdy wystąpi nieobsługiwany wyjątek. W scenariuszach, w których pożądane jest zwrócenie ustandaryzowanej odpowiedzi ProblemDetails na klienta, oprogramowanie pośredniczące ProblemDetails może służyć do mapowania wyjątków i 404 odpowiedzi na ładunek ProblemDetails . Oprogramowanie pośredniczące obsługujące wyjątki może być również używane do zwracania ProblemDetails ładunku dla nieobsługiwane wyjątki.

Dodatkowe zasoby

W tym artykule opisano sposób obsługi i dostosowywania obsługi błędów za pomocą internetowych interfejsów API platformy ASP.NET Core.

Wyświetlanie lub pobieranie przykładowego kodu (jak pobrać)

Strona wyjątku dla deweloperów

Strona wyjątku dla deweloperów jest przydatnym narzędziem do uzyskiwania szczegółowych śladów stosu błędów serwera. Używa DeveloperExceptionPageMiddleware go do przechwytywania synchronicznych i asynchronicznych wyjątków z potoku HTTP i generowania odpowiedzi o błędach. Aby zilustrować, rozważmy następującą akcję kontrolera:

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

Uruchom następujące curl polecenie, aby przetestować poprzednią akcję:

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

Strona wyjątku dewelopera wyświetla odpowiedź w postaci zwykłego tekstu, jeśli klient nie żąda danych wyjściowych w formacie HTML. Wyświetlane są następujące dane wyjściowe:

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

Aby wyświetlić odpowiedź w formacie HTML, ustaw Accept nagłówek żądania HTTP na typ nośnika text/html . Na przykład:

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

Rozważmy następujący fragment odpowiedzi 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;
}

Odpowiedź w formacie HTML staje się przydatna podczas testowania za pomocą narzędzi takich jak Postman. Na poniższym zrzucie ekranu przedstawiono zarówno zwykły tekst, jak i odpowiedzi w formacie HTML w narzędziu Postman:

Test the Developer Exception Page in Postman.

Ostrzeżenie

Włącz stronę wyjątku dewelopera tylko wtedy, gdy aplikacja jest uruchomiona w środowisku dewelopera. Nie udostępniaj publicznie szczegółowych informacji o wyjątkach, gdy aplikacja działa w środowisku produkcyjnym. Aby uzyskać więcej informacji na temat konfigurowania środowisk, zobacz Używanie wielu środowisk w programie ASP.NET Core.

Nie oznaczaj metody akcji procedury obsługi błędów z atrybutami metody HTTP, takimi jak HttpGet. Jawne czasowniki uniemożliwiają dotarcie niektórych żądań do metody akcji. Zezwalaj na dostęp anonimowy do metody, jeśli użytkownicy nieuwierzytelnieni powinni zobaczyć błąd.

Procedura obsługi wyjątków

W środowiskach nieprodukcyjnych oprogramowanie pośredniczące obsługi wyjątków może służyć do generowania ładunku błędu:

  1. W Startup.Configurepliku wywołaj metodę UseExceptionHandler , aby użyć oprogramowania pośredniczącego:

    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. Skonfiguruj akcję kontrolera w celu reagowania /error na trasę:

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

Error Poprzednia akcja wysyła ładunek zgodny ze specyfikacją RFC 7807 do klienta.

Oprogramowanie pośredniczące obsługi wyjątków może również udostępniać bardziej szczegółowe dane wyjściowe negocjowane przez zawartość w lokalnym środowisku programistycznym. Wykonaj następujące kroki, aby utworzyć spójny format ładunku w środowiskach deweloperskich i produkcyjnych:

  1. W Startup.Configureprogramie zarejestruj wystąpienia oprogramowania pośredniczącego obsługi wyjątków specyficzne dla środowiska:

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

    W poprzednim kodzie oprogramowanie pośredniczące jest zarejestrowane za pomocą:

    • Trasa /error-local-development w środowisku programistycznym.
    • Trasa /error w środowiskach, które nie są programowaniem.

  2. Zastosuj routing atrybutów do akcji kontrolera:

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

    Powyższy kod wywołuje metodę ControllerBase.Problem , aby utworzyć ProblemDetails odpowiedź.

Modyfikowanie odpowiedzi przy użyciu wyjątków

Zawartość odpowiedzi można modyfikować spoza kontrolera. W ASP.NET interfejsie API sieci Web w wersji 4.x jednym ze sposobów, aby to zrobić, było użycie HttpResponseException typu . ASP.NET Core nie zawiera równoważnego typu. HttpResponseException Obsługę programu można dodać, wykonując następujące czynności:

  1. Utwórz dobrze znany typ wyjątku o nazwie HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Utwórz filtr akcji o nazwie 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;
            }
        }
    }
    

    Powyższy filtr określa maksymalną Order wartość całkowitą minus 10. Umożliwia to Order uruchamianie innych filtrów na końcu potoku.

  3. W Startup.ConfigureServicespliku dodaj filtr akcji do kolekcji filtrów:

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

Odpowiedź na błąd niepowodzenia walidacji

W przypadku kontrolerów internetowego interfejsu API mvC odpowiada za pomocą typu odpowiedzi, gdy walidacja modelu zakończy się niepowodzeniem ValidationProblemDetails . MvC używa wyników polecenia InvalidModelStateResponseFactory , aby skonstruować odpowiedź o błędzie dla błędu weryfikacji. W poniższym przykładzie użyto fabryki, aby zmienić domyślny typ odpowiedzi na SerializableError w pliku 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;
        };
    });

Odpowiedź na błąd klienta

Wynik błędu jest definiowany w wyniku z kodem stanu HTTP 400 lub wyższym. W przypadku kontrolerów internetowego interfejsu API MVC przekształca wynik błędu na wynik za pomocą polecenia ProblemDetails.

Odpowiedź o błędzie można skonfigurować na jeden z następujących sposobów:

  1. Implementowanie elementu ProblemDetailsFactory
  2. Korzystanie z interfejsu ApiBehaviorOptions.ClientErrorMapping

Zaimplementować ProblemDetailsFactory

Funkcja MVC używa Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory metody do tworzenia wszystkich wystąpień elementów ProblemDetails i ValidationProblemDetails. Ta fabryka jest używana do:

Aby dostosować odpowiedź ze szczegółami problemu, zarejestruj niestandardową implementację ProblemDetailsFactory elementu w pliku Startup.ConfigureServices:

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

Korzystanie z interfejsu ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping Użyj właściwości , aby skonfigurować zawartość ProblemDetails odpowiedzi. Na przykład następujący kod w Startup.ConfigureServices pliku aktualizuje type właściwość 404 odpowiedzi:

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

Niestandardowe oprogramowanie pośredniczące do obsługi wyjątków

Wartości domyślne w obsłudze oprogramowania pośredniczącego obsługującego wyjątki działają dobrze w przypadku większości aplikacji. W przypadku aplikacji wymagających wyspecjalizowanej obsługi wyjątków rozważ dostosowanie oprogramowania pośredniczącego obsługującego wyjątki.

Tworzenie ładunku ProblemDetails dla wyjątków

ASP.NET Core nie generuje standardowego ładunku błędu, gdy wystąpi nieobsługiwany wyjątek. W scenariuszach, w których pożądane jest zwrócenie ustandaryzowanej odpowiedzi ProblemDetails na klienta, oprogramowanie pośredniczące ProblemDetails może służyć do mapowania wyjątków i 404 odpowiedzi na ładunek ProblemDetails . Oprogramowanie pośredniczące obsługujące wyjątki może być również używane do zwracania ProblemDetails ładunku dla nieobsługiwane wyjątki.