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

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

Автор: Том Дикстра (Tom Dykstra)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. См. также инструкции по обработке ошибок в веб-API ASP.NET Core и обработке ошибок в минимальных приложениях API.

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

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Приложения ASP.NET Core по умолчанию включают страницу со сведениями об исключении при выполнении всех следующих условий:

  • Выполнение в среде разработки.
  • Приложение создано с использованием текущих шаблонов, то есть с помощью WebApplication.CreateBuilder. Приложения, созданные с помощью WebHost.CreateDefaultBuilder, должны поддерживать страницу исключений разработчика путем вызова app.UseDeveloperExceptionPage и Configure.

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

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

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

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • файлы Cookie (при наличии);
  • Заголовки
  • Метаданные конечной точки, если таковые есть

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

Страница исключений разработчика с выбранными параметрами маршрутизации и метаданными конечной точки

Страница со сведениями об исключении для разработчика не гарантирует предоставление каких-либо сведений. Используйте Ведение журнала для получения полных сведений об ошибке.

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

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

  • Перехватывает и записывает в журнал необработанные исключения.
  • повторно выполняет запрос в альтернативном конвейере по указанному пути. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос, используя путь /Error.

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

  • По промежуточному слоям необходимо обрабатывать повторную обработку с тем же запросом. Обычно это означает очистку состояния после вызова _next или кэширования их обработки на ней HttpContext , чтобы избежать повторения. При работе с текстом запроса это означает буферизацию или кэширование результатов, таких как средство чтения форм.
  • Для перегрузки UseExceptionHandler(IApplicationBuilder, String) , используемой в шаблонах, изменяется только путь запроса, а данные маршрута очищаются. Запрос данных, таких как заголовки, метод и элементы, используются повторно.
  • Службы с областью действия остаются неизменными.

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

var app = builder.Build();

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

ПО промежуточного слоя обработки исключений повторно выполняет запрос, используя исходный метод HTTP. Если конечная точка обработчика ошибок ограничена определенным набором методов HTTP, она выполняется только для этих методов HTTP. Например, действие контроллера MVC, использующее атрибут [HttpGet], выполняется только для запросов GET. Чтобы гарантировать, что страницу пользовательской обработки ошибок будут достигать все запросы, не ограничивайте их определенным набором методов HTTP.

Для избирательного управления исключениями в зависимости от исходного метода HTTP:

  • Для Razor Pages создайте несколько методов обработчика. Например, используйте OnGet, чтобы обрабатывать исключения GET, и OnPost, чтобы обрабатывать исключения POST.
  • Для MVC примените атрибуты HTTP-команды к нескольким действиям. Например, используйте [HttpGet], чтобы обрабатывать исключения GET, и [HttpPost], чтобы обрабатывать исключения POST.

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

Доступ к исключению

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

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

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

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

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

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

IExceptionHandler

IExceptionHandler — это интерфейс, который предоставляет разработчику обратный вызов для обработки известных исключений в центральном расположении.

IExceptionHandler реализации регистрируются путем вызова IServiceCollection.AddExceptionHandler<T>. Время существования экземпляра IExceptionHandler — одноэлементное. Можно добавить несколько реализаций, и они вызываются в порядке регистрации.

Если обработчик исключений обрабатывает запрос, он может вернуться true к остановке обработки. Если исключение не обрабатывается обработчиком исключений, то элемент управления возвращается к поведению по умолчанию и параметрам из по промежуточного слоя. Для обработки и необработанных исключений создаются различные метрики и журналы.

В следующем примере показана IExceptionHandler реализация:

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

В следующем примере показано, как зарегистрировать реализацию IExceptionHandler для внедрения зависимостей:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

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

// Remaining Program.cs code omitted for brevity

При выполнении предыдущего кода в среде разработки:

  • Вызывается CustomExceptionHandler сначала для обработки исключения.
  • После ведения журнала исключения метод возвращаетсяfalse, TryHandleException поэтому отображается страница исключений разработчика.

В других средах:

  • Вызывается CustomExceptionHandler сначала для обработки исключения.
  • После ведения журнала исключения метод возвращаетсяfalse, TryHandleException поэтому /Error отображается страница.

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницу для кодов состояния ошибок HTTP, таких как код 404 Not Found (не найдено). Когда в приложении устанавливается код состояния ошибки HTTP 400–599 без текста, возвращается код состояния и пустой текст ответа. Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

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

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. При вызове метода UseStatusCodePages браузер вернет следующий ответ:

Status Code: 404; Not Found

UseStatusCodePages обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Примечание.

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

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

var app = builder.Build();

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

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

В предыдущем коде {0} является заполнителем для кода ошибки.

UseStatusCodePages со строкой формата обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePages с лямбда-выражением

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

var app = builder.Build();

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

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

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

UseStatusCodePages с лямбда-выражением обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиент в конечную точку обработки ошибок, указанную в шаблоне URL-адреса. Конечная точка обработки ошибок обычно отображает сведения об ошибке и возвращает код HTTP 200.
var app = builder.Build();

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

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

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в предыдущем коде. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. При указании конечной точки в приложении создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

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

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
  • Не изменяет код состояния до или после повторного выполнения конвейера.

Новое выполнение конвейера может изменить код состояния ответа, так как новый конвейер имеет полный контроль над кодом состояния. Если новый конвейер не изменяет код состояния, исходный код состояния будет отправлен клиенту.

var app = builder.Build();

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

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

Если указывается конечная точка в приложении, создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблон URL-адреса должен начинаться с символа / и может содержать заполнитель {0} для кода состояния. Чтобы передать код состояния в качестве параметра строки запроса, передайте второй аргумент в UseStatusCodePagesWithReExecute. Например:

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

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

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений также может создавать исключения. Рабочие страницы ошибок необходимо тщательно тестировать, чтобы они не создавали собственных исключений.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера также может обрабатывать некоторые исключения. Если сервер перехватывает исключение перед отправкой заголовков ответа, сервер отправляет 500 - Internal Server Error ответ без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

Фильтр исключений для страницы разработчика базы данных AddDatabaseDeveloperPageExceptionFilter перехватывает исключения, относящиеся к базе данных, которые могут быть устранены с помощью миграций Entity Framework Core. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эта страница включается только в среде разработки. В следующем коде добавляется фильтр исключений для страницы разработчика базы данных:

var builder = WebApplication.CreateBuilder(args);

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

Фильтры исключений

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

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Однако эти фильтры не так гибки, как встроенное ПО промежуточного слоя для обработки исключенийUseExceptionHandler. Мы рекомендуем использовать UseExceptionHandler, если ошибки не нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

Сведения о проблеме

Сведения о проблеме — это не единственный формат ответа, описывающий ошибку API HTTP, однако они часто используются для сообщения об ошибках для API HTTP.

Служба сведений о проблеме IProblemDetailsService реализует интерфейс, который поддерживает создание сведений о проблеме в ASP.NET Core. Метод AddProblemDetails расширения для IServiceCollection регистрации реализации по умолчанию IProblemDetailsService .

В приложениях ASP.NET Core следующий по промежуточному слоя создает ответы HTTP для получения сведений о проблемах при AddProblemDetails вызове, за исключением случаев, когда Accept заголовок HTTP запроса не включает один из типов контента, поддерживаемых зарегистрированным IProblemDetailsWriter (по умолчанию: application/json):

  • ExceptionHandlerMiddleware: создает ответ сведений о проблеме, когда настраиваемый обработчик не определен.
  • StatusCodePagesMiddleware: создает ответ сведений о проблеме по умолчанию.
  • DeveloperExceptionPageMiddleware: создает ответ сведений о проблеме в разработке, если Accept заголовок HTTP запроса не включает text/html.

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

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

В следующем разделе показано, как настроить текст ответа сведений о проблеме.

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

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

  1. Использование ProblemDetailsOptions.CustomizeProblemDetails
  2. Использование пользовательского IProblemDetailsWriter
  3. Вызов по промежуточного IProblemDetailsService слоя

CustomizeProblemDetails Операции

Сведения о созданной проблеме можно настроить с помощью CustomizeProblemDetails, и настройки применяются ко всем автоматически созданным сведениям о проблеме.

Следующий код используется ProblemDetailsOptions для задания CustomizeProblemDetails:

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Например, результат конечной HTTP Status 400 Bad Request точки создает следующий текст ответа сведений о проблеме:

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

Настраиваемый код IProblemDetailsWriter

Для IProblemDetailsWriter дополнительных настроек можно создать реализацию.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Примечание. При использовании пользовательского IProblemDetailsWriterпользователь IProblemDetailsWriter должен быть зарегистрирован перед вызовом AddRazorPages, AddControllersAddControllersWithViewsили AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

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

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

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

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

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

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

app.Run();

Сведения о проблеме из ПО промежуточного слоя

Альтернативный подход к использованию ProblemDetailsOptionsCustomizeProblemDetails заключается в настройке по промежуточного ProblemDetails слоя. Ответ сведений о проблеме можно записать путем вызова IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

В приведенном выше коде минимальные конечные точки /divide API и /squareroot возвращают ожидаемый пользовательский ответ на проблемы при вводе ошибок.

Конечные точки контроллера API возвращают ответ проблемы по умолчанию при вводе ошибок, а не в пользовательском ответе на проблему. Ответ на проблему по умолчанию возвращается, так как контроллер API записывается в поток ответа, сведения о проблеме для кодов состояния ошибки, прежде чем IProblemDetailsService.WriteAsync вызываться, и ответ не записывается снова.

ValuesController Ниже возвращается значениеBadRequestResult, которое записывается в поток отклика и, следовательно, не позволяет возвращать пользовательский ответ на проблему.

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

}

Values3Controller Ниже возвращается ControllerBase.Problem ожидаемый результат настраиваемой проблемы:

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

Рассмотрим следующее приложение:

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

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

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

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

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда позволяет получить доступ к ошибке и записать ответ сведений о проблеме с помощью IProblemDetailsService.WriteAsync:

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Альтернативный подход к созданию сведений о проблеме — использовать сторонний пакет NuGet Hellang.Middleware.ProblemDetails , который можно использовать для сопоставления исключений и ошибок клиента с сведениями о проблеме.

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

Автор: Том Дикстра (Tom Dykstra)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. См. также инструкции по обработке ошибок в веб-API ASP.NET Core и обработке ошибок в минимальных приложениях API.

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

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Приложения ASP.NET Core по умолчанию включают страницу со сведениями об исключении при выполнении всех следующих условий:

  • Выполнение в среде разработки.
  • Приложение создано с использованием текущих шаблонов, то есть с помощью WebApplication.CreateBuilder. Приложения, созданные с помощью WebHost.CreateDefaultBuilder, должны поддерживать страницу исключений разработчика путем вызова app.UseDeveloperExceptionPage и Configure.

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

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

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

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • файлы Cookie (при наличии);
  • Заголовки

Страница со сведениями об исключении для разработчика не гарантирует предоставление каких-либо сведений. Используйте Ведение журнала для получения полных сведений об ошибке.

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

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

  • Перехватывает и записывает в журнал необработанные исключения.
  • повторно выполняет запрос в альтернативном конвейере по указанному пути. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос, используя путь /Error.

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

  • По промежуточному слоям необходимо обрабатывать повторную обработку с тем же запросом. Обычно это означает очистку состояния после вызова _next или кэширования их обработки на ней HttpContext , чтобы избежать повторения. При работе с текстом запроса это означает буферизацию или кэширование результатов, таких как средство чтения форм.
  • Для перегрузки UseExceptionHandler(IApplicationBuilder, String) , используемой в шаблонах, изменяется только путь запроса, а данные маршрута очищаются. Запрос данных, таких как заголовки, метод и элементы, используются повторно.
  • Службы с областью действия остаются неизменными.

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

var app = builder.Build();

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

ПО промежуточного слоя обработки исключений повторно выполняет запрос, используя исходный метод HTTP. Если конечная точка обработчика ошибок ограничена определенным набором методов HTTP, она выполняется только для этих методов HTTP. Например, действие контроллера MVC, использующее атрибут [HttpGet], выполняется только для запросов GET. Чтобы гарантировать, что страницу пользовательской обработки ошибок будут достигать все запросы, не ограничивайте их определенным набором методов HTTP.

Для избирательного управления исключениями в зависимости от исходного метода HTTP:

  • Для Razor Pages создайте несколько методов обработчика. Например, используйте OnGet, чтобы обрабатывать исключения GET, и OnPost, чтобы обрабатывать исключения POST.
  • Для MVC примените атрибуты HTTP-команды к нескольким действиям. Например, используйте [HttpGet], чтобы обрабатывать исключения GET, и [HttpPost], чтобы обрабатывать исключения POST.

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

Доступ к исключению

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

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

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

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

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

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

IExceptionHandler

IExceptionHandler — это интерфейс, который предоставляет разработчику обратный вызов для обработки известных исключений в центральном расположении.

IExceptionHandler реализации регистрируются путем вызова IServiceCollection.AddExceptionHandler<T>. Время существования экземпляра IExceptionHandler — одноэлементное. Можно добавить несколько реализаций, и они вызываются в порядке регистрации.

Если обработчик исключений обрабатывает запрос, он может вернуться true к остановке обработки. Если исключение не обрабатывается обработчиком исключений, то элемент управления возвращается к поведению по умолчанию и параметрам из по промежуточного слоя. Для обработки и необработанных исключений создаются различные метрики и журналы.

В следующем примере показана IExceptionHandler реализация:

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

В следующем примере показано, как зарегистрировать реализацию IExceptionHandler для внедрения зависимостей:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

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

// Remaining Program.cs code omitted for brevity

При выполнении предыдущего кода в среде разработки:

  • Вызывается CustomExceptionHandler сначала для обработки исключения.
  • После ведения журнала исключения метод возвращаетсяfalse, TryHandleException поэтому отображается страница исключений разработчика.

В других средах:

  • Вызывается CustomExceptionHandler сначала для обработки исключения.
  • После ведения журнала исключения метод возвращаетсяfalse, TryHandleException поэтому /Error отображается страница.

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницу для кодов состояния ошибок HTTP, таких как код 404 Not Found (не найдено). Когда в приложении устанавливается код состояния ошибки HTTP 400–599 без текста, возвращается код состояния и пустой текст ответа. Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

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

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. При вызове метода UseStatusCodePages браузер вернет следующий ответ:

Status Code: 404; Not Found

UseStatusCodePages обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Примечание.

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

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

var app = builder.Build();

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

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

В предыдущем коде {0} является заполнителем для кода ошибки.

UseStatusCodePages со строкой формата обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePages с лямбда-выражением

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

var app = builder.Build();

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

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

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

UseStatusCodePages с лямбда-выражением обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиент в конечную точку обработки ошибок, указанную в шаблоне URL-адреса. Конечная точка обработки ошибок обычно отображает сведения об ошибке и возвращает код HTTP 200.
var app = builder.Build();

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

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

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в предыдущем коде. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. При указании конечной точки в приложении создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

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

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
  • Не изменяет код состояния до или после повторного выполнения конвейера.

Новое выполнение конвейера может изменить код состояния ответа, так как новый конвейер имеет полный контроль над кодом состояния. Если новый конвейер не изменяет код состояния, исходный код состояния будет отправлен клиенту.

var app = builder.Build();

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

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

Если указывается конечная точка в приложении, создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблон URL-адреса должен начинаться с символа / и может содержать заполнитель {0} для кода состояния. Чтобы передать код состояния в качестве параметра строки запроса, передайте второй аргумент в UseStatusCodePagesWithReExecute. Например:

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

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

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений также может создавать исключения. Рабочие страницы ошибок необходимо тщательно тестировать, чтобы они не создавали собственных исключений.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера также может обрабатывать некоторые исключения. Если сервер перехватывает исключение перед отправкой заголовков ответа, сервер отправляет 500 - Internal Server Error ответ без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

Фильтр исключений для страницы разработчика базы данных AddDatabaseDeveloperPageExceptionFilter перехватывает исключения, относящиеся к базе данных, которые могут быть устранены с помощью миграций Entity Framework Core. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эта страница включается только в среде разработки. В следующем коде добавляется фильтр исключений для страницы разработчика базы данных:

var builder = WebApplication.CreateBuilder(args);

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

Фильтры исключений

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

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Однако эти фильтры не так гибки, как встроенное ПО промежуточного слоя для обработки исключенийUseExceptionHandler. Мы рекомендуем использовать UseExceptionHandler, если ошибки не нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

Сведения о проблеме

Сведения о проблеме — это не единственный формат ответа, описывающий ошибку API HTTP, однако они часто используются для сообщения об ошибках для API HTTP.

Служба сведений о проблеме IProblemDetailsService реализует интерфейс, который поддерживает создание сведений о проблеме в ASP.NET Core. Метод AddProblemDetails расширения для IServiceCollection регистрации реализации по умолчанию IProblemDetailsService .

В приложениях ASP.NET Core следующий по промежуточному слоя создает ответы HTTP для получения сведений о проблемах при AddProblemDetails вызове, за исключением случаев, когда Accept заголовок HTTP запроса не включает один из типов контента, поддерживаемых зарегистрированным IProblemDetailsWriter (по умолчанию: application/json):

  • ExceptionHandlerMiddleware: создает ответ сведений о проблеме, когда настраиваемый обработчик не определен.
  • StatusCodePagesMiddleware: создает ответ сведений о проблеме по умолчанию.
  • DeveloperExceptionPageMiddleware: создает ответ сведений о проблеме в разработке, если Accept заголовок HTTP запроса не включает text/html.

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

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

В следующем разделе показано, как настроить текст ответа сведений о проблеме.

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

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

  1. Использование ProblemDetailsOptions.CustomizeProblemDetails
  2. Использование пользовательского IProblemDetailsWriter
  3. Вызов по промежуточного IProblemDetailsService слоя

CustomizeProblemDetails Операции

Сведения о созданной проблеме можно настроить с помощью CustomizeProblemDetails, и настройки применяются ко всем автоматически созданным сведениям о проблеме.

Следующий код используется ProblemDetailsOptions для задания CustomizeProblemDetails:

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Например, результат конечной HTTP Status 400 Bad Request точки создает следующий текст ответа сведений о проблеме:

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

Настраиваемый код IProblemDetailsWriter

Для IProblemDetailsWriter дополнительных настроек можно создать реализацию.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Примечание. При использовании пользовательского IProblemDetailsWriterпользователь IProblemDetailsWriter должен быть зарегистрирован перед вызовом AddRazorPages, AddControllersAddControllersWithViewsили AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

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

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

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

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

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

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

app.Run();

Сведения о проблеме из ПО промежуточного слоя

Альтернативный подход к использованию ProblemDetailsOptionsCustomizeProblemDetails заключается в настройке по промежуточного ProblemDetails слоя. Ответ сведений о проблеме можно записать путем вызова IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

В приведенном выше коде минимальные конечные точки /divide API и /squareroot возвращают ожидаемый пользовательский ответ на проблемы при вводе ошибок.

Конечные точки контроллера API возвращают ответ проблемы по умолчанию при вводе ошибок, а не в пользовательском ответе на проблему. Ответ на проблему по умолчанию возвращается, так как контроллер API записывается в поток ответа, сведения о проблеме для кодов состояния ошибки, прежде чем IProblemDetailsService.WriteAsync вызываться, и ответ не записывается снова.

ValuesController Ниже возвращается значениеBadRequestResult, которое записывается в поток отклика и, следовательно, не позволяет возвращать пользовательский ответ на проблему.

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

}

Values3Controller Ниже возвращается ControllerBase.Problem ожидаемый результат настраиваемой проблемы:

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

Рассмотрим следующее приложение:

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

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

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

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

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда позволяет получить доступ к ошибке и записать ответ сведений о проблеме с помощью IProblemDetailsService.WriteAsync:

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Альтернативный подход к созданию сведений о проблеме — использовать сторонний пакет NuGet Hellang.Middleware.ProblemDetails , который можно использовать для сопоставления исключений и ошибок клиента с сведениями о проблеме.

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

Автор: Том Дикстра (Tom Dykstra)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. См. также инструкции по обработке ошибок в веб-API ASP.NET Core и обработке ошибок в минимальных приложениях API.

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

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Приложения ASP.NET Core по умолчанию включают страницу со сведениями об исключении при выполнении всех следующих условий:

  • Выполнение в среде разработки.
  • Приложение создано с использованием текущих шаблонов, то есть с помощью WebApplication.CreateBuilder. Приложения, созданные с помощью WebHost.CreateDefaultBuilder, должны поддерживать страницу исключений разработчика путем вызова app.UseDeveloperExceptionPage и Configure.

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

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

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

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • файлы Cookie (при наличии);
  • Заголовки

Страница со сведениями об исключении для разработчика не гарантирует предоставление каких-либо сведений. Используйте Ведение журнала для получения полных сведений об ошибке.

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

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

  • Перехватывает и записывает в журнал необработанные исключения.
  • повторно выполняет запрос в альтернативном конвейере по указанному пути. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос, используя путь /Error.

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

  • По промежуточному слоям необходимо обрабатывать повторную обработку с тем же запросом. Обычно это означает очистку состояния после вызова _next или кэширования их обработки на ней HttpContext , чтобы избежать повторения. При работе с текстом запроса это означает буферизацию или кэширование результатов, таких как средство чтения форм.
  • Для перегрузки UseExceptionHandler(IApplicationBuilder, String) , используемой в шаблонах, изменяется только путь запроса, а данные маршрута очищаются. Запрос данных, таких как заголовки, метод и элементы, используются повторно.
  • Службы с областью действия остаются неизменными.

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

var app = builder.Build();

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

ПО промежуточного слоя обработки исключений повторно выполняет запрос, используя исходный метод HTTP. Если конечная точка обработчика ошибок ограничена определенным набором методов HTTP, она выполняется только для этих методов HTTP. Например, действие контроллера MVC, использующее атрибут [HttpGet], выполняется только для запросов GET. Чтобы гарантировать, что страницу пользовательской обработки ошибок будут достигать все запросы, не ограничивайте их определенным набором методов HTTP.

Для избирательного управления исключениями в зависимости от исходного метода HTTP:

  • Для Razor Pages создайте несколько методов обработчика. Например, используйте OnGet, чтобы обрабатывать исключения GET, и OnPost, чтобы обрабатывать исключения POST.
  • Для MVC примените атрибуты HTTP-команды к нескольким действиям. Например, используйте [HttpGet], чтобы обрабатывать исключения GET, и [HttpPost], чтобы обрабатывать исключения POST.

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

Доступ к исключению

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

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

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

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

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

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницу для кодов состояния ошибок HTTP, таких как код 404 Not Found (не найдено). Когда в приложении устанавливается код состояния ошибки HTTP 400–599 без текста, возвращается код состояния и пустой текст ответа. Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

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

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. При вызове метода UseStatusCodePages браузер вернет следующий ответ:

Status Code: 404; Not Found

UseStatusCodePages обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Примечание.

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

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

var app = builder.Build();

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

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

В предыдущем коде {0} является заполнителем для кода ошибки.

UseStatusCodePages со строкой формата обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePages с лямбда-выражением

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

var app = builder.Build();

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

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

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

UseStatusCodePages с лямбда-выражением обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиент в конечную точку обработки ошибок, указанную в шаблоне URL-адреса. Конечная точка обработки ошибок обычно отображает сведения об ошибке и возвращает код HTTP 200.
var app = builder.Build();

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

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

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в предыдущем коде. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. При указании конечной точки в приложении создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

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

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
  • Не изменяет код состояния до или после повторного выполнения конвейера.

Новое выполнение конвейера может изменить код состояния ответа, так как новый конвейер имеет полный контроль над кодом состояния. Если новый конвейер не изменяет код состояния, исходный код состояния будет отправлен клиенту.

var app = builder.Build();

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

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

Если указывается конечная точка в приложении, создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблон URL-адреса должен начинаться с символа / и может содержать заполнитель {0} для кода состояния. Чтобы передать код состояния в качестве параметра строки запроса, передайте второй аргумент в UseStatusCodePagesWithReExecute. Например:

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

Так как это ПО промежуточного слоя может повторно выполнить конвейер запросов:

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

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений также может создавать исключения. Рабочие страницы ошибок необходимо тщательно тестировать, чтобы они не создавали собственных исключений.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера также может обрабатывать некоторые исключения. Если сервер перехватывает исключение перед отправкой заголовков ответа, сервер отправляет 500 - Internal Server Error ответ без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

Фильтр исключений для страницы разработчика базы данных AddDatabaseDeveloperPageExceptionFilter перехватывает исключения, относящиеся к базе данных, которые могут быть устранены с помощью миграций Entity Framework Core. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эта страница включается только в среде разработки. В следующем коде добавляется фильтр исключений для страницы разработчика базы данных:

var builder = WebApplication.CreateBuilder(args);

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

Фильтры исключений

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

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Однако эти фильтры не так гибки, как встроенное ПО промежуточного слоя для обработки исключенийUseExceptionHandler. Мы рекомендуем использовать UseExceptionHandler, если ошибки не нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

Сведения о проблеме

Сведения о проблеме — это не единственный формат ответа, описывающий ошибку API HTTP, однако они часто используются для сообщения об ошибках для API HTTP.

Служба сведений о проблеме IProblemDetailsService реализует интерфейс, который поддерживает создание сведений о проблеме в ASP.NET Core. Метод AddProblemDetails расширения для IServiceCollection регистрации реализации по умолчанию IProblemDetailsService .

В приложениях ASP.NET Core следующий по промежуточному слоя создает ответы HTTP для получения сведений о проблемах при AddProblemDetails вызове, за исключением случаев, когда Accept заголовок HTTP запроса не включает один из типов контента, поддерживаемых зарегистрированным IProblemDetailsWriter (по умолчанию: application/json):

  • ExceptionHandlerMiddleware: создает ответ сведений о проблеме, когда настраиваемый обработчик не определен.
  • StatusCodePagesMiddleware: создает ответ сведений о проблеме по умолчанию.
  • DeveloperExceptionPageMiddleware: создает ответ сведений о проблеме в разработке, если Accept заголовок HTTP запроса не включает text/html.

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

builder.Services.AddProblemDetails();

var app = builder.Build();        

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

app.UseStatusCodePages();

В следующем разделе показано, как настроить текст ответа сведений о проблеме.

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

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

  1. Использование ProblemDetailsOptions.CustomizeProblemDetails
  2. Использование пользовательского IProblemDetailsWriter
  3. Вызов по промежуточного IProblemDetailsService слоя

CustomizeProblemDetails Операции

Сведения о созданной проблеме можно настроить с помощью CustomizeProblemDetails, и настройки применяются ко всем автоматически созданным сведениям о проблеме.

Следующий код используется ProblemDetailsOptions для задания CustomizeProblemDetails:

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

var app = builder.Build();        

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

app.UseStatusCodePages();

Например, результат конечной HTTP Status 400 Bad Request точки создает следующий текст ответа сведений о проблеме:

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

Настраиваемый код IProblemDetailsWriter

Для IProblemDetailsWriter дополнительных настроек можно создать реализацию.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Примечание. При использовании пользовательского IProblemDetailsWriterпользователь IProblemDetailsWriter должен быть зарегистрирован перед вызовом AddRazorPages, AddControllersAddControllersWithViewsили AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

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

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

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

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

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

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

app.Run();

Сведения о проблеме из ПО промежуточного слоя

Альтернативный подход к использованию ProblemDetailsOptionsCustomizeProblemDetails заключается в настройке по промежуточного ProblemDetails слоя. Ответ сведений о проблеме можно записать путем вызова IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

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

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

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

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

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

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

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

app.MapControllers();

app.Run();

В приведенном выше коде минимальные конечные точки /divide API и /squareroot возвращают ожидаемый пользовательский ответ на проблемы при вводе ошибок.

Конечные точки контроллера API возвращают ответ проблемы по умолчанию при вводе ошибок, а не в пользовательском ответе на проблему. Ответ на проблему по умолчанию возвращается, так как контроллер API записывается в поток ответа, сведения о проблеме для кодов состояния ошибки, прежде чем IProblemDetailsService.WriteAsync вызываться, и ответ не записывается снова.

ValuesController Ниже возвращается значениеBadRequestResult, которое записывается в поток отклика и, следовательно, не позволяет возвращать пользовательский ответ на проблему.

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

}

Values3Controller Ниже возвращается ControllerBase.Problem ожидаемый результат настраиваемой проблемы:

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

        return Ok(Numerator / Denominator);
    }

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

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

}

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

Рассмотрим следующее приложение:

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

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

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

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

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда позволяет получить доступ к ошибке и записать ответ сведений о проблеме с помощью IProblemDetailsService.WriteAsync:

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

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

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

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Альтернативный подход к созданию сведений о проблеме — использовать сторонний пакет NuGet Hellang.Middleware.ProblemDetails , который можно использовать для сопоставления исключений и ошибок клиента с сведениями о проблеме.

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

Автор: Том Дикстра (Tom Dykstra)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. Для веб-API см. статью Обработка ошибок в веб-API ASP.NET Core.

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

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Приложения ASP.NET Core по умолчанию включают страницу со сведениями об исключении при выполнении всех следующих условий:

  • Выполнение в среде разработки.
  • Приложение создано с использованием текущих шаблонов, то есть с помощью WebApplication.CreateBuilder. Приложения, созданные с помощью WebHost.CreateDefaultBuilder, должны поддерживать страницу исключений разработчика путем вызова app.UseDeveloperExceptionPage и Configure.

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

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

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

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • файлы Cookie (при наличии);
  • Заголовки

Страница со сведениями об исключении для разработчика не гарантирует предоставление каких-либо сведений. Используйте Ведение журнала для получения полных сведений об ошибке.

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

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

  • Перехватывает и записывает в журнал необработанные исключения.
  • повторно выполняет запрос в альтернативном конвейере по указанному пути. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос, используя путь /Error.

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

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

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

var app = builder.Build();

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

ПО промежуточного слоя обработки исключений повторно выполняет запрос, используя исходный метод HTTP. Если конечная точка обработчика ошибок ограничена определенным набором методов HTTP, она выполняется только для этих методов HTTP. Например, действие контроллера MVC, использующее атрибут [HttpGet], выполняется только для запросов GET. Чтобы гарантировать, что страницу пользовательской обработки ошибок будут достигать все запросы, не ограничивайте их определенным набором методов HTTP.

Для избирательного управления исключениями в зависимости от исходного метода HTTP:

  • Для Razor Pages создайте несколько методов обработчика. Например, используйте OnGet, чтобы обрабатывать исключения GET, и OnPost, чтобы обрабатывать исключения POST.
  • Для MVC примените атрибуты HTTP-команды к нескольким действиям. Например, используйте [HttpGet], чтобы обрабатывать исключения GET, и [HttpPost], чтобы обрабатывать исключения POST.

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

Доступ к исключению

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

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

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

    public string? ExceptionMessage { get; set; }

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

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

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

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

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницу для кодов состояния ошибок HTTP, таких как код 404 Not Found (не найдено). Когда в приложении устанавливается код состояния ошибки HTTP 400–599 без текста, возвращается код состояния и пустой текст ответа. Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в Program.cs:

var app = builder.Build();

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

app.UseStatusCodePages();

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

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. При вызове метода UseStatusCodePages браузер вернет следующий ответ:

Status Code: 404; Not Found

UseStatusCodePages обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Примечание.

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

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

var app = builder.Build();

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

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

В предыдущем коде {0} является заполнителем для кода ошибки.

UseStatusCodePages со строкой формата обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePages с лямбда-выражением

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

var app = builder.Build();

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

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

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

UseStatusCodePages с лямбда-выражением обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиент в конечную точку обработки ошибок, указанную в шаблоне URL-адреса. Конечная точка обработки ошибок обычно отображает сведения об ошибке и возвращает код HTTP 200.
var app = builder.Build();

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

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

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в предыдущем коде. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. При указании конечной точки в приложении создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

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

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Возвращает исходный код состояния клиенту.
  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
var app = builder.Build();

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

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

Если указывается конечная точка в приложении, создайте представление MVC или страницу Razor для конечной точки.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблон URL-адреса должен начинаться с символа / и может содержать заполнитель {0} для кода состояния. Чтобы передать код состояния в качестве параметра строки запроса, передайте второй аргумент в UseStatusCodePagesWithReExecute. Например:

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

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

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

    public string? OriginalPathAndQuery { get; set; }

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

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

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

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений также может создавать исключения. Рабочие страницы ошибок необходимо тщательно тестировать, чтобы они не создавали собственных исключений.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера также может обрабатывать некоторые исключения. Если сервер перехватывает исключение перед отправкой заголовков ответа, сервер отправляет 500 - Internal Server Error ответ без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

Фильтр исключений для страницы разработчика базы данных AddDatabaseDeveloperPageExceptionFilter перехватывает исключения, относящиеся к базе данных, которые могут быть устранены с помощью миграций Entity Framework Core. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эта страница включается только в среде разработки. В следующем коде добавляется фильтр исключений для страницы разработчика базы данных:

var builder = WebApplication.CreateBuilder(args);

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

Фильтры исключений

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

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Однако эти фильтры не так гибки, как встроенное ПО промежуточного слоя для обработки исключенийUseExceptionHandler. Мы рекомендуем использовать UseExceptionHandler, если ошибки не нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

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

Авторы: Кирк Ларкин (Kirk Larkin), Том Дикстра (Tom Dykstra) и Стив Смит (Steve Smith)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. Для веб-API см. статью Обработка ошибок в веб-API ASP.NET Core.

Просмотреть или скачать образец кода. (Как скачать.) Сетевая вкладка в средствах разработчика браузера F12 полезна при тестировании примера приложения.

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

Страница исключений для разработчика содержит подробные сведения о необработанных исключениях запросов. Шаблоны ASP.NET Core создают следующий код:

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

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

    app.UseRouting();

    app.UseAuthorization();

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

Выделенный выше код включает страницу исключений для разработчика при выполнении приложения в среде разработки.

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

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

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

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • Cookie (при наличии);
  • Заголовки

Страница со сведениями об исключении для разработчика не гарантирует предоставление каких-либо сведений. Используйте Ведение журнала для получения полных сведений об ошибке.

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

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

  • Перехватывает и записывает в журнал необработанные исключения.
  • повторно выполняет запрос в альтернативном конвейере по указанному пути. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос, используя путь /Error.

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

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

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

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

ПО промежуточного слоя обработки исключений повторно выполняет запрос, используя исходный метод HTTP. Если конечная точка обработчика ошибок ограничена определенным набором методов HTTP, она выполняется только для этих методов HTTP. Например, действие контроллера MVC, использующее атрибут [HttpGet], выполняется только для запросов GET. Чтобы гарантировать, что страницу пользовательской обработки ошибок будут достигать все запросы, не ограничивайте их определенным набором методов HTTP.

Для избирательного управления исключениями в зависимости от исходного метода HTTP:

  • Для Razor Pages создайте несколько методов обработчика. Например, используйте OnGet, чтобы обрабатывать исключения GET, и OnPost, чтобы обрабатывать исключения POST.
  • Для MVC примените атрибуты HTTP-команды к нескольким действиям. Например, используйте [HttpGet], чтобы обрабатывать исключения GET, и [HttpPost], чтобы обрабатывать исключения POST.

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

Доступ к исключению

Используйте интерфейс IExceptionHandlerPathFeature, чтобы получить доступ к исключению и к пути исходного запроса в обработчике ошибок. Следующий код добавляется ExceptionMessage к умолчанию Pages/Error.cshtml.cs , созданному шаблонами ASP.NET Core:

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

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Чтобы протестировать исключение в примере приложения, выполните указанные ниже действия.

  • Настройте среду как рабочую.
  • Удалите комментарии из webBuilder.UseStartup<Startup>(); в Program.cs.
  • На домашней странице выберите Вызвать исключение.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

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

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;;
                context.Response.ContentType = "text/html";

                await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
                await context.Response.WriteAsync("ERROR!<br><br>\r\n");

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

                if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
                {
                    await context.Response.WriteAsync(
                                              "File error thrown!<br><br>\r\n");
                }

                await context.Response.WriteAsync(
                                              "<a href=\"/\">Home</a><br>\r\n");
                await context.Response.WriteAsync("</body></html>\r\n");
                await context.Response.WriteAsync(new string(' ', 512)); 
            });
        });
        app.UseHsts();
    }

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

    app.UseRouting();

    app.UseAuthorization();

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках из IExceptionHandlerFeature или IExceptionHandlerPathFeature. Сохранение ошибок создает риски для безопасности.

Чтобы протестировать лямбда-выражение для обработки исключений в примере приложения, выполните указанные ниже действия.

  • Настройте среду как рабочую.
  • Удалите комментарии из webBuilder.UseStartup<StartupLambda>(); в Program.cs.
  • На домашней странице выберите Вызвать исключение.

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницу для кодов состояния ошибок HTTP, таких как код 404 Not Found (не найдено). Когда в приложении устанавливается код состояния ошибки HTTP 400–599 без текста, возвращается код состояния и пустой текст ответа. Чтобы указать коды состояния, используйте ПО промежуточного слоя страниц с кодами состояния. Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в методе Startup.Configure.

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

    app.UseStatusCodePages();

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

    app.UseRouting();

    app.UseAuthorization();

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

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

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. Например, можно перейти по следующему адресу: Home/Privacy2. В этом случае при вызове UseStatusCodePages браузер вернет следующее сообщение:

Status Code: 404; Not Found

UseStatusCodePages обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

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

  • Настройте среду как рабочую.
  • Удалите комментарии из webBuilder.UseStartup<StartupUseStatusCodePages>(); в Program.cs.
  • Выберите ссылки на домашней странице.

Примечание.

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

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

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

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

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

    app.UseRouting();

    app.UseAuthorization();

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

В предыдущем коде {0} является заполнителем для кода ошибки.

UseStatusCodePages со строкой формата обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Чтобы протестировать UseStatusCodePages в примере приложения, удалите комментарии из webBuilder.UseStartup<StartupFormat>(); в Program.cs.

UseStatusCodePages с лямбда-выражением

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

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

    app.UseStatusCodePages(async context =>
    {
        context.HttpContext.Response.ContentType = "text/plain";

        await context.HttpContext.Response.WriteAsync(
            "Status code page, status code: " +
            context.HttpContext.Response.StatusCode);
    });

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

    app.UseRouting();

    app.UseAuthorization();

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

UseStatusCodePages с лямбда-выражением обычно не применяется в рабочей среде, так как возвращает сообщение, бесполезное для пользователей.

Чтобы протестировать UseStatusCodePages в примере приложения, удалите комментарии из webBuilder.UseStartup<StartupStatusLambda>(); в Program.cs.

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиент в конечную точку обработки ошибок, указанную в шаблоне URL-адреса. Конечная точка обработки ошибок обычно отображает сведения об ошибке и возвращает код HTTP 200.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");

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

    app.UseRouting();

    app.UseAuthorization();

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

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в предыдущем коде. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. При указании конечной точки в приложении создайте представление MVC или страницу Razor для конечной точки. Пример Razor Pages доступен в примере приложения в файле Pages/MyStatusCode.cshtml.

Этот метод обычно используется, если приложение:

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

Чтобы протестировать UseStatusCodePages в примере приложения, удалите комментарии из webBuilder.UseStartup<StartupSCredirect>(); в Program.cs.

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Возвращает исходный код состояния клиенту.
  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithReExecute("/MyStatusCode2", "?code={0}");

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

    app.UseRouting();

    app.UseAuthorization();

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

Если указывается конечная точка в приложении, создайте представление MVC или страницу Razor для конечной точки. Обязательно поместите UseStatusCodePagesWithReExecute перед UseRouting, чтобы запрос можно было перенаправить на страницу состояния. Пример Razor Pages доступен в примере приложения в файле Pages/MyStatusCode2.cshtml.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблоны URL-адреса и строки запроса могут содержать заполнитель {0} для кода состояния. Шаблон URL-адреса должен начинаться с символа /.

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

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

    public string ErrorStatusCode { get; set; }

    public string OriginalURL { get; set; }
    public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);

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

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

Пример Razor Pages доступен в примере приложения в файле Pages/MyStatusCode2.cshtml.

Чтобы протестировать UseStatusCodePages в примере приложения, удалите комментарии из webBuilder.UseStartup<StartupSCreX>(); в Program.cs.

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений также может создавать исключения. Рабочие страницы ошибок необходимо тщательно тестировать, чтобы они не создавали собственных исключений.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера также может обрабатывать некоторые исключения. Если сервер перехватывает исключение перед отправкой заголовков ответа, сервер отправляет 500 - Internal Server Error ответ без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

Фильтр исключений для страницы разработчика базы данных AddDatabaseDeveloperPageExceptionFilter перехватывает исключения, относящиеся к базе данных, которые могут быть устранены с помощью миграций Entity Framework Core. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эта страница включается только в среде разработки. Следующий код был создан шаблонами страниц Razor в ASP.NET Core при указании отдельных учетных записей пользователей:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Фильтры исключений

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

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Однако эти фильтры не так гибки, как встроенное ПО промежуточного слоя для обработки исключенийUseExceptionHandler. Мы рекомендуем использовать UseExceptionHandler, если ошибки не нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

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

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

    app.UseRouting();

    app.UseAuthorization();

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

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

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

Авторы: Том Дикстра (Tom Dykstra) и Стив Смит (Steve Smith)

В этой статье рассматриваются основные методы обработки ошибок в веб-приложениях ASP.NET Core. Для веб-API см. статью Обработка ошибок в веб-API ASP.NET Core.

Просмотреть или скачать образец кода. См. раздел Загрузка примера.

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

Страница исключений для разработчика содержит подробные сведения об исключениях запросов. Шаблоны ASP.NET Core создают следующий код:

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

Приведенный выше код включает страницу исключений для разработчика при выполнении приложения в среде разработки.

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

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

Страница исключений для разработчика содержит следующие сведения об исключении и запросе:

  • Трассировка стека
  • параметры строки запроса (при наличии);
  • Cookie (при наличии);
  • Заголовки

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

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

  • Перехватывает и записывает в журнал исключения.
  • Повторно выполняет запрос в альтернативном конвейере для указанной страницы или контроллера. Запрос не выполняется повторно, если запущен отклик. Созданный шаблоном код повторно выполняет запрос к /Error.

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

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

Шаблон приложения Razor Pages предоставляет страницу ошибки (.cshtml) и класс PageModel (ErrorModel) в папке Pages. Для приложения MVC шаблон проекта содержит метод действия Error и представление ошибок для контроллера Home.

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

Доступ к исключению

Используйте интерфейс IExceptionHandlerPathFeature, чтобы получить доступ к исключению и к пути исходного запроса в контроллере или на странице обработчика ошибок:

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

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

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

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

Не передавайте клиентам конфиденциальную информацию об ошибках. Сохранение ошибок создает риски для безопасности.

Чтобы активировать предыдущую страницу обработки исключений, задайте рабочую среду и принудительно вызовите исключение.

Лямбда-функция для обработчика исключений

Альтернативой пользовательской странице обработчика исключений является предоставление лямбда-функции для UseExceptionHandler. Использование лямбда-функции позволяет получить доступ к ошибке до возврата ответа.

Ниже приведен пример использования лямбда-функции для обработки исключений:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
   app.UseExceptionHandler(errorApp =>
   {
        errorApp.Run(async context =>
        {
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            context.Response.ContentType = "text/html";

            await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
            await context.Response.WriteAsync("ERROR!<br><br>\r\n");

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

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
            }

            await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
            await context.Response.WriteAsync("</body></html>\r\n");
            await context.Response.WriteAsync(new string(' ', 512)); // IE padding
        });
    });
    app.UseHsts();
}

В приведенном выше коде добавляется await context.Response.WriteAsync(new string(' ', 512));, поэтому браузер Internet Explorer отображает сообщение об ошибке, а не сообщение об ошибке IE. Дополнительные сведения см. здесь на GitHub.

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

Не передавайте клиентам конфиденциальную информацию об ошибках из IExceptionHandlerFeature или IExceptionHandlerPathFeature. Сохранение ошибок создает риски для безопасности.

Чтобы просмотреть результат обработки ошибок лямбда-функцией в примере приложения, используйте директивы препроцессора ProdEnvironment и ErrorHandlerLambda, а на домашней странице выберите Trigger an exception (Вызывать исключение).

UseStatusCodePages

По умолчанию приложение ASP.NET Core не предоставляет страницы для кодов состояния HTTP, таких как код 404 Not Found (не найдено). Приложение возвращает код состояния без текста ответа. Чтобы указать коды состояния, используйте ПО промежуточного слоя страниц с кодами состояния.

Это ПО промежуточного слоя доступно в пакете Microsoft.AspNetCore.Diagnostics.

Чтобы включить обработчики по умолчанию, возвращающие только текст для распространенных кодов состояния ошибки, вызовите UseStatusCodePages в методе Startup.Configure.

app.UseStatusCodePages();

Вызовите UseStatusCodePages до ПО промежуточного слоя для обработки запросов (например ПО промежуточного слоя для статических файлов и ПО промежуточного слоя MVC).

Если UseStatusCodePages не используется, при переходе по URL-адресу без конечной точки возвращается зависящее от браузера сообщение об ошибке, в котором указывается, что конечная точка не найдена. Например, можно перейти по следующему адресу: Home/Privacy2. В этом случае при вызове UseStatusCodePages браузер вернет следующее сообщение:

Status Code: 404; Not Found

UseStatusCodePages со строкой формата

Чтобы настроить тип содержимого и указать текст ответа, используйте перегрузку UseStatusCodePages, которая принимает тип содержимого и строку формата.

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

UseStatusCodePages с лямбда-выражением

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

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";

    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

UseStatusCodePagesWithRedirects

Метод расширения UseStatusCodePagesWithRedirects:

  • Отправляет клиенту код состояния 302 — Found.
  • Перенаправляет клиента к расположению, предоставленному в шаблоне URL-адреса.
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

Шаблон URL-адреса может содержать заполнитель {0} для кода состояния, как показано в примере. Если шаблон URL-адреса начинается с символа ~ (тильды), ~ заменяется PathBase приложения. Если вы указываете на конечную точку в приложении, создайте представление MVC или страницу Razor для конечной точки. Razor Пример Pages смPages/StatusCode.cshtml. в примере приложения.

Этот метод обычно используется, если приложение:

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

UseStatusCodePagesWithReExecute

Метод расширения UseStatusCodePagesWithReExecute:

  • Возвращает исходный код состояния клиенту.
  • Позволяет создать текст ответа путем повторного выполнения конвейера запросов с использованием другого пути.
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");

Если вы указываете на конечную точку в приложении, создайте представление MVC или страницу Razor для конечной точки. Обязательно поместите UseStatusCodePagesWithReExecute перед UseRouting, чтобы запрос можно было перенаправить на страницу состояния. Razor Пример Pages смPages/StatusCode.cshtml. в примере приложения.

Этот метод обычно используется, если приложение:

  • Обрабатывает запрос без перенаправления к другой конечной точке. Для веб-приложений в адресной строке браузера клиента отображается изначально запрошенная конечная точка.
  • Сохраняет и возвращает исходный код состояния с ответом.

Шаблоны URL-адреса и строки запроса могут содержать заполнитель ({0}) для кода состояния. Шаблон URL-адреса должен начинаться с косой черты (/). При использовании заполнителя в пути убедитесь, что конечная точка (страница или контроллер) могут обрабатывать сегмент пути. Например, страница Razor для ошибок должна принимать необязательное значение сегмента с директивой @page.

@page "{code?}"

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

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

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

Отключение страниц с кодами состояния

Чтобы отключить страницы кодов состояния для метода контроллера или действия MVC, используйте атрибут [SkipStatusCodePages].

Чтобы отключить страницы кодов состояния для конкретных запросов в методе обработчика Razor Pages или в контроллере MVC, используйте IStatusCodePagesFeature.

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

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

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

Код на страницах обработки исключений может создавать исключения. Часто желательно, чтобы страницы ошибок в рабочей среде содержали только статическое содержимое.

Заголовки ответа

После отправки заголовков для ответа происходит следующее:

  • Приложение не может изменить код состояния ответа.
  • Нельзя запустить страницу или обработчик исключений. Необходимо завершить ответ или прервать подключение.

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

Помимо логики обработки исключений в приложении, реализация HTTP-сервера может выполнять ряд операций по обработке исключений. Если сервер перехватывает исключение перед отправкой заголовков ответа, он отсылает ответ 500 Internal Server Error (внутренняя ошибка сервера) без текста ответа. Если сервер перехватывает исключение после отправки заголовков ответа, он закрывает соединение. Запросы, не обработанные приложением, обрабатываются сервером. Все исключения, возникшие при обработке запроса серверов, обрабатываются с помощью механизма обработки исключений на сервере. Пользовательские страницы ошибок приложений, ПО промежуточного слоя для обработки исключений и фильтры не влияют на этот процесс.

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

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

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

  • На уровне размещения в журнале фиксируется критическое исключение.
  • Выполняется аварийное завершение процесса dotnet.
  • Если приложение запущено на HTTP-сервере Kestrel, страница со сведениями об ошибке не отображается.

При работе в службах IIS (или Службе приложений Azure) либо IIS Expressмодуль ASP.NET Core возвращает ошибку 502.5 Process Failure (ошибка процесса), если процесс невозможно запустить. Дополнительные сведения см. в статье Устранение неполадок с ASP.NET Core в Службе приложений Azure и IIS.

Страница ошибок базы данных

ПО промежуточного слоя для отображения страницы ошибок базы данных перехватывает исключения, относящиеся к базе данных, которые могут быть устранены при помощи миграций Entity Framework. При возникновении этих исключений формируется HTML-ответ с подробными сведениями о возможных действиях для устранения проблемы. Эту страницу следует включать только в среде разработки. Включите страницу, добавив код в Startup.Configure.

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

Для UseDatabaseErrorPage необходим пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Фильтры исключений

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

Совет

Фильтры исключений полезны при перехвате исключений, которые возникают в действиях MVC. Но эти фильтры не так гибки, как ПО промежуточного слоя для обработки исключений. Мы рекомендуем использовать ПО промежуточного слоя. Используйте фильтры, только если ошибки нужно обрабатывать по-разному в зависимости от выбранного действия MVC.

Ошибки состояния модели

Сведения о том, как обрабатывать ошибки состояния модели, см. в статьях о привязке модели и проверке модели.

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