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

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

Загрузить 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, True)> _
Public Function UpdateProduct _
    (ByVal productName As String, ByVal unitPrice As Nullable(Of Decimal), _
ByVal unitsInStock As Nullable(Of Short), ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

Завершив этот метод, мы готовы создать страницу 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{0:c}необходимо задать значение , а для свойства falseHtmlEncode — значение , а для свойства ApplyFormatInEditMode — значение true, как показано на рис. 2.

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

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

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

Protected Sub GridView1_RowUpdating(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) _
    Handles GridView1.RowUpdating
    If e.NewValues("UnitPrice") IsNot Nothing Then
        e.NewValues("UnitPrice") = _
            Decimal.Parse(e.NewValues("UnitPrice").ToString(), _
            System.Globalization.NumberStyles.Currency)
    End If

Наш 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 почему он был вызван. Лучший подход — представить пользователю более понятное сообщение, объясняющее, что при попытке обновить продукт возникли проблемы.

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

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

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

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

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

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    ExceptionDetails.Visible = False
End Sub

При использовании этого кода при первом посещении страницы и последующих обратных операциях ExceptionDetails для элемента управления будет Visible задано falseзначение . Перед лицом исключения уровня DAL или BLL, которое можно обнаружить в обработчике событий GridView RowUpdated , мы присвоим свойству ExceptionDetails элемента управления Visible значение 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 Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
End Sub

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

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

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

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

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

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
    If e.Exception IsNot Nothing Then
        ExceptionDetails.Visible = True
        ExceptionDetails.Text = "There was a problem updating the product. "
        If e.Exception.InnerException IsNot Nothing Then
            Dim inner As Exception = e.Exception.InnerException
            If TypeOf inner Is System.Data.Common.DbException Then
                ExceptionDetails.Text &= _
                "Our database is currently experiencing problems." & _
                "Please try again later."
            ElseIf TypeOf inner _
             Is System.Data.NoNullAllowedException Then
                ExceptionDetails.Text += _
                    "There are one or more required fields that are missing."
            ElseIf TypeOf inner Is ArgumentException Then
                Dim paramName As String = CType(inner, ArgumentException).ParamName
                ExceptionDetails.Text &= _
                    String.Concat("The ", paramName, " value is illegal.")
            ElseIf TypeOf inner Is ApplicationException Then
                ExceptionDetails.Text += inner.Message
            End If
        End If
        e.ExceptionHandled = True
        e.KeepInEditMode = True
    End If
End Sub

Этот обработчик событий начинается с проверки того, имеет ли 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 в случае нарушения правила. Обновленный метод выглядит следующим образом:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(ByVal productName As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    If unitPrice.HasValue AndAlso Not product.IsUnitPriceNull() Then
        If unitPrice > product.UnitPrice * 2 Then
            Throw New ApplicationException( _
                "When updating a product price," & _
                " the new price cannot exceed twice the original price.")
        End If
    End If
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

При этом изменении любое обновление цены, которое более чем в два раза превышает существующую цену, вызовет 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.