Обработка ошибок

Steve Smith

Когда в вашем ASP.NET приложении возникают ошибки, вы можете обработать их различными способами, и мы поговорим о них в этой статье.

Просмотрите или скачайте код

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

Поток для каждого запроса настраивается в методе Configure() класса Startup. Вы можете легко добавить простую страницу исключений, предназначенную только для разработки. Все, что требуется, - это добавить в проект зависимость для Microsoft.AspNetCore.Diagnostics, а затем добавить одну строку кода в метод Configure() класса Startup.cs:

public void Configure(IApplicationBuilder app, 
    IHostingEnvironment env)
{
    app.UseIISPlatformHandler();

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

В данный код перед вызовом UseDeveloperExceptionPage включена проверка на предмет того, является ли средой “development”. Это хорошая практика, поскольку обычно никто не хочет делиться со всеми детальной информацией об исключениях в производственной версии.

В нашем примере есть простой механизм для создания исключения:

public static void HomePage(IApplicationBuilder app)
{
    app.Run(async (context) =>
    {
        if (context.Request.Query.ContainsKey("throw"))
        {
            throw new Exception("Exception triggered!");
        }
        var builder = new StringBuilder();
        builder.AppendLine("<html><body>Hello World!");
        builder.AppendLine("<ul>");
        builder.AppendLine("<li><a href=\"/?throw=true\">Throw Exception</a></li>");
        builder.AppendLine("<li><a href=\"/missingpage\">Missing Page</a></li>");
        builder.AppendLine("</ul>");
        builder.AppendLine("</body></html>");

        context.Response.ContentType = "text/html";
        await context.Response.WriteAsync(builder.ToString());
    });
}

Если запрос включает в себя не пустой параметр строки запроса для переменной throw, будет выброшено исключение. Если среда установлена на Development, от отобразится страница исключений:

../_images/developer-exception-page.png

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

app.UseExceptionHandler("/Error");

Для действий, связанных с финальной стадией процесса обработки, не передавайте IActionResult атрибуты HTTP метода, например HttpGet, напрямую, поскольку в таком случае некоторые запросы даже не достигнут метода.

[Route("/Error")]
public IActionResult Index()
{
    // Handle error here
}

Использование страницы исключений для разработки

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

../_images/developer-exception-page.png

Следующая таблица представляет параметры строки запроса, если таковые есть:

../_images/developer-exception-page-query.png

В данном случае вы можете увидеть значение параметра throw, который был передан этому запросу. У этого запроса нет куки, но если бы были, они бы появились в таблице Cookies. В поледней таблице можно увидеть переданные заголовки:

../_images/developer-exception-page-headers.png

Настройка страниц с кодом статуса

По умолчанию в вашем приложении не будет полных страниц с кодом статуса для кодов статуса HTTP, например, 500 (Internal Server Error) или 404 (Not Found). Вы можете настроить StatusCodePagesMiddleware, добавив эту строку в метод Configure:

app.UseStatusCodePages();

По умолчанию это связующее ПО добавляет очень простые текстовые обработчики для общих кодов статуса. Например, вот результат 404 Not Found:

../_images/default-404-status-code.png

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

app.UseStatusCodePages(context =>
  context.HttpContext.Response.SendAsync("Handler, status code: " +
  context.HttpContext.Response.StatusCode, "text/plain"));

Как вариант вы можете просто передать контекстный тип и строку формата:

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

Связующее ПО может обрабатывать перенаправления (с относительными и абсолютными URL путями), передавая код статуса как часть URL:

app.UseStatusCodePagesWithRedirects("~/errors/{0}");

В данном примере в браузере отобразится статус 302 / Found, а затем произойдет перенаправление.

Связующее ПО может заново выполнить запрос из новой строки формата:

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

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

Если вы хотите отключить страницы кода статуса для конкретных запросов, используйте следующий код:

var statusCodePagesFeature = context.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
  statusCodePagesFeature.Enabled = false;
}

Ограничения в обработке исключений во время взаимодействия с клиентом

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

  1. Как только были отправлены заголовки ответа, вы не можете изменить статусный код ответа, а также запустить страницы исключений или обработчики. Либо должен быть завершен ответ, либо происходит обрыв связи.
  2. Если со стороны клиента произошел разрыв связи на середине ответа, вы не можете отправить ему остальную часть ответа.
  3. Не забывайте, что на страницах обработки исключений также могут быть исключения. Лучше всего, чтобы страницы ошибок для производственной версии приложения состояли из чисто статического контекста.

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

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

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

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

Обработку исключений, которая происходит во время запуска приложения, можно провести только на уровне хостинга. Исключения, которые появляются во время запуска приложения, также могут влиять на поведение сервера. Например, чтобы включить SSL в Kestrel, нужно настроить сервер с помощью KestrelServerOptions.UseHttps(). Если исключение возникнет до этой строки в Startup, тогда по умолчанию хостинг словит исключение, запустит сервер и отобразит страницу с ошибкой для не-SSL порта. Если исключение возникнет после этой строки кода, тогда страница с ошибкой будет обрабатываться через HTTPS.

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

У MVC приложений есть дополнительные опции для обработки ошибок, например, фильтры для обработки ошибок конфигурации и валидация состояния модели.

Фильтры

Фильтры исключений можно настроить глобально или для каждого контроллера или действия в MVC приложении. Такие фильтры обрабатывают только те исключения, которые случаются во время выполнения действия контроллера, иначе они не вызываются. См. filters.

Совет

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

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

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

Некоторые приложения следуют стандартному соглашению при обработке ошибок валидации, и в таком случае для реализации этой политики отлично подходит фильтр. Вам стоит протестировать поведение методов действий с помощью валидных и невалидных состояний модели (см. тестирование логики контроллера).

Поделись хорошей новостью с друзьями!
Следи за новостями!