处理 BLL 层和 DAL 层异常

本文档是 Visual C# 教程 (切换到 Visual Basic 教程)

« 前一篇教程  |  下一篇教程 »

简介

在“在DataList 中编辑和删除数据概述” 教程中,我们创建了提供简单编辑和删除功能的DataList 。而具有完全功能的时候,它几乎不是用户友好的,因为在编辑和删除过程中出现的任何错误都将导致未处理异常。例如,删除产品的名称或者当编辑产品、输入价格的值为 “Very affordable!” 时,都将抛出异常。由于在代码中不捕获此异常,它会传递到 ASP.NET 运行时,然后在网页中显示异常的详细信息。

如同我们在 “在 ASP.NET 页中处理 BLL 层和 DAL 层异常” 教程中看到的,如果从业务逻辑层或数据访问层的深处引发异常,则将异常的详细信息返回到 ObjectDataSource ,然后再传给 GridView 。我们看到如何通过为 ObjectDataSource 或 GridView 创建 Updated 或 RowUpdated event handler ,检查异常,然后指示异常已经处理,妥善地处理这些异常。

但是,我们的 DataList 教程没有使用 ObjectDataSource 来更新和删除数据。相反,我们直接处理 BLL 。为了检测源于 BLL 或 DAL 的异常,需要在 ASP.NET 的 code-behind 内实现异常处理代码。本教程中,我们将了解怎样更巧妙地处理在可编辑 DataList 更新工作流期间引发的异常。

注意 :在 “在DataList 中编辑和删除数据概述 ” 教程中,我们介绍了用于从 DataList 中编辑和删除数据的不同技术,其中一些技术涉及到使用 ObjectDataSource 来进行更新和删除。如果使用这些技术,您可以通过 ObjectDataSource 的 Updated 或 Deleted event handler 来处理来自 BLL 或 DAL 的异常。

步骤1 :创建可编辑的 DataList

在我们为处理更新工作流期间出现的异常担忧之前,首先创建一个可编辑的 DataList 。打开 EditDeleteDataList 文件夹中的 ErrorHandling.aspx 页,向设计器添加一个 DataList ,将其 ID 属性设置为 Products ,然后新建一个名为 ProductsDataSource 的 ObjectDataSource 。将 ObjectDataSource 配置为使用 ProductsBLL 类的 GetProducts() 方法来选择记录;在 INSERT 、 UPDATE 和 DELETE 选项卡中将下拉列表设置为 (None) 。

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

完成 ObjectDataSource 向导之后,Visual Studio 将会自动为 DataList 创建 ItemTemplate 。我们使用一个自定义的ItemTemplate 替换它,在这个ItemTemplate 中,显示了没个产品的名称和价格,还包含了一个Edit 按钮。然后,创建一个包含用于修改名称和价格的 Web 文本框 控件以及 Update 和 Cancel 按钮的 EditItemTemplate 。最后,将 DataList 的 RepeatColumns 属性设置为 2 。

进行这些更改之后,页面的声明式标记应该与以下类似:仔细检查以确定 Edit 、 Cancel 和 Update 按扭的 CommandName 属性分别设置为 “Edit” 、 “Cancel” 和 “Update” 。

<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 :每种产品都包括 Edit 按钮

现在,Edit 按钮只引起回传—— 它还没有使产品成为可编辑的状态。为启用编辑状态,需要为 DataList 的 EditCommand 、CancelCommand 和 UpdateCommand 事件创建event handler 。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 event handler 更复杂一些。它需要从 DataKeys 集合读入所编辑的产品的 ProductID 和从 EditItemTemplate 的 TextBoxes 中读入产品的名称和价格,然后调用 ProductsBLL 类的 UpdateProduct 方法,最后将DataList 返回到非编辑状态。

现在,让我们只使用与“在 DataList 中编辑和删除数据概述 ” 教程中的 UpdateCommand event handler 的代码完全相同的代码。我们将添加代码以便在步骤 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 event handler 还不包括任何异常处理代码,异常将传递到 ASP.NET 运行时,在这里显示给最终用户(参见图 3)。

图3 :当出现未处理异常时,最终用户将看到一个错误页面

步骤2 :在UpdateCommand event handler 中正确地处理异常

在更新工作流期间,在 UpdateCommand event handler 、BLL 或 DAL 中可能出现异常。例如,如果用户输入的价格是 “Too expensive” ,UpdateCommand event handler 中的 Decimal.Parse 语句将抛出一个 FormatException 异常。如果用户遗漏产品的名称或者价格是一个负值,DAL 将引发一个异常。

当一个异常出现时,我们希望在页面本身中显示信息性消息。向页面添加一个 Label Web 控件,将其 ID 设置为 ExceptionDetails 。通过在 Styles.css 文件中定义的 Warning CSS 类 给CssClass 属性赋值,将 Label 的文本配置为以红色、特大、粗体和斜体字体显示。

当发生错误时,我们只希望 Label 显示一次。即,在后续的回发中,Label 的警告消息应该消失。这可以通过在 Page_Load event handler 中清除 Label 的 Text 属性或者将其 Visible 属性设置为 False (像我们前面在 “在 ASP.NET 页中处理 BLL 层和 DAL 层异常 ” 教程中所做的一样)或通过禁用 Label 的对视图状态支持来实现。我们使用后面的选择。

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

当引发一个异常时,我们将异常的详细信息赋给 ExceptionDetails Label 控件的 Text 属性。由于它的视图状态被禁用,在后续回传中通过编程方式对 Text 属性的更改将会丢失,恢复到默认的文本(空字符串),从而隐藏警告消息。

若要确定何时引发错误以便在页面上显示帮助消息,需要向 UpdateCommand event handler 程序添加一个Try ...Catch 块。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 层异常 教程中以几乎完全相同的形式使用这些代码 —— 提供此级别的详细信息:

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 块调用DisplayExceptionDetails 方法来传递捕获的Exception 实例 (ex) 。

准备好 Try ...Catch 块之后,可以提供给用户更丰富的错误信息,如图 4 和图 5 所示。请注意,出现异常后,DataList 仍处于编辑模式。这是因为一旦出现异常,控制流程立即被重定向到 Catch 块,绕过将 DataList 返回到它的非编辑状态的代码。

图4 :如果用户漏填了必需的字段,将显示错误消息

图5 :当输入一个负的价格时,将显示错误消息

小结

GridView 和 ObjectDataSource 提供post 级 event handler(该程序包含关于更新和删除工作流期间引发的任何异常的信息)和一些属性(该属性可以被设置用于指示异常是否被处理)。但是这些功能在使用 DataList 和直接使用 BLL 时却可不用。相反,我们负责实现异常处理。

本教程中,我们将了解怎样通过向 UpdateCommand event handler 添加 Try ... Catch 块来向可编辑 DataList 的更新工作流添加异常处理。如果在更新工作流期间引发异常,Catch 块的代码将执行,在 ExceptionDetails Label 中显示有用的信息。

现在,DataList 没有首先做任何努力来防止异常发生。虽然知道负的价格将导致异常,但我们尚未添加任何功能以主动防止用户输入这样无效的输入。在下一篇教程中,我们将了解怎样通过在 EditItemTemplate 中添加校验控件来帮助减少由非法用户输入引发的异常。

快乐编程!

 

下一篇教程