处理 BLL 和 DAL 级别的异常 (VB)

作者 :斯科特·米切尔

下载 PDF

在本教程中,我们将了解如何在可编辑 DataList 更新工作流期间处理引发的异常。

介绍

DataList 教程中编辑和删除数据的概述中,我们创建了一个 DataList,提供简单的编辑和删除功能。 虽然功能完全正常,但用户并不友好,因为编辑或删除过程中发生的任何错误都会导致未经处理的异常。 例如,省略产品名称,或在编辑产品时输入价格值“非常实惠!”将引发异常。 由于此异常未在代码中捕获,因此它会气泡到 ASP.NET 运行时,然后会在网页中显示异常的详细信息。

正如我们在 ASP.NET 页教程的“处理 BLL”和“DAL 级别异常”中看到的那样,如果从业务逻辑或数据访问层的深度引发异常,异常详细信息将返回到 ObjectDataSource,然后返回到 GridView。 我们了解了如何通过为 ObjectDataSource 或 GridView 创建 UpdatedRowUpdated 事件处理程序来正常处理这些异常,并检查异常,然后指示已处理异常。

但是,我们的 DataList 教程不使用 ObjectDataSource 来更新和删除数据。 相反,我们直接针对 BLL 工作。 为了检测源自 BLL 或 DAL 的异常,我们需要在 ASP.NET 页的代码隐藏中实现异常处理代码。 在本教程中,我们将了解如何更巧妙地处理在可编辑 DataList 更新工作流期间引发的异常。

注意

DataList 教程中编辑和删除数据的概述中,我们讨论了从 DataList 中编辑和删除数据的不同技术,其中涉及使用 ObjectDataSource 进行更新和删除的一些技术。 如果使用这些技术,可以通过 ObjectDataSource 或UpdatedDeleted事件处理程序处理 BLL 或 DAL 中的异常。

步骤 1:创建可编辑 DataList

在担心在更新工作流期间发生的异常之前,让我们先创建一个可编辑的 DataList。 打开ErrorHandling.aspx文件夹中的页面EditDeleteDataList,将 DataList 添加到设计器,将其ID属性设置为Products,并添加新的 ObjectDataSource。ProductsDataSource 将 ObjectDataSource 配置为使用 ProductsBLL 类方法 GetProducts() 选择记录;将 INSERT、UPDATE 和 DELETE 选项卡中的下拉列表设置为“无”。

使用 GetProducts 方法返回产品信息

图 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 EditCommandCancelCommandUpdateCommand事件创建事件处理程序。 事件EditCommandCancelCommand只是更新 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 返回到其预编辑状态之前调用ProductsBLLUpdateProduct方法。

现在,让我们在 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的页面。 通过将标签的属性分配给CssClassWarning文件中定义的 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 ... CatchUpdateCommand 。 该 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 块的代码将开始执行。 引发DbExceptionNoNullAllowedException的异常类型,ArgumentException依此类推,取决于首先引发错误的内容。 如果在数据库级别出现问题,将引发一个 DbException 。 如果为 UnitPriceUnitsInStockReorderLevel 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 ... CatchUpdateCommand 。 如果在更新工作流期间引发异常,则 Catch 块的代码将执行,并在标签中 ExceptionDetails 显示有用的信息。

此时,DataList 不努力防止异常首先发生。 尽管我们知道负价将导致异常,但我们尚未添加任何功能来主动阻止用户输入如此无效的输入。 在下一教程中,我们将了解如何通过添加 EditItemTemplate验证控件来帮助减少用户输入无效导致的异常。

快乐编程!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

斯科特·米切尔,七本 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