处理 BLL 和 DAL 级别的异常 (C#)
在本教程中,我们将了解如何巧妙地处理在可编辑的 DataList 更新工作流期间引发的异常。
简介
在 DataList 中编辑和删除数据的概述教程中 ,我们创建了一个提供简单编辑和删除功能的 DataList。 虽然功能齐全,但对用户来说并不友好,因为编辑或删除过程中发生的任何错误都会导致未经处理的异常。 例如,如果省略产品的名称,或者在编辑产品时输入价格值非常实惠!,则会引发异常。 由于此异常未在代码中捕获,因此它会冒泡到 ASP.NET 运行时,该运行时随后会在网页中显示异常的详细信息。
正如我们在 ASP.NET 页中处理 BLL 和 DAL-Level 异常 教程中看到的那样,如果从业务逻辑或数据访问层的深度引发异常,则异常详细信息将返回到 ObjectDataSource,然后返回到 GridView。 通过为 ObjectDataSource 或 GridView 创建 Updated
或 RowUpdated
事件处理程序,检查异常,然后指示已处理异常,我们了解了如何正常处理这些异常。
但是,我们的 DataList 教程不使用 ObjectDataSource 来更新和删除数据。 相反,我们直接针对 BLL 工作。 为了检测源自 BLL 或 DAL 的异常,我们需要在 ASP.NET 页的代码隐藏内实现异常处理代码。 在本教程中,我们将了解如何更巧妙地处理在可编辑的 DataList 更新工作流期间引发的异常。
注意
在 DataList 中编辑和删除数据的概述教程中 ,我们讨论了用于编辑和删除 DataList 中的数据的不同方法,其中一些技术涉及使用 ObjectDataSource 进行更新和删除。 如果采用这些技术,可以通过 ObjectDataSource 或事件处理程序处理来自 BLL 或 Deleted
DAL 的Updated
异常。
步骤 1:创建可编辑的数据列表
在担心处理更新工作流期间发生的异常之前,让我们先创建一个可编辑的 DataList。 ErrorHandling.aspx
打开 文件夹中的页面EditDeleteDataList
,将 DataList 添加到Designer,将其 ID
属性设置为 Products
,然后添加名为 ProductsDataSource
的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsBLL
类方法来 GetProducts()
选择记录;将“插入”、“更新”和“删除”选项卡中的下拉列表设置为 (None) 。
图 1:使用 GetProducts()
方法返回产品信息 (单击以查看全尺寸图像)
完成 ObjectDataSource 向导后,Visual Studio 将自动为 DataList 创建 ItemTemplate
。 将此 替换为显示 ItemTemplate
每个产品名称和价格并包含“编辑”按钮的 。 接下来,使用 TextBox Web 控件创建 EditItemTemplate
用于名称和价格以及“更新”和“取消”按钮的 。 最后,将 DataList 的 RepeatColumns
属性设置为 2。
进行这些更改后,页面声明性标记应如下所示。 双检查以确保“编辑”、“取消”和“更新”按钮的属性CommandName
分别设置为“编辑”、“取消”和“更新”。
<asp:DataList ID="Products" runat="server" DataKeyField="ProductID"
DataSourceID="ProductsDataSource" RepeatColumns="2">
<ItemTemplate>
<h5>
<asp:Label runat="server" ID="ProductNameLabel"
Text='<%# Eval("ProductName") %>' />
</h5>
Price:
<asp:Label runat="server" ID="Label1"
Text='<%# Eval("UnitPrice", "{0:C}") %>' />
<br />
<asp:Button runat="server" id="EditProduct" CommandName="Edit"
Text="Edit" />
<br />
<br />
</ItemTemplate>
<EditItemTemplate>
Product name:
<asp:TextBox ID="ProductName" runat="server"
Text='<%# Eval("ProductName") %>' />
<br />
Price:
<asp:TextBox ID="UnitPrice" runat="server"
Text='<%# Eval("UnitPrice", "{0:C}") %>' />
<br />
<br />
<asp:Button ID="UpdateProduct" runat="server" CommandName="Update"
Text="Update" />
<asp:Button ID="CancelUpdate" runat="server" CommandName="Cancel"
Text="Cancel" />
</EditItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
SelectMethod="GetProducts" TypeName="ProductsBLL"
OldValuesParameterFormatString="original_{0}">
</asp:ObjectDataSource>
注意
对于本教程,必须启用 DataList 的视图状态。
请花点时间通过浏览器查看进度 (请参阅图 2) 。
图 2:每个产品都包含一个“编辑”按钮 (单击以查看全尺寸图像)
目前,“编辑”按钮只会导致回发,它尚未使产品可编辑。 若要启用编辑,需要为 DataList 、 EditCommand
CancelCommand
和 UpdateCommand
事件创建事件处理程序。 EditCommand
和 CancelCommand
事件只是更新 DataList 的 EditItemIndex
属性,并将数据重新绑定到 DataList:
protected void Products_EditCommand(object source, DataListCommandEventArgs e)
{
// Set the DataList's EditItemIndex property to the
// index of the DataListItem that was clicked
Products.EditItemIndex = e.Item.ItemIndex;
// Rebind the data to the DataList
Products.DataBind();
}
protected void Products_CancelCommand(object source, DataListCommandEventArgs e)
{
// Set the DataList's EditItemIndex property to -1
Products.EditItemIndex = -1;
// Rebind the data to the DataList
Products.DataBind();
}
UpdateCommand
事件处理程序涉及更多一些。 它需要从 DataKeys
集合中读取已编辑的产品ProductID
,以及中的 TextBoxes EditItemTemplate
的产品名称和价格,然后在将 DataList 返回到其预编辑状态之前调用ProductsBLL
类 s UpdateProduct
方法。
现在,让我们使用 DataList 中编辑和删除数据概述教程中事件处理程序中完全相同的代码UpdateCommand
。 我们将添加代码以正常处理步骤 2 中的异常。
protected void Products_UpdateCommand(object source, DataListCommandEventArgs e)
{
// Read in the ProductID from the DataKeys collection
int productID = Convert.ToInt32(Products.DataKeys[e.Item.ItemIndex]);
// Read in the product name and price values
TextBox productName = (TextBox)e.Item.FindControl("ProductName");
TextBox unitPrice = (TextBox)e.Item.FindControl("UnitPrice");
string productNameValue = null;
if (productName.Text.Trim().Length > 0)
productNameValue = productName.Text.Trim();
decimal? unitPriceValue = null;
if (unitPrice.Text.Trim().Length > 0)
unitPriceValue = Decimal.Parse(unitPrice.Text.Trim(),
System.Globalization.NumberStyles.Currency);
// Call the ProductsBLL's UpdateProduct method...
ProductsBLL productsAPI = new ProductsBLL();
productsAPI.UpdateProduct(productNameValue, unitPriceValue, productID);
// Revert the DataList back to its pre-editing state
Products.EditItemIndex = -1;
Products.DataBind();
}
面对无效输入,可以是格式不正确的单价,非法单价值,如 -$5.00,或遗漏产品名称将提出例外。 UpdateCommand
由于事件处理程序此时不包含任何异常处理代码,因此异常将浮升到 ASP.NET 运行时,在运行时中,它将显示给最终用户 (见图 3) 。
图 3:发生未经处理的异常时,最终用户将看到错误页
步骤 2:正常处理 UpdateCommand 事件处理程序中的异常
在更新工作流期间,事件处理程序、BLL 或 DAL 中 UpdateCommand
可能会发生异常。 例如,如果用户输入的价格太贵, Decimal.Parse
事件处理程序中的 UpdateCommand
语句将引发异常 FormatException
。 如果用户省略产品名称或价格为负值,DAL 将引发异常。
发生异常时,我们希望在页面本身内显示信息性消息。 将标签 Web 控件添加到设置为 ExceptionDetails
的页面ID
。 通过将 Label 文本的 属性Warning
分配给文件中定义的 Styles.css
CSS 类,将 Label 文本配置为CssClass
以红色、特大、粗体和斜体字体显示。
发生错误时,我们只希望标签显示一次。 也就是说,在后续回发时,标签 警告消息应会消失。 这可以通过清除 Label 属性Text
或在事件处理程序 (中将其Visible
属性False
Page_Load
设置为 来实现,就像我们在 ASP.NET Page 中处理 BLL 和 DAL-Level 异常教程) 中所做的那样,或者禁用 Label 的视图状态支持。 让我们使用后一个选项。
<asp:Label ID="ExceptionDetails" EnableViewState="False" CssClass="Warning"
runat="server" />
当引发异常时,我们会将该异常的详细信息分配给 ExceptionDetails
Label 控件的 Text
属性。 由于其视图状态已禁用,因此在后续回发时, Text
属性的编程更改将丢失, (空字符串) 还原为默认文本,从而隐藏警告消息。
若要确定何时引发错误以在页面上显示有用的消息,我们需要将 块添加到Try ... Catch
UpdateCommand
事件处理程序。 部分 Try
包含可能导致异常的代码,而 Catch
块包含面对异常时执行的代码。 有关 块的详细信息Try ... Catch
,请查看.NET Framework文档中的异常处理基础知识部分。
protected void Products_UpdateCommand(object source, DataListCommandEventArgs e)
{
// Handle any exceptions raised during the editing process
try
{
// Read in the ProductID from the DataKeys collection
int productID = Convert.ToInt32(Products.DataKeys[e.Item.ItemIndex]);
... Some code omitted for brevity ...
}
catch (Exception ex)
{
// TODO: Display information about the exception in ExceptionDetails
}
}
当块中的代码引发任何类型的异常时 Try
, Catch
块代码将开始执行。 引发 DbException
的 、 NoNullAllowedException
、 ArgumentException
等异常的类型取决于最初引发错误的确切情况。 如果在数据库级别出现问题, DbException
将引发 。 如果为 UnitPrice
、 UnitsInStock
、 UnitsOnOrder
或 ReorderLevel
字段输入了非法值, ArgumentException
则会引发 ,因为我们添加了代码来验证类中的 ProductsDataTable
这些字段值 (请参阅 创建业务逻辑层 教程) 。
通过将消息文本基于捕获的异常类型,我们可以向最终用户提供更有用的解释。 以下代码在 ASP.NET 页中处理 BLL 和 DAL-Level 异常教程中 以几乎完全相同的形式使用,提供了此级别的详细信息:
private void DisplayExceptionDetails(Exception ex)
{
// Display a user-friendly message
ExceptionDetails.Text = "There was a problem updating the product. ";
if (ex is System.Data.Common.DbException)
ExceptionDetails.Text += "Our database is currently experiencing problems.
Please try again later.";
else if (ex is NoNullAllowedException)
ExceptionDetails.Text += "There are one or more required fields that are
missing.";
else if (ex is ArgumentException)
{
string paramName = ((ArgumentException)ex).ParamName;
ExceptionDetails.Text +=
string.Concat("The ", paramName, " value is illegal.");
}
else if (ex is ApplicationException)
ExceptionDetails.Text += ex.Message;
}
若要完成本教程,只需从Catch
传入捕获Exception
实例的块调用 DisplayExceptionDetails
方法, (ex
) 。
块到位后 Try ... Catch
,用户会看到一条信息更丰富的错误消息,如图 4 和图 5 所示。 请注意,面对异常,DataList 将保持编辑模式。 这是因为发生异常后,控制流会立即重定向到 Catch
块,绕过将 DataList 返回到其预编辑状态的代码。
图 4:如果用户省略必填字段 (单击以查看全尺寸图像)
图 5:输入负价格时显示错误消息 (单击以查看全尺寸图像)
总结
GridView 和 ObjectDataSource 提供后级别事件处理程序,其中包括有关更新和删除工作流期间引发的任何异常的信息,以及可以设置为指示是否已处理异常的属性。 但是,在使用 DataList 并直接使用 BLL 时,这些功能不可用。 相反,我们负责实现异常处理。
在本教程中,我们了解了如何通过将 块添加到事件处理程序,将异常处理添加到Try ... Catch
UpdateCommand
可编辑的 DataList 更新工作流。 如果在更新工作流期间引发异常,则会 Catch
执行块代码,并在 Label 中 ExceptionDetails
显示有用的信息。
此时,DataList 不会首先阻止异常发生。 尽管我们知道负价格会导致异常,但我们尚未添加任何功能来主动阻止用户输入此类无效输入。 在下一教程中,我们将了解如何通过在 中添加 EditItemTemplate
验证控件来帮助减少无效用户输入导致的异常。
编程愉快!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
- 异常设计准则
- 错误日志记录模块和处理程序 (ELMAH) (用于记录错误的开源库)
- 适用于 .NET Framework 2.0 (的企业库包括异常管理应用程序块)
关于作者
Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。
特别感谢
本教程系列由许多有用的审阅者查看。 本教程的首席审阅者是 Ken Pespisa。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。