Обработка ошибок в веб-API ASP.NET Core

В этой статье описывается обработка ошибок и настройка обработки ошибок с помощью веб-API ASP.NET Core.

Страница со сведениями об исключении для разработчика

На странице исключений разработчика отображаются подробные трассировки стека для ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Например, рассмотрим следующее действие контроллера, которое вызывает исключение:

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

Когда страница исключений разработчика обнаруживает необработанное исключение, он создает ответ обычного текста по умолчанию, аналогичный следующему примеру:

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

...

Если клиент запрашивает html-форматированный ответ, страница исключений разработчика создает ответ, аналогичный следующему примеру:

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

...

Чтобы запросить html-форматированный ответ, задайте для заголовка Accept HTTP-запроса значение text/html.

Предупреждение

Не включите страницу исключений разработчика, если приложение не запущено в среде разработки. Не делитесь подробными сведениями об исключениях публично при запуске приложения в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Обработчик исключений

В средах, отличных от разработки, используйте ПО промежуточного слоя обработки исключений для создания полезных данных об ошибке:

  1. В Program.csвызове для UseExceptionHandler добавления ПО промежуточного слоя обработки исключений:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Настройте действие контроллера для ответа на маршрут /error:

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

Предыдущее действие HandleError отправляет клиенту полезные данные, соответствующие RFC 7807.

Предупреждение

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet. Из-за использования явных команд некоторые запросы могут не передаваться в метод действия.

Для веб-API, использующих Swagger / OpenAPI, пометьте действие обработчика ошибок атрибутом [Api ОбозревательПараметры] и задайте для его свойства trueзначениеIgnoreApi. Эта конфигурация атрибута исключает действие обработчика ошибок из спецификации OpenAPI приложения:

[ApiExplorerSettings(IgnoreApi = true)]

Разрешить анонимный доступ к методу, если пользователи, не прошедшие проверку подлинности, должны увидеть ошибку.

По промежуточному слоям обработки исключений также можно использовать в среде разработки для создания согласованного формата полезных данных во всех средах:

  1. В Program.cs зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

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

    В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

    • Маршрут /error-development в среде разработки.
    • Маршрут в средах, отличных от разработки /error .

  2. Добавьте действия контроллера для маршрутов разработки и не разработки:

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

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера с помощью настраиваемого исключения и фильтра действий:

  1. Создайте известный тип исключения с именем 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. Создайте фильтр действий с именем 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;
            }
        }
    }
    

    Предыдущий фильтр задает Order максимальное целочисленное значение минус 10. Это Order позволяет другим фильтрам выполняться в конце конвейера.

  3. В Program.cs добавьте фильтр действий в коллекцию фильтров:

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

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере фабрика по умолчанию заменяет реализацию, которая также поддерживает форматирование ответов в формате XML.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();

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. Для контроллеров веб-API MVC преобразует результат ошибки для создания ProblemDetails.

Автоматическое создание ProblemDetails кодов состояния ошибки включено по умолчанию, но ответы на ошибки можно настроить одним из следующих способов:

  1. Использование службы сведений о проблеме
  2. Реализация ProblemDetailsFactory.
  3. Использование ApiBehaviorOptions.ClientErrorMapping.

Ответ сведений о проблеме по умолчанию

Program.cs Следующий файл был создан шаблонами веб-приложений для контроллеров API:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Рассмотрим следующий контроллер, который возвращается BadRequest , когда входные данные недопустимы:

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

Ответ сведений о проблеме создается с помощью предыдущего кода при применении любого из следующих условий:

  • /api/values2/divide Конечная точка вызывается с нулевым знаменателем.
  • Конечная /api/values2/squareroot точка вызывается с радикой меньше нуля.

Текст ответа о проблеме по умолчанию содержит следующие typetitleзначения:status

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

Служба сведений о проблеме

ASP.NET Core поддерживает создание сведений о проблеме для API HTTP с помощью .IProblemDetailsService Дополнительные сведения см. в службе сведений о проблеме.

Следующий код настраивает приложение для создания ответа сведений о проблеме для всех ответов об ошибках HTTP-клиента и сервера, которые еще не содержат содержимого текста:

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

Рассмотрим контроллер API из предыдущего раздела, который возвращает BadRequest , когда входные данные недопустимы:

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

Ответ сведений о проблеме создается с помощью предыдущего кода при применении любого из следующих условий:

  • Предоставляется недопустимый вход.
  • Универсальный код ресурса (URI) не имеет соответствующей конечной точки.
  • Возникает необработанное исключение.

Отключить автоматическое создание ProblemDetails для кодов состояния ошибок можно, задав свойству SuppressMapClientErrors значение 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();

При возврате BadRequestконтроллера API состояние ответа HTTP 400 возвращается без текста ответа. SuppressMapClientErrors Запрещает ProblemDetails создание ответа даже при вызове WriteAsync конечной точки контроллера API. WriteAsync объясняется далее в этой статье.

В следующем разделе показано, как настроить текст ответа сведений о проблеме, используя его CustomizeProblemDetails, чтобы вернуть более полезный ответ. Дополнительные параметры настройки см. в разделе "Настройка проблем".

Настройка сведений о проблеме с помощью CustomizeProblemDetails

Следующий код используется ProblemDetailsOptions для задания 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();

Обновленный контроллер 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));
    }

}

Следующий код содержит MathErrorFeature и MathErrorType, которые используются в предыдущем примере:

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

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

Ответ сведений о проблеме создается с помощью предыдущего кода при применении любого из следующих условий:

  • /divide Конечная точка вызывается с нулевым знаменателем.
  • Конечная /squareroot точка вызывается с радикой меньше нуля.
  • Универсальный код ресурса (URI) не имеет соответствующей конечной точки.

Текст ответа сведений о проблеме содержит следующее, если любая squareroot конечная точка вызывается с радикандом меньше нуля:

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

Просмотреть или скачать образец кода

Реализуйте ProblemDetailsFactory

MVC создает все экземпляры ProblemDetails и ValidationProblemDetails с помощью Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory. Эта фабрика используется для:

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Program.cs:

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

Использование ApiBehaviorOptions.ClientErrorMapping

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails. Например, следующий код в Link обновляет свойство Program.cs для ответов 404:

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

Дополнительные ресурсы

В этой статье описывается обработка ошибок и настройка обработки ошибок с помощью веб-API ASP.NET Core.

Страница со сведениями об исключении для разработчика

На странице исключений разработчика отображаются подробные трассировки стека для ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Например, рассмотрим следующее действие контроллера, которое вызывает исключение:

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

Когда страница исключений разработчика обнаруживает необработанное исключение, он создает ответ обычного текста по умолчанию, аналогичный следующему примеру:

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

...

Если клиент запрашивает html-форматированный ответ, страница исключений разработчика создает ответ, аналогичный следующему примеру:

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

...

Чтобы запросить html-форматированный ответ, задайте для заголовка Accept HTTP-запроса значение text/html.

Предупреждение

Не включите страницу исключений разработчика, если приложение не запущено в среде разработки. Не делитесь подробными сведениями об исключениях публично при запуске приложения в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Обработчик исключений

В средах, отличных от разработки, используйте ПО промежуточного слоя обработки исключений для создания полезных данных об ошибке:

  1. В Program.csвызове для UseExceptionHandler добавления ПО промежуточного слоя обработки исключений:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. Настройте действие контроллера для ответа на маршрут /error:

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

Предыдущее действие HandleError отправляет клиенту полезные данные, соответствующие RFC 7807.

Предупреждение

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet. Из-за использования явных команд некоторые запросы могут не передаваться в метод действия.

Для веб-API, использующих Swagger / OpenAPI, пометьте действие обработчика ошибок атрибутом [Api ОбозревательПараметры] и задайте для его свойства trueзначениеIgnoreApi. Эта конфигурация атрибута исключает действие обработчика ошибок из спецификации OpenAPI приложения:

[ApiExplorerSettings(IgnoreApi = true)]

Разрешить анонимный доступ к методу, если пользователи, не прошедшие проверку подлинности, должны увидеть ошибку.

По промежуточному слоям обработки исключений также можно использовать в среде разработки для создания согласованного формата полезных данных во всех средах:

  1. В Program.cs зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

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

    В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

    • Маршрут /error-development в среде разработки.
    • Маршрут в средах, отличных от разработки /error .

  2. Добавьте действия контроллера для маршрутов разработки и не разработки:

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

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера с помощью настраиваемого исключения и фильтра действий:

  1. Создайте известный тип исключения с именем 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. Создайте фильтр действий с именем 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;
            }
        }
    }
    

    Предыдущий фильтр задает Order максимальное целочисленное значение минус 10. Это Order позволяет другим фильтрам выполняться в конце конвейера.

  3. В Program.cs добавьте фильтр действий в коллекцию фильтров:

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

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере фабрика по умолчанию заменяет реализацию, которая также поддерживает форматирование ответов в формате XML.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();

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. Для контроллеров веб-API MVC преобразует результат ошибки для создания ProblemDetails.

Ответ при ошибке можно настроить одним из следующих способов:

  1. Реализация ProblemDetailsFactory.
  2. Использование ApiBehaviorOptions.ClientErrorMapping

Реализуйте ProblemDetailsFactory

MVC создает все экземпляры ProblemDetails и ValidationProblemDetails с помощью Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory. Эта фабрика используется для:

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Program.cs:

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

Использование ApiBehaviorOptions.ClientErrorMapping

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails. Например, следующий код в Link обновляет свойство Program.cs для ответов 404:

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

Настраиваемое ПО промежуточного слоя для обработки исключений

Значения по умолчанию в ПО промежуточного слоя обработки исключений хорошо работают для большинства приложений. Для приложений, требующих специализированной обработки исключений, рекомендуется настроить ПО промежуточного слоя обработки исключений.

Создание полезных данных ProblemDetails для исключений

ASP.NET Core не создает стандартные полезные данные об ошибках при возникновении необработанного исключения. В сценариях, когда желательно вернуть стандартизованный ответ ProblemDetails клиенту, по промежуточному слоям ProblemDetails можно использовать для сопоставления исключений и 404 ответов на полезные данные ProblemDetails . По промежуточному ProblemDetails слоям обработки исключений также можно использовать для возврата полезных данных для необработанных исключений.

Дополнительные ресурсы

В этой статье описывается обработка и настройка обработки ошибок с помощью веб-API ASP.NET Core.

Просмотрите или скачайте пример кода (как скачивать)

Страница со сведениями об исключении для разработчика

Страница со сведениями об исключении для разработчика — это полезное средство, с помощью которого можно получить подробные трассировки стека для ошибок сервера. Он использует DeveloperExceptionPageMiddleware для записи синхронных и асинхронных исключений из конвейера HTTP и для создания ответов об ошибках. Для иллюстрации рассмотрим следующее действие контроллера:

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

Выполните следующую команду curl, чтобы проверить предыдущее действие:

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

Страница исключений разработчика отображает обычный текстовый ответ, если клиент не запрашивает выходные данные в формате HTML. Отображаются следующие результаты:

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

Чтобы вместо этого отображался отформатированный HTML-запрос, задайте для заголовка HTTP-запроса Accept тип носителя text/html. Например:

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

Рассмотрим следующий фрагмент 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;
}

Отформатированный HTML-ответ полезен при тестировании с помощью таких инструментов, как Postman. На следующем снимке экрана показаны ответы в виде обычного текста и в формате HTML в Postman:

Test the Developer Exception Page in Postman.

Предупреждение

Включать страницу исключений для разработчика следует только тогда, когда приложение выполняется в среде разработки. Не делитесь подробными сведениями об исключениях публично при запуске приложения в рабочей среде. Дополнительные сведения о настройке сред см. в статье Использование нескольких сред в ASP.NET Core.

Не следует помечать метод действия обработки ошибок атрибутами метода HTTP, например HttpGet. Из-за использования явных команд некоторые запросы могут не передаваться в метод действия. Разрешить анонимный доступ к методу, если пользователи, не прошедшие проверку подлинности, должны увидеть ошибку.

Обработчик исключений

В средах, не относящихся к разработке, для получения полезных данных об ошибках можно использовать ПО промежуточного слоя для обработки исключений.

  1. В Startup.Configure вызовите UseExceptionHandler, чтобы использовать ПО промежуточного слоя:

    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. Настройте действие контроллера для ответа на маршрут /error:

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

Предыдущее действие Error отправляет клиенту полезные данные, соответствующие RFC 7807.

ПО промежуточного слоя для обработки исключений также может предоставлять более подробные данные, согласованные с содержимым, в локальной среде разработки. Чтобы создать согласованный формат полезных данных в среде разработки и рабочей среде, сделайте следующее:

  1. В Startup.Configure зарегистрируйте экземпляры ПО промежуточного слоя обработки исключений для конкретной среды:

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

    В приведенном выше коде ПО промежуточного слоя регистрируется с помощью внедрения зависимостей.

    • Маршрут /error-local-development в среде разработки.
    • Маршрут /error в средах, не имеющих отношения к разработке.

  2. Примените маршрутизацию атрибутов к действиям контроллера:

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

    Предыдущий код вызывает ControllerBase.Problem для создания ProblemDetails ответа.

Изменение ответа с помощью исключений

Содержимое ответа можно изменить за пределами контроллера. В веб-API ASP.NET 4.x один из способов это сделать — использовать тип HttpResponseException. ASP.NET Core не содержит эквивалентный тип. Поддержку HttpResponseException можно добавить, сделав следующее:

  1. Создайте известный тип исключения с именем HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Создайте фильтр действий с именем 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;
            }
        }
    }
    

    Предыдущий фильтр задает Order максимальное целочисленное значение минус 10. Это Order позволяет другим фильтрам выполняться в конце конвейера.

  3. В Startup.ConfigureServices добавьте фильтр действий в коллекцию фильтров:

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

Ответ в случае ошибки при сбое проверки

В контроллерах веб-API MVC отвечает с помощью типа ответа ValidationProblemDetails при сбое проверки модели. MVC использует результаты InvalidModelStateResponseFactory для создания ответа в случае ошибки при сбое проверки. В следующем примере в Startup.ConfigureServices для изменения типа ответа по умолчанию на SerializableError используется фабрика:

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

Ответ при ошибке клиента

Результат ошибки определяется как результат с кодом состояния HTTP 400 или выше. В контроллерах веб-API платформа MVC преобразует результат ошибки в результат с ProblemDetails.

Ответ при ошибке можно настроить одним из следующих способов:

  1. Реализация ProblemDetailsFactory.
  2. Использование ApiBehaviorOptions.ClientErrorMapping.

Реализуйте ProblemDetailsFactory

MVC создает все экземпляры ProblemDetails и ValidationProblemDetails с помощью Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory. Эта фабрика используется для:

Чтобы настроить ответ с подробными сведениями о проблемах, зарегистрируйте пользовательскую реализацию ProblemDetailsFactory в Startup.ConfigureServices:

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

Использование ApiBehaviorOptions.ClientErrorMapping.

Используйте свойство ClientErrorMapping, чтобы настроить содержимое ответа ProblemDetails. Например, следующий код в type обновляет свойство Startup.ConfigureServices для ответов 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;
    });

Настраиваемое ПО промежуточного слоя для обработки исключений

Значения по умолчанию в ПО промежуточного слоя обработки исключений хорошо работают для большинства приложений. Для приложений, требующих специализированной обработки исключений, рекомендуется настроить ПО промежуточного слоя обработки исключений.

Создание полезных данных ProblemDetails для исключений

ASP.NET Core не создает стандартные полезные данные об ошибках при возникновении необработанного исключения. В сценариях, когда желательно вернуть стандартизованный ответ ProblemDetails клиенту, по промежуточному слоям ProblemDetails можно использовать для сопоставления исключений и 404 ответов на полезные данные ProblemDetails . По промежуточному ProblemDetails слоям обработки исключений также можно использовать для возврата полезных данных для необработанных исключений.