Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве мы увидим, как отобразить дружелюбное и информативное сообщение об ошибке, если возникнет исключение при операции вставки, обновления или удаления веб-контрола данных ASP.NET.
Введение
Работа с данными из веб-приложения ASP.NET с помощью многоуровневой архитектуры приложений включает следующие три общие действия.
- Определите метод уровня бизнес-логики, который необходимо вызвать, и какие значения параметров следует передать. Значения параметров могут быть жестко закодированы, программно назначены или входные данные, введенные пользователем.
- Вызовите метод.
- Обработайте результаты. При вызове метода 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
.
Рис. 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.
Рис. 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
должен выглядеть аналогично рисунку 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 в красном, курсивном, полужирном, дополнительном большом шрифте.
Рис. 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
.
Рис. 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
добавляется дополнительное полезное сообщение. Наконец, для обоих ExceptionHandled
KeepInEditMode
свойств задано значение true
.
На рисунке 9 показан снимок экрана этой страницы при пропуске имени продукта; На рисунке 10 показаны результаты при вводе недопустимого UnitPrice
значения (-50).
Рис. 9.ProductName
BoundField должен содержать значение (щелкните, чтобы просмотреть изображение полного размера)
Рис. 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
код обработчика событий, как записано, правильно обнаружит это исключение и отобразит ApplicationException
Message
значение свойства. На рисунке 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.