Обработка нескольких ошибок

70

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

В ASP.NET Framework также поддерживается другая модель, при которой запрос может сталкиваться с несколькими ошибками, о которых можно сообщить, не останавливая обработку запроса. Это позволяет предоставлять пользователю больше нюансов и полезной информации.

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

Функциональность, на которую опирается этот подход, определена в классе HttpContext и кратко описана в таблице ниже:

Свойства и методы класса HttpContext, имеющие отношение к обработке ошибок
Имя Описание
AddError(error)

Записывает для текущего запроса ошибку, выраженную в виде объекта Exception

AllErrors

Возвращает массив объектов Exception, каждый из которых представляет ошибку. Если ошибки отсутствуют, возвращается null

ClearError()

Очищает все ошибки, записанные для текущего запроса

Error

Возвращает объект Exception, представляющий первую ошибку, которая была записана, или null, если ошибок нет

Свойство Error и метод ClearError() уже использовались в предыдущих примерах. Сейчас нас больше всего интересуют метод AddError() и свойство AllErrors, которые предлагают поддержку нескольких ошибок.

Отчет об ошибках

В примере ниже приведен модифицированный файл отделенного кода SumControl.ascx.cs, в котором записывается несколько ошибок в случае возникновения проблем при обработке данных формы, отправленной пользователем.

using System;

namespace ErrorHandling
{
    public partial class SumControl : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (IsPostBack)
            {
                int? first = GetIntValue("first");
                int? second = GetIntValue("second");

                if (first.HasValue && second.HasValue)
                {
                    result.InnerText = (first.Value + second.Value).ToString();
                    resultPlaceholder.Visible = true;
                }
                else
                {
                    Context.AddError(new Exception("Невозможно выполнить расчет"));
                }
            }
        }

        private int? GetIntValue(string name)
        {
            int value;
            if (Request[name] == null)
            {
                Context.AddError(new ArgumentNullException(name));
                return null;
            }
            else if (!int.TryParse(Request[name], out value))
            {
                Context.AddError(new ArgumentOutOfRangeException(name));
                return null;
            }
            return value;
        }
    }
}

При обработке значений, введенных пользователем, мы вызываем метод HttpContext.AddError() для каждой возникающей проблемы. Мы будем записывать максимум три ошибки, если пользователь ввел значения, преобразовать которые не удалось - по одной ошибке для каждого значения и еще одну, связанную с невозможностью выполнения вычислений.

В этом примере данным, которые отправил пользователь, уделяется большее внимание, но все еще недостаточное для реального проекта. Заметим только, что пользователь вполне мог бы предоставить вещественное число.

Отображение ошибок

Нам необходим способ отображения отдельных ошибок, поэтому мы добавляем в проект новую веб-форму по имени MultipleErrors.aspx. Содержимое файла веб-формы показано в примере ниже:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultipleErrors.aspx.cs" 
    Inherits="ErrorHandling.MultipleErrors" %>

<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <h1>Извините</h1>
    <p>В приложении возникли следующие проблемы:</p>
    <p>
        <asp:Repeater ID="Repeater1" ItemType="System.String" SelectMethod="GetErrorMessages" runat="server">
            <HeaderTemplate>
                <ul>
            </HeaderTemplate>
            <ItemTemplate>
                <li><%# Item %></li>        
            </ItemTemplate>
            <FooterTemplate>
                </ul>
            </FooterTemplate>
        </asp:Repeater>
    </p>
    <p><a href="Default.aspx">Перейти на главную?</a></p>    
</body>
</html>

Эта веб-форма содержит элемент управления Repeater, который применяется для отображения детальных сведений о каждой записанной ошибке. Такие детальные сведения получаются посредством метода GetErrorMessages() класса отделенного кода, определение которого можно видеть в примере ниже, где приведено содержимое файла отделенного кода MultipleErrors.aspx.cs:

using System.Collections.Generic;
using System.Linq;

namespace ErrorHandling
{
    public partial class MultipleErrors : System.Web.UI.Page
    {
        public IEnumerable<string> GetErrorMessages()
        {
            return Context.AllErrors.Select(e => e.Message);
        }
    }
}

В методе используется LINQ для возврата значений перечисления из свойства Message каждого объекта Exception, доступного через свойство HttpContext.AllErrors.

Перехват ошибок

Вызов метода HttpContext.AddError() для записи ошибки не инициирует событие Error класса Page или HttpApplication. Это означает, что в глобальном классе приложения необходимо реализовать обработчик события EndRequest, внутри которого инспектировать результат обращения к свойству HttpContext.AllErrors с целью выяснения, есть ли ошибки для отображения. В примере ниже показано, как это делается:

using System;

namespace ErrorHandling
{
    // ...

    public class Global : System.Web.HttpApplication
    {
        protected void Application_EndRequest(object sender, EventArgs e)
        {
            if (Context.AllErrors != null && Context.AllErrors.Length > 1)
            {
                Response.ClearHeaders();
                Response.ClearContent();
                Response.StatusCode = 200;
                Server.Execute("/MultipleErrors.aspx");
                Context.ClearError();
            }
        }

        // ...
    }
}

Ответ генерируется с применением веб-формы MultipleErrors.aspx на основе значения, возвращаемого свойством HttpContext.AllErrors. Мы удостоверяемся в наличии более одной ошибки, т.к. необработанное исключение также будет возвращаться свойством AllErrors. Если существует только одна ошибка, мы не хотим прерывать нормальный процесс обработки, хотя в реальном проекте вы наверняка предпримете более единообразный подход.

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

Отображение нескольких сообщений об ошибках

Обратите внимание на вызов метода HttpContext.ClearError () после генерации страницы с множеством ошибок. Вызов метода AddError() не инициирует событие Error, но запускает встроенную обработку ошибок ASP.NET, которая была сконфигурирована в файле Web.config в предыдущих статьях. Если не вызвать ClearError(), страница с множеством ошибок будет заменена тем, что указано в Web.config.

Использование модуля для обработки ошибок

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

Удаление существующего кода обработки ошибок

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

using System;

namespace ErrorHandling
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_EndRequest(object sender, EventArgs e)
        { }

        protected void Application_Error(object sender, EventArgs e)
        { }
    }
}

Определение модуля

Мы добавили в проект новый файл класса по имени ErrorModule.cs с определением модуля, показанным в примере ниже:

using System.Web;

namespace ErrorHandling {
    public class ErrorModule : IHttpModule {

        public void Init(HttpApplication app) {
            app.Error += (src, args) => HandleRequest(app);
            app.EndRequest += (src, args) => HandleRequest(app);
        }

        private void HandleRequest(HttpApplication app) {
            if (app.Context.AllErrors != null) {
                app.Response.ClearHeaders();
                app.Response.ClearContent();
                app.Response.StatusCode = 200;
                app.Server.Execute("/MultipleErrors.aspx");
                app.Context.ClearError();
            }
        }

        public void Dispose() {
            // Освобождать нечего
        }
    }
}

Этот модуль не содержит новой функциональности. Мы обрабатываем события Error и EndRequest для перехвата одной и нескольких ошибок и вызываем метод HttpServerUtility.Execute() для визуализации сообщения об ошибке с применением веб-формы MultipleErrors.aspx. Наконец, необходимо зарегистрировать модуль в файле Web.config, как демонстрируется в примере ниже:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>

  <system.webServer>
    ...
    <modules>
      <add name="ErrorLog" type="ErrorHandling.ErrorModule"/>
    </modules>
  </system.webServer>
</configuration>
Пройди тесты
Лучший чат для C# программистов