Обработка исключений уровней BLL и DAL на странице ASP.NET(C#)

Скотт Митчелл

Загрузить PDF-файл

В этом руководстве мы посмотрим, как отобразить понятное информативное сообщение об ошибке при возникновении исключения во время операции вставки, обновления или удаления веб-элемента управления ASP.NET данных.

Введение

Работа с данными из веб-приложения ASP.NET с использованием многоуровневой архитектуры приложений состоит из следующих трех общих шагов.

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

Как мы видели в предыдущем руководстве, элементы управления ObjectDataSource и веб-элементы управления data предоставляют точки расширяемости для шагов 1 и 3. GridView, например, запускает свое RowUpdating событие перед назначением значений полей коллекции ObjectDataSource UpdateParameters ; его RowUpdated событие возникает после завершения операции ObjectDataSource.

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

Чтобы проиллюстрировать работу с этими событиями постуровневого уровня, создадим страницу со списком продуктов в редактируемом GridView. При обновлении продукта, если возникает исключение, на странице ASP.NET отображается короткое сообщение над GridView, в котором объясняется, что возникла проблема. Приступим к работе!

Шаг 1. Создание редактируемого представления GridView для продуктов

В предыдущем руководстве мы создали редактируемый Элемент GridView с двумя полями: ProductName и UnitPrice. Для этого требовалось создать дополнительную перегрузку ProductsBLL для метода класса UpdateProduct , которая принимала только три входных параметра (имя продукта, цену за единицу и идентификатор), а не параметр для каждого поля продукта. В этом руководстве мы еще раз научимся использовать этот метод, создав редактируемый GridView, который отображает имя продукта, количество на единицу, цену за единицу и единицы на складе, но позволяет изменять только имя, цену за единицу и единицы на складе.

Для реализации этого сценария нам потребуется еще одна перегрузка UpdateProduct метода, которая принимает четыре параметра: название продукта, цена за единицу, единицы на складе и идентификатор. Добавьте в класс ProductsBLL следующий метод:

[System.ComponentModel.DataObjectMethodAttribute(
    System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
    int productID)
{
    Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;
    Northwind.ProductsRow product = products[0];
    product.ProductName = productName;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null) product.SetUnitsInStockNull();
      else product.UnitsInStock = unitsInStock.Value;
    // Update the product record
    int rowsAffected = Adapter.Update(product);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

Выполнив этот метод, мы готовы создать страницу ASP.NET, которая позволяет редактировать эти четыре конкретных поля продукта. Откройте страницу ErrorHandling.aspx в папке EditInsertDelete и добавьте GridView на страницу с помощью Designer. Привяжите GridView к новому объекту ObjectDataSource, сопоставив Select() метод с методом ProductsBLLGetProducts() класса и Update() метод с только что созданной перегрузкой UpdateProduct .

Использование перегрузки метода UpdateProduct, принимающей четыре входных параметра

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

При этом будет создан объект ObjectDataSource с коллекцией UpdateParameters с четырьмя параметрами и GridView с полем для каждого из полей продукта. Декларативная разметка ObjectDataSource присваивает OldValuesParameterFormatString свойству значение original_{0}, что вызовет исключение, так как класс BLL не ожидает передачи входного параметра с именем original_productID . Не забудьте полностью удалить этот параметр из декларативного синтаксиса (или присвойте ему значение по умолчанию , {0}).

Затем выполните синтаксический анализ GridView, чтобы включить только ProductName, QuantityPerUnit, UnitPriceи UnitsInStock BoundFields. Кроме того, вы можете применить любое форматирование на уровне полей, которое вы сочтете необходимым (например, изменение HeaderText свойств).

В предыдущем руководстве мы рассмотрели, как отформатировать UnitPrice BoundField в виде валюты как в режиме только для чтения, так и в режиме редактирования. Давайте сделаем то же самое здесь. Напомним, что для этого требовалось задать свойству BoundField DataFormatString значение , а свойству HtmlEncodefalse— значение , а ApplyFormatInEditMode свойству — значение true, как показано на рисунке {0:c}2.

Настройка Параметра UnitPrice BoundField для отображения в качестве валюты

Рис. 2. Настройка UnitPrice BoundField для отображения в качестве валюты (щелкните для просмотра полноразмерного изображения)

Для форматирования UnitPrice в виде валюты в интерфейсе редактирования требуется создать обработчик событий для события GridView RowUpdating , который анализирует строку в формате валюты в decimal значение. Помните, что RowUpdating обработчик событий из последнего учебника также проверял, что пользователь предоставил UnitPrice значение. Однако в этом руководстве давайте разрешим пользователю опустить цену.

protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    if (e.NewValues["UnitPrice"] != null)
        e.NewValues["UnitPrice"] =decimal.Parse(e.NewValues["UnitPrice"].ToString(),
            System.Globalization.NumberStyles.Currency);
}

Наш GridView содержит QuantityPerUnit BoundField, но этот BoundField должен быть только для отображения и не должен быть редактируемым пользователем. Чтобы упорядочить это, просто присвойте свойству BoundFields ReadOnly значение true.

Сделать QuantityPerUnit BoundField только для чтения

Рис. 3. Создание QuantityPerUnit Read-Only BoundField (щелкните для просмотра полноразмерного изображения)

Наконец, проверка флажок Включить редактирование из смарт-тега GridView. После выполнения этих шагов ErrorHandling.aspx Designer страницы должны выглядеть примерно так, как на рис. 4.

Удалите все, кроме необходимых полей boundfields и установите флажок Включить редактирование

Рис. 4. Удаление всех, кроме необходимых полей BoundFields и установка флажка Включить редактирование (Щелкните, чтобы просмотреть изображение в полноразмерном размере)

На этом этапе у нас есть список всех полей продуктов ProductName, QuantityPerUnit, UnitPriceи UnitsInStock , однако можно изменить только ProductNameполя , UnitPriceи UnitsInStock .

Теперь пользователи могут легко изменять названия продуктов, цены и единицы в запасах

Рис. 5. Теперь пользователи могут легко изменять названия продуктов, цены и единицы на складе (щелкните для просмотра полноразмерного изображения)

Шаг 2. Изящная обработка исключений DAL-Level

Хотя наш редактируемый GridView прекрасно работает, когда пользователи вводят юридические значения для названия измененного продукта, цены и единиц на складе, ввод недопустимых значений приводит к исключению. Например, пропуск ProductName значения приводит к возникновению исключения NoNullAllowedException , так как ProductName свойство в ProductsRow классе имеет AllowDBNull значение false; если база данных не работает, SqlException tableAdapter создает исключение при попытке подключения к базе данных. Не предпринимая никаких действий, эти исключения переносимы из уровня доступа к данным на уровень бизнес-логики, затем на страницу ASP.NET и, наконец, в среду выполнения ASP.NET.

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

На рисунке 6 показан экран, который возникает при попытке обновить продукт без указания ProductName значения. Это подробный отчет об ошибках по умолчанию, отображаемый при прохождении через localhost.

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

Рис. 6. Пропуск имени продукта приведет к отображению сведений об исключении (щелкните для просмотра полноразмерного изображения)

Хотя такие сведения об исключении полезны при тестировании приложения, представление конечного пользователя с таким экраном перед лицом исключения не является идеальным. Конечный пользователь, скорее всего, не знает, что такое и NoNullAllowedException почему он был вызван. Лучший подход — представить пользователю более понятное сообщение, объясняющее, что при попытке обновить продукт возникли проблемы.

Если при выполнении операции возникает исключение, события постуровневого уровня как в ObjectDataSource, так и в веб-элементе управления данными предоставляют средства для его обнаружения и отмены исключения из восходящей передачи в среду выполнения ASP.NET. В нашем примере давайте создадим обработчик событий для события GridView RowUpdated , который определяет, сработало ли исключение, и, если да, отображает сведения об исключении в веб-элементе управления Метка.

Начните с добавления метки на страницу ASP.NET, присвоив свойству ID значение ExceptionDetails и очистив его Text свойство. Чтобы обратить внимание пользователя на это сообщение, задайте для его CssClass свойства Warningзначение , которое является классом CSS, добавленным Styles.css в файл в предыдущем руководстве. Помните, что этот класс CSS приводит к отображению текста Label красным, курсивом, полужирным шрифтом и очень большим шрифтом.

Добавление веб-элемента управления

Рис. 7. Добавление веб-элемента управления "Метка" на страницу (щелкните для просмотра полноразмерного изображения)

Так как мы хотим, чтобы этот веб-элемент управления Label отображался только сразу после возникновения исключения, задайте для его Visible свойства значение false в обработчике Page_Load событий:

protected void Page_Load(object sender, EventArgs e)
{
    ExceptionDetails.Visible = false;
}

При использовании этого кода при первом посещении страницы и последующей обратной передачи ExceptionDetails для свойства элемента управления будет Visible задано значение false. Перед лицом исключения уровня DAL или BLL, которое можно обнаружить в обработчике событий GridViewRowUpdated, мы присвоим Visible свойству ExceptionDetails элемента управления значение true. Так как обработчики событий веб-элемента управления возникают после обработчика Page_Load событий в жизненном цикле страницы, отобразится метка. Однако при следующей обратной отправке Page_Load обработчик событий отменить изменения Visible свойство обратно в false, снова скрыв его от просмотра.

Примечание

Кроме того, можно устранить необходимость задания ExceptionDetails свойства элемента управления Visible в , Page_Load назначив его Visible свойство false в декларативном синтаксисе и отключив его состояние представления (задав свойству EnableViewState значение false). Мы будем использовать этот альтернативный подход в будущем руководстве.

После добавления элемента управления Label мы создадим обработчик событий для события GridView RowUpdated . Выберите GridView в Designer, перейдите к окно свойств и щелкните значок молнии, в котором перечислены события GridView. Там уже должна быть запись для события GridView RowUpdating , так как мы создали обработчик событий для этого события ранее в этом руководстве. Создайте обработчик событий для RowUpdated события.

Создание обработчика событий для события RowUpdated GridView

Рис. 8. Создание обработчика событий для события GridView RowUpdated

Примечание

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

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

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}

Второй входной параметр этого обработчика событий — это объект типа GridViewUpdatedEventArgs, который имеет три свойства, представляющие интерес для обработки исключений:

  • Exception ссылка на созданное исключение; Если исключение не было создано, это свойство будет иметь значение null
  • ExceptionHandled Логическое значение, указывающее, было ли обработано исключение в RowUpdated обработчике событий; если false (по умолчанию) исключение создается повторно, то выполняется повторное выполнение до среды выполнения ASP.NET.
  • KeepInEditMode Если задано значение true измененной строки GridView, остается в режиме редактирования; если false (значение по умолчанию), строка GridView возвращается в режим только для чтения.

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

  • Отображение понятного сообщения в метке ExceptionDetails
  • Укажите, что исключение было обработано
  • Сохранение строки GridView в режиме редактирования

Следующий код выполняет следующие задачи:

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
    if (e.Exception != null)
    {
        // Display a user-friendly message
        ExceptionDetails.Visible = true;
        ExceptionDetails.Text = "There was a problem updating the product. ";
        if (e.Exception.InnerException != null)
        {
            Exception inner = e.Exception.InnerException;
            if (inner is System.Data.Common.DbException)
                ExceptionDetails.Text +=
                    "Our database is currently experiencing problems." +
                    "Please try again later.";
            else if (inner is NoNullAllowedException)
                ExceptionDetails.Text +=
                    "There are one or more required fields that are missing.";
            else if (inner is ArgumentException)
            {
                string paramName = ((ArgumentException)inner).ParamName;
                ExceptionDetails.Text +=
                    string.Concat("The ", paramName, " value is illegal.");
            }
            else if (inner is ApplicationException)
                ExceptionDetails.Text += inner.Message;
        }
        // Indicate that the exception has been handled
        e.ExceptionHandled = true;
        // Keep the row in edit mode
        e.KeepInEditMode = true;
    }
}

Этот обработчик событий начинается с проверки того, имеет ли e.Exception значение null. В противном случае свойству ExceptionDetails Label Visible присваивается значение true , а свойству Text — значение "Возникла проблема с обновлением продукта". Сведения о фактическом исключении, которое было создано, находятся в e.Exception свойстве InnerException объекта . Это внутреннее исключение проверяется, и, если оно имеет определенный тип, к свойству ExceptionDetails Label добавляется дополнительное полезное Text сообщение. Наконец, ExceptionHandled свойства и KeepInEditMode имеют значение true.

На рисунке 9 показан снимок экрана этой страницы при пропуске названия продукта; На рисунке 10 показаны результаты при вводе недопустимого UnitPrice значения (-50).

Поле BoundField productName должно содержать значение

Рис. 9. BoundField ProductName должно содержать значение (щелкните для просмотра полноразмерного изображения)

Отрицательные значения UnitPrice не разрешены

Рис. 10. Отрицательные UnitPrice значения запрещены (щелкните для просмотра полноразмерного изображения)

Присвоив свойству e.ExceptionHandledRowUpdated значение true, обработчик событий указал, что он обработал исключение. Таким образом, исключение не распространяется на ASP.NET среду выполнения.

Примечание

На рисунках 9 и 10 показан удобный способ обработки исключений, вызванных недопустимыми входными данными пользователем. В идеале, однако, такие недопустимые входные данные никогда не достигнут уровня бизнес-логики, так как страница ASP.NET должна гарантировать допустимость входных данных пользователя перед вызовом ProductsBLL метода класса UpdateProduct . В следующем руководстве мы посмотрим, как добавить элементы управления проверкой в интерфейсы редактирования и вставки, чтобы убедиться, что данные, отправляемые на уровень бизнес-логики, соответствуют бизнес-правилам. Элементы управления проверкой не только предотвращают вызов UpdateProduct метода до тех пор, пока предоставленные пользователем данные не будут допустимыми, но и обеспечивают более информативный пользовательский интерфейс для выявления проблем с вводом данных.

Шаг 3. Изящная обработка исключений BLL-Level

При вставке, обновлении или удалении данных уровень доступа к данным может вызвать исключение из-за ошибки, связанной с данными. Возможно, база данных находится в автономном режиме, в обязательном столбце таблицы базы данных не указано значение или может быть нарушено ограничение на уровне таблицы. Помимо исключений, связанных строго с данными, уровень бизнес-логики может использовать исключения, чтобы указать, когда были нарушены бизнес-правила. Например, в учебнике Создание уровня бизнес-логики мы добавили бизнес-правило, проверка к исходной UpdateProduct перегрузке. В частности, если пользователь помечает продукт как неподдерживаемый, мы требовали, чтобы продукт не был единственным, предоставленным его поставщиком. Если это условие было нарушено, ApplicationException возникает исключение .

Для перегрузки, созданной UpdateProduct в этом руководстве, давайте добавим бизнес-правило, запрещающее UnitPrice задать для поля новое значение, которое более чем в два раза превышает исходное UnitPrice значение. Для этого настройте перегрузку UpdateProduct таким образом, чтобы она выполняла это проверка и вызывает исключение , ApplicationException если правило нарушено. Обновленный метод выглядит следующим образом:

public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
    int productID)
{
    Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
    if (products.Count == 0)
        // no matching record found, return false
        return false;
    Northwind.ProductsRow product = products[0];
    // Make sure the price has not more than doubled
    if (unitPrice != null && !product.IsUnitPriceNull())
        if (unitPrice > product.UnitPrice * 2)
          throw new ApplicationException(
            "When updating a product price," +
            " the new price cannot exceed twice the original price.");
    product.ProductName = productName;
    if (unitPrice == null) product.SetUnitPriceNull();
      else product.UnitPrice = unitPrice.Value;
    if (unitsInStock == null) product.SetUnitsInStockNull();
      else product.UnitsInStock = unitsInStock.Value;
    // Update the product record
    int rowsAffected = Adapter.Update(product);
    // Return true if precisely one row was updated, otherwise false
    return rowsAffected == 1;
}

При этом изменении любое обновление цены, которое более чем в два раза превышает существующую цену, вызовет ApplicationException исключение . Как и исключение, созданное из DAL, это исключение, вызванное ApplicationException BLL, можно обнаружить и обработать в обработчике RowUpdated событий GridView. Фактически код RowUpdated обработчика событий правильно обнаруживает это исключение и отображает ApplicationExceptionMessage значение свойства . На рисунке 11 показан снимок экрана, когда пользователь пытается обновить цену Chai до $50,00, что более чем в два раза превышает текущую цену в $19,95.

Бизнес-правила запрещают повышение цен, что более чем в два раза цена продукта

Рис. 11. Бизнес-правила запрещают увеличение цен более чем в два раза за продукт (щелкните, чтобы просмотреть полноразмерное изображение)

Примечание

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

Сводка

Во время операций вставки, обновления и удаления веб-элемент управления данными и ObjectDataSource включали события предварительного и последующего уровня, которые резервируют фактическую операцию. Как мы видели в этом руководстве и предыдущем руководстве, при работе с редактируемым GridView возникает событие GridView RowUpdating , за которым следует событие ObjectDataSource Updating , после чего команда обновления выполняется для базового объекта ObjectDataSource. После завершения операции срабатывает событие ObjectDataSource Updated , за которым следует событие GridView RowUpdated .

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

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

Счастливого программирования!

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Особая благодарность

Эта серия учебников была рассмотрена многими полезными рецензентами. Ведущим рецензентом этого руководства была Лиз Шулок. Хотите просмотреть предстоящие статьи MSDN? Если да, опустите мне строку на mitchell@4GuysFromRolla.com.