Обработка исключений уровней BLL и DAL на странице ASP.NET (VB)
В этом руководстве мы посмотрим, как отобразить понятное информативное сообщение об ошибке в случае возникновения исключения во время операции вставки, обновления или удаления веб-элемента управления ASP.NET данных.
Введение
Работа с данными из веб-приложения ASP.NET с использованием многоуровневой архитектуры приложений включает следующие три общих шага:
- Определите, какой метод уровня бизнес-логики необходимо вызвать и какие значения параметров следует передать. Значения параметров могут быть жестко закодированы, назначаемыми программными средствами или входными данными, введенными пользователем.
- Вызовите метод.
- Обработайте результаты. При вызове метода 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()
метод с методом ProductsBLL
GetProducts()
класса и Update()
метод с только что созданной перегрузкой 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}
необходимо задать значение , а для свойства false
HtmlEncode
— значение , а для свойства ApplyFormatInEditMode
— значение true
, как показано на рис. 2.
Рис. 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
.
Рис. 3. Создание QuantityPerUnit
Read-Only BoundField (щелкните для просмотра полноразмерного изображения)
Наконец, проверка флажок Включить редактирование из смарт-тега GridView. После выполнения этих действий ErrorHandling.aspx
Designer страницы должны выглядеть примерно так, как на рис. 4.
Рис. 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 красным курсивом, полужирным шрифтом и очень большим шрифтом.
Рис. 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
события.
Рис. 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).
Рис. 9. BoundField ProductName
должен содержать значение (щелкните для просмотра полноразмерного изображения)
Рис. 10. Отрицательные UnitPrice
значения запрещены (щелкните для просмотра полноразмерного изображения)
Присвоив свойству e.ExceptionHandled
RowUpdated
значение 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
обработчика событий, как написано, правильно обнаружит это исключение и отобразит ApplicationException
Message
значение свойства . На рисунке 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.