在ASP.NET 2.0中操作数据之十八:在ASP.NET页面中处理BLL/DAL层的异常

导言

在一个使用了分层体系架构的ASP.NET web应用系统里处理数据,一般遵循以下几步:

1.确定业务逻辑层需要调用哪个方法,并且需要出入哪些参数。这些参数可以通过硬编码设置,程序自动设定,或者由用户输入。
2.调用此方法。
3.处理结果。当调用一个返回数据的BLL方法时,这包括绑定数据到Data Web服务器控件。而对于修改数据的BLL方法而言,这包括基于返回值的基础上执行某些动作,或者适当地处理在第二步中引发的异常。

  正如我们在前一节里看到的,无论ObjectDataSource控件还是数据Web服务器控件,都为第1和第3步提供了可扩展性。例如GridView控件,触发它的RowUpdating事件之前把它的字段的值赋值到ObjectDataSource的UpdateParameters集合;在ObjectDataSource完成它的操作之后触发RowUpdated事件。

  我们已经检测到第1步中触发的事件,并且看过了如何使用它们实现自定义出入参数或者取消操作。这一节我们将把我们的注意力转到操作完成后所触发的事件。通过这些post级的event handler和其它,可以判断在操作过程中是否产生了一个异常,并且适当地处理它,在屏幕中显示友好的错误信息要优于转到ASP.NET的默认错误处理页。

  为了举例说明这些post级事件的工作方式,让我们创建一个页面,它在一个可编辑的GridView中列出产品信息。当更新一个产品时,如果引发了一个异常,我们的ASP.NET页面会在GridView控件的上方显示一个简短的信息,说明出现了一个问题。好吧,让我们开始!

第一步: 为产品创建一个可编辑的GridView

  这一节里我们创建一个可编辑的GridView,它仅仅包含两个的字段,ProductName和UnitPrice。这需要为ProductsBLL类的UpdateProduct方法增加一个额外的重载,它仅仅接受3个输入参数(product's name,unit price,和ID),相对于接受每一个产品的字段的方法。在本节里让我们再一次练习一下这些技巧,创建一个可编辑的GridView,它显示产品的name、quantity per unit、unit price、和units in stock,但仅仅允许name,unit price,和units in stock可编辑。

  为了提供这个场景,我们需要对UpdateProduct方法的另一个重载,它接收4个参数: product's name,unit price,units in stock和ID。在ProductsBLL类中添加下面这个方法:

[System.ComponentModel.DataObjectMethodAttribute(
  System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
  int productID)
{
  Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
  if (products.Count == 0)
    // no matching record found, return false
    return false;
  Northwind.ProductsRow product = products[0];
  product.ProductName = productName;
  if (unitPrice == null) product.SetUnitPriceNull();
   else product.UnitPrice = unitPrice.Value;
  if (unitsInStock == null) product.SetUnitsInStockNull();
   else product.UnitsInStock = unitsInStock.Value;
  // Update the product record
  int rowsAffected = Adapter.Update(product);
  // Return true if precisely one row was updated, otherwise false
  return rowsAffected == 1;
}

  完成了此方法后,我们可以创建一个ASP.NET页面,它允许编辑这四个产品字段。打开EditInsertDelete文件夹里的ErrorHandling.aspx页面,并通过设计器添加一个GridView控件到页面中。绑定这个GridView到一个新的ObjectDataSource控件,映射Select()方法到ProductsBLL类的GetProducts()方法,方法Update()映射到刚刚创建的UpdateProduct重载。

图1: 使用UpdateProduct方法重载,它接受四个输入参数

  这将创建一个ObjectDataSource,它包含四个参数的UpdateParameters集合,还有一个一个GridView,它包含产品的每一个字段。ObjectDataSource的声明标记给OldValuesParameterFormatString属性赋值为original_{0},它将引发一个异常,因为我们的BLL类没有一个名为original_productID的输入参数需要传入。别忘了从声明语法里把这些设置通通删除(或者把它们设置为默认值:{0})。

  然后,减少GridView的绑定列,仅包含ProductName,QuantityPerUnit,UnitPrice和UnitsInStock这几列。随意设置一些你认为必要的字段级的格式(例如更改HeaderText属性)。

  在之前的章节里我们已经看过了如何在只读和编辑两种模式下格式化UnitPrice绑定列为货币格式。在这里我们同样这样做。这需要设置绑定列的DataFormatString属性为{0:c},它的HtmlEncode属性为false,还有它的ApplyFormatInEditMode属性为true,如图2所示。

图2: UnitPrice绑定列配置为显示一个货币金额

  要在编辑界面将UnitPrice格式化为货币,这需要为GridView的RowUpdating事件创建一个事件处理,它将一个货币格式的字符串转换成decimal。回想上一节,RowUpdating事件处理也用来检测并确保用户输入的是一个UnitPrice的值。不过,本节我们可以允许用户忽略price列。

protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
  if (e.NewValues["UnitPrice"] != null)
    e.NewValues["UnitPrice"] =decimal.Parse(e.NewValues["UnitPrice"].ToString(),
      System.Globalization.NumberStyles.Currency);
}

  我们的GridView包含一个QuantityPerUnit绑定列,但它仅仅用作显示,不能被用户编辑。为了实现这一点,只需要简单地将该绑定列的ReadOnly属性设置为true。

图 3: 设置QuantityPerUnit绑定列为只读

最后,从GridView的智能标记里勾选上“启用编辑”。完成了这些步骤后,ErrorHandling.aspx页面在设计视图里将如图4所示。

图 4: 删除除了必需的绑定列之外的其它列并启用编辑

在这里我们显示产品的所有列,ProductName、QuantityPerUnit、UnitPrice和UnitsInStock;不过仅仅ProductName、UnitPrice和UnitsInStock这几列可以编辑。

图 5: 用户现在可以很方便地编辑Products' Names、Prices和Units In Stock字段

第二步:适当地处理DAL层异常

  这时我们的可编辑的GridView在用户输入合法的product's name、price和units in stock时表现极佳,输入不合法的值时则导致一个异常。例如,遗漏了ProductName值则引发抛出一个NoNullAllowedException异常,因为ProdcutsRow类的ProductName属性设置了它的AllowDBNull属性为false;如果数据库不正常运作,则在试图连接数据库时通过TableAdapter抛出一个SqlException异常。没有任何的动作,这些异常都会从数据访问层冒出到业务逻辑层,然后到ASP.NET页面,最后到ASP.NET运行时。

  取决于你的web应用程序如何配置以及是否从localhost访问该应用,一个未经处理的异常会出现在一类服务器错误处理页,一个详细的错误报表,或者一个对用户友好的web页面。查看Web Application Error Handling in ASP.NET 和 customErrors Element 获得更多的关于ASP.NET页面如何响应一个未捕获的异常的相关信息。
图6展示的是试图不指定ProductName的值更新一个产品时屏幕的状况。这显示的是通过localhost访问时的默认详细错误报表。

图 6: 省略Product's Name将显示异常明细

  虽然这样的异常明细在我们测试应用程序的时候是很有用的,然而当一个最终用户面对这样的异常呈现时却是无所适从的。一个最终用户很可能并不知道NoNullAllowedException是什么,或者它是如何引起的。更好的方法是呈现给用户一个更友好的信息说明试图更新产品时出现了问题。

  如果在执行这项操作时出现了一个异常,ObjectDataSource 和数据Web控件的post级事件都提供了发现并不让它出现在ASP.NET运行时的方法。在我们的例子里,让我们为GridView的RowUpdated事件创建一个事件处理程序,它判断是否激发了一个异常,如果是,则在一个Label服务器控件中显示异常详细信息。

  首先,添加一个Label控件到ASP.NET页面,设置它的ID属性为ExceptionDetails并清空它的Text属性。为了吸引用户的实现到此信息,设置其CssClass为Warning,这是我们在之前的章节里添加到Styles.css文件的一个CSS类别。记得这个CSS类别让Label的text显示为红色、斜体、加粗的较大的字体。

图 7: 添加一个Label服务器控件到页面

因为我们希望这个Label控件仅在异常出现时显示,在Page_Load事件处理中设置它的Visible属性为false:

protected void Page_Load(object sender, EventArgs e)
{
  ExceptionDetails.Visible = false;
}

  通过这些代码,当第一次访问页面和随后的回传后,ExceptionDetails控件的Visible属性都将被设置为false。当在GridView的RowUpdated事件处理程序中检测到一个DAL/BLL层的异常时,我们将设置ExceptionDetails控件的Visible属性为true。因为页面生命周期里Web服务器控件的事件处理出现在Page_Load事件处理之后,该Label将会显示。不过,下一次回传,Page_Load事件处理将重新将Visible属性设置回false,再次隐藏它。

  注意: 我们也可以不必在Page_Load里设置ExceptionDetails控件的Visible属性,作为另一种选择,可以在声明语法里设置其Visible属性为false并禁用视图状态(设置它的EnableViewState属性为false)。我们将在以后的章节里使用这种方法。

  通过添加这个Label控件,我们下一步是为GridView的RowUpdated事件添加一个事件处理程序。在设计视图中选中GridView控件,打开属性窗口,点击黄色闪电状图标,列出GridView的所有事件。在GridView的RowUpdating事件里我们可以看到已经存在一个入口,因为我们在本节较早的时候已经为此事件创建了一个事件处理程序。为RowUpdated事件创建一个事件处理程序。

图 8: 为GridView的事件创建一个事件处理

  注意: 你也可以通过代码隐藏文件顶处的下拉列表创建这个事件处理。从左边的下拉列表中选择这个GridView控件,并从右边的下拉列表中选择RowUpdated事件。

创建这个事件处理将添加下面这些代码到ASP.NET页面的代码隐藏类中:

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}

这个事件处理程序的第二个输入参数是一个GridViewUpdatedEventArgs类型的对象,它有三个关于处理异常的属性:

·Exception –获取更新操作过程中引发的异常;如果没有抛出异常,该属性的值为null
·ExceptionHandled –获取或设置一个值,它指示在更新操作过程中所引发的异常是否已在RowUpdated事件处理程序中得到处理;如果设为false(默认值),该异常将被重新引发,漏出到ASP.NET运行时
·KeepInEditMode – 如果设置为true,GridView当前编辑行将维持在编辑模式;如果设置为false(默认值),当前行将恢复到只读模式

那么我们的代码应该检测Exception是否为null,不是null则意味着执行此操作时引发了一个异常。如果是这样,我们则希望:

·在ExceptionDetails控件中显示一个对用户友好的提示信息
·指示异常已经被处理
·让当前行保持编辑模式

下面的代码实现了上述的目的:

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
  if (e.Exception != null)
  {
    // Display a user-friendly message
    ExceptionDetails.Visible = true;
    ExceptionDetails.Text = "There was a problem updating the product. ";
    if (e.Exception.InnerException != null)
    {
      Exception inner = e.Exception.InnerException;
      if (inner is System.Data.Common.DbException)
        ExceptionDetails.Text +=
          "Our database is currently experiencing problems." +
          "Please try again later.";
      else if (inner is NoNullAllowedException)
        ExceptionDetails.Text +=
          "There are one or more required fields that are missing.";
      else if (inner is ArgumentException)
      {
        string paramName = ((ArgumentException)inner).ParamName;
        ExceptionDetails.Text +=
          string.Concat("The ", paramName, " value is illegal.");
      }
      else if (inner is ApplicationException)
        ExceptionDetails.Text += inner.Message;
    }
    // Indicate that the exception has been handled
    e.ExceptionHandled = true;
    // Keep the row in edit mode
    e.KeepInEditMode = true;
  }
}

  在这个事件处理程序中,首先检测e.Exception是否为null。如果不是,设置ExceptionDetails控件的Visible属性为true、设置它的Text属性为“There was a problem updating the product.”。当前抛出的异常详细信息则保存在e.Exception对象的InnerException属性里。检查这个内部异常,如果它是特定的类型,则把一些额外的有用的信息附加到ExceptionDetails标签的Text属性。最后,ExceptionHandled和KeepInEditMode属性都设置为true。

图9展示的是遗漏了产品名称时的页面的截屏;图10则显示输入一个不合法的UnitPrice值(-50)时的结果。

图 9: ProductName绑定列必须包含一个值

图 10: UnitPrice值不接受负数

  通过设置属性为,事件处理程序指示该异常已经被处理。因此,这个异常不会传送到ASP.NET运行时。
  注意: 图9和图10显示了一种得体的方式处理不正确的用户输入所引发的异常。可是,更理想地,这些不正确的输入不应该到达业务逻辑层,因为ASP.NET页面应该在调用ProductsBLL类的UpdateProduct方法之前就确保用户的输入是有效的。我们在下一节里将会看看如何添加validation控件到编辑和插入界面从而保证提交到业务逻辑层的数据遵循业务规则。validation控件不但可以阻止调用UpdateProduct方法直到用户提供有效的数据,还可以为定位数据输入问题提供一个更充满提示性的用户体验。

第三步: 适当地处理BLL层异常

  当插入、更新或删除数据时,面对一个数据相关的错误时数据访问层会抛出一个异常。数据库可能未连线,一个必需的数据库表字段可能未指定值,或者违反了某个表间约束。除了确定的数据相关的异常外,业务逻辑层也使用异常指示违反了业务逻辑。在创建一个业务逻辑层 这一节里,作为例子,我们添加了一个业务规则检查最初的UpdateProduct重载。特别地,如果用户标记一个产品为停止供应,我们要求这个产品不能是该供应商唯一供应的产品。如果违反了这个条件,抛出一个ApplicationException异常。

  在这一节里,我们给UpdateProduct重载增加一个业务规则:禁止把UnitPrice字段的值设置为超过原来的两倍。为了实现这一点,调整UpdateProduct重载以使它可以执行这个检查并且在违反该规则时抛出一个ApplicationException异常。此更新方法如下:

public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
  int productID)
{
  Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
  if (products.Count == 0)
    // no matching record found, return false
    return false;
  Northwind.ProductsRow product = products[0];
  // Make sure the price has not more than doubled
  if (unitPrice != null && !product.IsUnitPriceNull())
    if (unitPrice > product.UnitPrice * 2)
     throw new ApplicationException(
      "When updating a product price," +
      " the new price cannot exceed twice the original price.");
  product.ProductName = productName;
  if (unitPrice == null) product.SetUnitPriceNull();
   else product.UnitPrice = unitPrice.Value;
  if (unitsInStock == null) product.SetUnitsInStockNull();
   else product.UnitsInStock = unitsInStock.Value;
  // Update the product record
  int rowsAffected = Adapter.Update(product);
  // Return true if precisely one row was updated, otherwise false
  return rowsAffected == 1;
}

  通过这个修改,任何超过现有价格两倍的价格更新都回引发一个ApplicationException异常被抛出。就像DAL中引发的异常一样,这个BLL引发的ApplicationException异常可以在GridView的RowUpdated事件处理程序中被侦测并处理。实际上,我们已有的RowUpdated事件处理程序的代码可以正确地发现到这个异常并显示ApplicationException的Message属性的值。图11显示的是当一个用户试图将产品“Chai”的价格更新为$50.00时的截屏,这超过了它原有价格$19.95的两倍。

图 11: 这个业务规则不接受价格增长超出产品现有价格的两倍

注意: 理想化地我们的业务规则不应该在UpdateProduct方法重载里而应该在一个公共的方法中。这留作读者练习。

总结

  在插入、更新或删除操作的过程中,数据Web控件和ObjectDataSource控件都包含了pre- 和post-级的事件,它们记录着当前的操作。正如我们在本节和前面的一节里所看到的,当使用一个可编辑的GridView时,GridView的RowUpdating事件在ObjectDataSource的Updating事件之后触发,此时update命令发送到ObjectDataSource的隐含对象。完成了此操作,在GridView的RowUpdated事件之后,触发ObjectDataSource的Updated事件。

  我们可以为这些发生在操作之前的事件创建事件处理程序,目的是自定义输入参数;为发生在
操作之后的事件创建事件处理,目的是检测和相应操作的结果。Post-level的事件处理程序通常用作侦测在操作过程中是否出现了一个异常。当面对一个异常时,这些post-level的事件处理程序可以随意地处理该异常。在本节里我们看过了如何处理这样的一个异常,显示一个友好的错误提示信息。

  在下一节里我们将看看如何降低因数据格式的问题引起异常的可能性(例如在UnitPrice输入一个负数)。特别地,我们将看看如何添加validation控件到编辑和插入界面。

祝编程快乐!

作者简介

Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用微软Web技术。Scott是个独立的技 术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。

(0)

相关推荐

  • Asp.net Mvc 身份验证、异常处理、权限验证(拦截器)实现代码

    1.用户登录 验证用户是否登录成功步骤直接忽略,用户登录成功后怎么保存当前用户登录信息(session,cookie),本文介绍的是身份验证(其实就是基于cookie)的,下面看看代码. 引入命名空间 using System.Web.Security; 复制代码 代码如下: Users ModelUser = new Users() { ID = 10000, Name = UserName, UserName = UserName, PassWord = PassWord, Roles =

  • 在 .NET Framework 2.0 中未处理的异常导致基于 ASP.NET 的应用程序意外退出

    但是,系统日志中可能会记录类似于以下内容的事件消息: 事件类型:警告 事件来源:W3SVC 事件类别:无 事件 ID: 1009 日期: 9/28/2005 时间:3:18:11 PM 用户:N/A 计算机:IIS-SERVER 描述: 为应用程序池"DefaultAppPool"提供服务的进程意外终止.进程 ID 是"2548".进程退出代码是"0xe0434f4d". 而且,应用程序日志中可能会记录类似于以下内容的事件消息: 事件类型:错误

  • asp.net开发中常见公共捕获异常方式总结(附源码)

    本文实例总结了asp.net开发中常见公共捕获异常方式.分享给大家供大家参考,具体如下: 前言:在实际开发过程中,对于一个应用系统来说,应该有自己的一套成熟的异常处理框架,这样当异常发生时,也能得到统一的处理风格,将异常信息优雅地反馈给开发人员和用户.我们都知道,.net的异常处理是按照"异常链"的方式从底层向高层逐层抛出,如果不能尽可能地早判断异常发生的边界并捕获异常,CLR会自动帮我们处理,但是这样系统的开销是非常大的,所以异常处理的一个重要原则是"早发现早抛出早处理&q

  • 在ASP.NET 2.0中操作数据之三十八:处理BLL和DAL的异常

    导言 在DataList里编辑和删除数据概述里,我们创建了一个提供简单编辑和删除功能的DataList.虽然功能上已经完整了,但是对用户来说是不友好的.因为所有在编辑和删除过程中产生的异常都是未处理的.比如,遗漏了输入product的name,或者编辑product时在price里输入"Very affordable!",都会抛出异常.而由于在代码里未捕捉这些异常,页面会显示ASP.NET运行时的详细错误信息. 如我们在在ASP.NET页面中处理BLL/DAL层的异常里看到的,如果BL

  • asp.net服务器上几种常见异常的解决方案.

    如下 (1)配置Asp.net站点ISS报出:服务器应用程序不可用.具体异常信息如下: 服务器应用程序不可用 您试图在此 Web 服务器上访问的 Web 应用程序当前不可用.请点击 Web 浏览器中的"刷新"按钮重试您的请求. 管理员注意事项: 详述此特定请求失败原因的错误信息可在 Web 服务器的系统事件日志中找到.请检查此日志项以查明导致该错误发生的原因. 我检查ISS上其他的配置.发现全部都是Asp编写的网站.属性中查看运行的环境竟是Asp.net Framework 1.1版本

  • asp.net Http异常eurl.axd出错信息解决方法

    错误发生的原因是当ASP.NET检测到Web站点配置为使用ASP.NET 4.0,本地ASP.NET 4.0 的组件会传递一个不能扩展的 URL到ASP.NET的管理程序作进一步处理.但是,如果一个低于ASP.NET 4.0 的网站配置为使用ASP.NET 2.0,处理这样不能扩展的 URL 时,URL的修改结果中会包含字符串"eurl.axd",修改后的URL会被发送到 ASP.NET 2.0应用程序. ASP.NET 2.0中是不能识别"eurl.axd"的.因

  • ASP.NET mvc异常处理的方法示例介绍

    1.首先常见保存异常的类(就是将异常信息写入到文件中去) 复制代码 代码如下: public class LogManager { private string logFilePath = string.Empty; public LogManager(string logFilePath) { this.logFilePath = logFilePath; FileInfo file = new FileInfo(logFilePath); if (!file.Exists) { file.C

  • asp.net 错误:0x8007000B 异常的解决方法

    在Asp.net里调用非托管的.dll文件时,出现"An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)"这样的错误提示. 在c# winform的程序里大概知道该错误:0x8007000B是由于本机操作系统是64位,调用的DLL是32位而产生的错误,所以只需将网站的连接池的设置改成支持32位程序运行就可以解决问题了. 1.打开IIS管理器

  • ASP.NET生成eurl.axd Http异常错误的处理方法

    在IIS6中同时启用了ASP.NET 2.0 和 ASP.NET 4.0 后,网站程序可能会出现如下错误:" System.Web.HttpException: Path '//eurl.axd/' was not found. " 错误发生的原因是当ASP.NET检测到Web站点配置为使用ASP.NET 4.0,本地ASP.NET 4.0 的组件会传递一个不能扩展的 URL到ASP.NET的管理程序作进一步处理.但是,如果一个低于ASP.NET 4.0 的网站配置为使用ASP.NET

  • ASP.NET MVC异常处理模块详解

    一.前言 异常处理是每个系统必不可少的一个重要部分,它可以让我们的程序在发生错误时友好地提示.记录错误信息,更重要的是不破坏正常的数据和影响系统运行.异常处理应该是一个横切点,所谓横切点就是各个部分都会使用到它,无论是分层中的哪一个层,还是具体的哪个业务逻辑模块,所关注的都是一样的.所以,横切关注点我们会统一在一个地方进行处理.无论是MVC还是WebForm都提供了这样实现,让我们可以集中处理异常. 在MVC中,在FilterConfig中,已经默认帮我们注册了一个HandleErrorAttr

随机推荐