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

作者 :斯科特·米切尔

下载 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 或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 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事件处理程序涉及更多。 它需要从ProductID集合中读取已编辑的产品DataKeys以及 TextBoxes 中的EditItemTemplate产品名称和价格,然后在将 DataList 返回到其预编辑状态之前调用ProductsBLLUpdateProduct方法。

现在,让我们在 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 控件添加到 ID 其设置为 ExceptionDetails的页面。 通过将标签的属性分配给CssClassWarning文件中定义的 Styles.css CSS 类,将标签文本配置为以红色、特大、粗体和斜体字体显示。

发生错误时,我们只需要显示一次标签。 也就是说,在后续回发时,标签警告消息应消失。 这可以通过清除 Label 的Text属性,或者在事件处理程序中将其Visible属性设置为False(就像我们在 ASP.NET 页面教程Page_Load中所做的一样),或者通过禁用 Label 的视图状态支持来完成。 让我们使用后一个选项。

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

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

若要确定何时引发错误以便在页面上显示有用的消息,我们需要向事件处理程序添加一个 Try ... CatchUpdateCommand 。 该 Try 部分包含可能导致异常的代码,而 Catch 块包含面对异常执行的代码。 请查看 .NET Framework 文档中异常处理基础知识部分,以了解有关Try ... Catch块的更多信息。

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 块的代码将开始执行。 引发DbExceptionNoNullAllowedException的异常类型,ArgumentException依此类推,取决于首先引发错误的内容。 如果在数据库级别出现问题,将引发一个 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;
}

若要完成本教程,只需从传入捕获DisplayExceptionDetails实例的块调用Catch该方法(Exception)。ex

在设置块后 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 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。

特别致谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Ken Pespisa。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com