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

作者 :Scott Mitchell

下载 PDF

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

简介

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

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

但是,我们的 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) 。

使用 GetProducts () 方法返回产品信息

图 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 、 EditCommandCancelCommandUpdateCommand 事件创建事件处理程序。 EditCommandCancelCommand 事件只是更新 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属性FalsePage_Load设置为 来实现,就像我们在 ASP.NET Page 中处理 BLL 和 DAL-Level 异常教程) 中所做的那样,或者禁用 Label 的视图状态支持。 让我们使用后一个选项。

<asp:Label ID="ExceptionDetails" EnableViewState="False" CssClass="Warning"
    runat="server" />

当引发异常时,我们会将该异常的详细信息分配给 ExceptionDetails Label 控件的 Text 属性。 由于其视图状态已禁用,因此在后续回发时, Text 属性的编程更改将丢失, (空字符串) 还原为默认文本,从而隐藏警告消息。

若要确定何时引发错误以在页面上显示有用的消息,我们需要将 块添加到Try ... CatchUpdateCommand事件处理程序。 部分 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
    }
}

当块中的代码引发任何类型的异常时 TryCatch 块代码将开始执行。 引发 DbException的 、 NoNullAllowedExceptionArgumentException等异常的类型取决于最初引发错误的确切情况。 如果在数据库级别出现问题, DbException 将引发 。 如果为 UnitPriceUnitsInStockUnitsOnOrderReorderLevel 字段输入了非法值, 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 ... CatchUpdateCommand可编辑的 DataList 更新工作流。 如果在更新工作流期间引发异常,则会 Catch 执行块代码,并在 Label 中 ExceptionDetails 显示有用的信息。

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

编程愉快!

深入阅读

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

关于作者

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。