Обработка исключений уровней BLL и DAL на странице ASP.NET(C#)
В этом руководстве мы посмотрим, как отобразить понятное информативное сообщение об ошибке при возникновении исключения во время операции вставки, обновления или удаления веб-элемента управления 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, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Выполнив этот метод, мы готовы создать страницу ASP.NET, которая позволяет редактировать эти четыре конкретных поля продукта. Откройте страницу ErrorHandling.aspx
в папке EditInsertDelete
и добавьте GridView на страницу с помощью Designer. Привяжите GridView к новому объекту ObjectDataSource, сопоставив Select()
метод с методом 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
значение , а свойству HtmlEncode
false
— значение , а ApplyFormatInEditMode
свойству — значение true
, как показано на рисунке {0:c}
2.
Рис. 2. Настройка UnitPrice
BoundField для отображения в качестве валюты (щелкните для просмотра полноразмерного изображения)
Для форматирования UnitPrice
в виде валюты в интерфейсе редактирования требуется создать обработчик событий для события GridView RowUpdating
, который анализирует строку в формате валюты в decimal
значение. Помните, что RowUpdating
обработчик событий из последнего учебника также проверял, что пользователь предоставил UnitPrice
значение. Однако в этом руководстве давайте разрешим пользователю опустить цену.
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] =decimal.Parse(e.NewValues["UnitPrice"].ToString(),
System.Globalization.NumberStyles.Currency);
}
Наш GridView содержит QuantityPerUnit
BoundField, но этот BoundField должен быть только для отображения и не должен быть редактируемым пользователем. Чтобы упорядочить это, просто присвойте свойству BoundFields ReadOnly
значение true
.
Рис. 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
почему он был вызван. Лучший подход — представить пользователю более понятное сообщение, объясняющее, что при попытке обновить продукт возникли проблемы.
Если при выполнении операции возникает исключение, события постуровневого уровня как в ObjectDataSource, так и в веб-элементе управления данными предоставляют средства для его обнаружения и отмены исключения из восходящей передачи в среду выполнения ASP.NET. В нашем примере давайте создадим обработчик событий для события GridView RowUpdated
, который определяет, сработало ли исключение, и, если да, отображает сведения об исключении в веб-элементе управления Метка.
Начните с добавления метки на страницу ASP.NET, присвоив свойству ID
значение ExceptionDetails
и очистив его Text
свойство. Чтобы обратить внимание пользователя на это сообщение, задайте для его CssClass
свойства Warning
значение , которое является классом CSS, добавленным Styles.css
в файл в предыдущем руководстве. Помните, что этот класс CSS приводит к отображению текста Label красным, курсивом, полужирным шрифтом и очень большим шрифтом.
Рис. 7. Добавление веб-элемента управления "Метка" на страницу (щелкните для просмотра полноразмерного изображения)
Так как мы хотим, чтобы этот веб-элемент управления Label отображался только сразу после возникновения исключения, задайте для его Visible
свойства значение false в обработчике Page_Load
событий:
protected void Page_Load(object sender, EventArgs e)
{
ExceptionDetails.Visible = false;
}
При использовании этого кода при первом посещении страницы и последующей обратной передачи ExceptionDetails
для свойства элемента управления будет Visible
задано значение false
. Перед лицом исключения уровня DAL или BLL, которое можно обнаружить в обработчике событий GridViewRowUpdated
, мы присвоим Visible
свойству ExceptionDetails
элемента управления значение true. Так как обработчики событий веб-элемента управления возникают после обработчика Page_Load
событий в жизненном цикле страницы, отобразится метка. Однако при следующей обратной отправке Page_Load
обработчик событий отменить изменения Visible
свойство обратно в false
, снова скрыв его от просмотра.
Примечание
Кроме того, можно устранить необходимость задания ExceptionDetails
свойства элемента управления Visible
в , Page_Load
назначив его Visible
свойство false
в декларативном синтаксисе и отключив его состояние представления (задав свойству EnableViewState
значение false
). Мы будем использовать этот альтернативный подход в будущем руководстве.
После добавления элемента управления Label мы создадим обработчик событий для события GridView RowUpdated
. Выберите GridView в Designer, перейдите к окно свойств и щелкните значок молнии, в котором перечислены события GridView. Там уже должна быть запись для события GridView RowUpdating
, так как мы создали обработчик событий для этого события ранее в этом руководстве. Создайте обработчик событий для RowUpdated
события.
Рис. 8. Создание обработчика событий для события GridView RowUpdated
Примечание
Обработчик событий также можно создать с помощью раскрывающихся списков в верхней части файла класса программной части. Выберите GridView в раскрывающемся списке слева, а RowUpdated
событие — справа.
При создании этого обработчика событий в класс кода программной части страницы ASP.NET добавляется следующий код:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}
Второй входной параметр этого обработчика событий — это объект типа GridViewUpdatedEventArgs, который имеет три свойства, представляющие интерес для обработки исключений:
Exception
ссылка на созданное исключение; Если исключение не было создано, это свойство будет иметь значениеnull
ExceptionHandled
Логическое значение, указывающее, было ли обработано исключение вRowUpdated
обработчике событий; еслиfalse
(по умолчанию) исключение создается повторно, то выполняется повторное выполнение до среды выполнения ASP.NET.KeepInEditMode
Если задано значениеtrue
измененной строки GridView, остается в режиме редактирования; еслиfalse
(значение по умолчанию), строка GridView возвращается в режим только для чтения.
Таким образом, наш код должен проверка, чтобы узнать, не null
имеет ли Exception
значение , а это означает, что при выполнении операции было создано исключение. Если это так, мы хотим:
- Отображение понятного сообщения в метке
ExceptionDetails
- Укажите, что исключение было обработано
- Сохранение строки GridView в режиме редактирования
Следующий код выполняет следующие задачи:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null)
{
// Display a user-friendly message
ExceptionDetails.Visible = true;
ExceptionDetails.Text = "There was a problem updating the product. ";
if (e.Exception.InnerException != null)
{
Exception inner = e.Exception.InnerException;
if (inner is System.Data.Common.DbException)
ExceptionDetails.Text +=
"Our database is currently experiencing problems." +
"Please try again later.";
else if (inner is NoNullAllowedException)
ExceptionDetails.Text +=
"There are one or more required fields that are missing.";
else if (inner is ArgumentException)
{
string paramName = ((ArgumentException)inner).ParamName;
ExceptionDetails.Text +=
string.Concat("The ", paramName, " value is illegal.");
}
else if (inner is ApplicationException)
ExceptionDetails.Text += inner.Message;
}
// Indicate that the exception has been handled
e.ExceptionHandled = true;
// Keep the row in edit mode
e.KeepInEditMode = true;
}
}
Этот обработчик событий начинается с проверки того, имеет ли e.Exception
значение null
. В противном случае свойству ExceptionDetails
Label Visible
присваивается значение true
, а свойству Text
— значение "Возникла проблема с обновлением продукта". Сведения о фактическом исключении, которое было создано, находятся в e.Exception
свойстве InnerException
объекта . Это внутреннее исключение проверяется, и, если оно имеет определенный тип, к свойству ExceptionDetails
Label добавляется дополнительное полезное Text
сообщение. Наконец, ExceptionHandled
свойства и KeepInEditMode
имеют значение true
.
На рисунке 9 показан снимок экрана этой страницы при пропуске названия продукта; На рисунке 10 показаны результаты при вводе недопустимого UnitPrice
значения (-50).
Рис. 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
если правило нарушено. Обновленный метод выглядит следующим образом:
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
// Make sure the price has not more than doubled
if (unitPrice != null && !product.IsUnitPriceNull())
if (unitPrice > product.UnitPrice * 2)
throw new ApplicationException(
"When updating a product price," +
" the new price cannot exceed twice the original price.");
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
При этом изменении любое обновление цены, которое более чем в два раза превышает существующую цену, вызовет ApplicationException
исключение . Как и исключение, созданное из DAL, это исключение, вызванное ApplicationException
BLL, можно обнаружить и обработать в обработчике RowUpdated
событий GridView. Фактически код RowUpdated
обработчика событий правильно обнаруживает это исключение и отображает 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.