Поделиться через


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

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

Скачивание PDF

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

Введение

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

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

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

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

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

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

В предыдущем руководстве мы создали редактируемый 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 на страницу с помощью конструктора. Привяжите GridView к новому ObjectDataSource, сопоставив метод Select() с методом ProductsBLL класса GetProducts(), а метод 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}`, для свойства `HtmlEncode` значение `false` и для свойства `ApplyFormatInEditMode` значение `true`, как показано на рис. 2.

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

Рис. 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 должен выглядеть аналогично рисунку 4.

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

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

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

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

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

Шаг 2: Корректная обработка исключений DAL-Level

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

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

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

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

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

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

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

Замечание

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

После добавления элемента управления Label мы создадим обработчик событий для события GridView RowUpdated . Выберите GridView в конструкторе, перейдите в окно свойств и щелкните значок молнии, в котором перечислены события 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 возвращается в режим только для чтения.

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

  • Отображение понятного сообщения в 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. Это внутреннее исключение проверяется и, если оно имеет определенный тип, к свойству метки ExceptionDetails Label Text добавляется дополнительное полезное сообщение. Наконец, для обоих ExceptionHandledKeepInEditMode свойств задано значение true.

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

Объект ProductName BoundField должен содержать значение

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

Отрицательные значения UnitPrice недопустимы

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

Установив для свойства e.ExceptionHandled значение true, обработчик событий RowUpdated указал, что исключение было обработано. Поэтому исключение не будет распространяться до среды выполнения 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, это исключение, вызываемое BLL ApplicationException , может быть обнаружено и обработано в обработчике событий GridView RowUpdated . На самом деле RowUpdated код обработчика событий, как записано, правильно обнаружит это исключение и отобразит ApplicationExceptionMessage значение свойства. На рисунке 11 показан снимок экрана, когда пользователь пытается обновить цену Chai до $50,00, что более чем в два раза превышает текущую цену $ 19,95.

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

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

Замечание

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

Сводка

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

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

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

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

Сведения о авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams: Изучаем самостоятельно ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.

Особое спасибо кому

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