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