在ASP.NET 2.0中操作数据之三十八:处理BLL和DAL的异常
导言
在datalist里编辑和删除数据概述里,我们创建了一个提供简单编辑和删除功能的datalist。虽然功能上已经完整了,但是对用户来说是不友好的。因为所有在编辑和删除过程中产生的异常都是未处理的。比如,遗漏了输入product的name,或者编辑product时在price里输入“very affordable!”,都会抛出异常。而由于在代码里未捕捉这些异常,页面会显示asp.net运行时的详细错误信息。
如我们在在asp.net页面中处理bll/dal层的异常里看到的,如果bll或dal里发生异常,详细的信息会返回到objectdatasource,然后再到gridview。我们已经学习了如何优雅的处理这些异常:为objectdatasource或gridview创建updated或rowupdated事件处理,检查异常,然后指明异常被处理。
然而在使用datalist时,我们并没有通过objectdatasource来更新和删除数据。我们是直接通过bll来实现的。为了检测到 bll或dal的异常,我们需要在asp.net页里写异常处理代码。本章我们将学习在使用datalist编辑时如何巧妙的处理异常。
注意:在datalist里编辑和删除数据概述里,我们讨论了几种不同的编辑和删除数据的方法,其中一些会涉及到使用objectdatasource来编辑和删除。如果你用这些技术的话,你可以直接通过objectdatasource的updated或deleted 事件处理中处理这些异常。
第一步: 创建一个可编辑的datalist
首先创建一个可编辑的datalist。打开editdeletedatalist文件夹下的errorhandling.aspx页,添加一个id为products的datalist和一个名为productsdatasource的objectdatasouce。在select标签下选择productsbll类的getproducts()方法。在insert,update和delete标签里选择none.
图 1: 配置objectdatasource
完成objectdatasouce后,visual studio会自动创建一个itemtemplate。用显示每个product的name和price并包含一个edit button的itemtemplate代替它,然后创建一个用textbox显示name和price,并包含一个update button和cancel button的edititemtemplate。最后将datalist的repeatcolumns属性设为2。
做完这些后,你的声明代码应该和下面的差不多。检查并确保edit,cancel和update button的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的view state必须开启。浏览一下页面,见图2。
图 2: 每个product 都包含一个edit button
现在edit button只是引起一个postback —还不能将product变成可编辑的。为了实现编辑功能,我们需要为editcommand,cancelcommand和updatecommand创建事件处理。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事件处理稍微麻烦一点。它需要从datakey集合里读取被编辑的product的productid,和edititemtemplate里的textbox里的product的name和price,然后调用productsbll类的updateproduct方法,最后返回到datalist编辑前的状态。
我们在这里使用 在datalist里编辑和删除数据概述 里的updatecommand事件处理代码。
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(); }
在有非法输入的时候— 可能是不正确的price格式,比如“-$5.00”,或者忽略了product的name— 就会引起异常。由于updatecommand事件处理还没有处理异常,页面会出现asp.net运行时错误。见图3。
图 3: 未处理异常发生时,用户会看到这样的错误页面
第二步: 在updatecommand event handler里处理异常
更新流程中,异常可能发生在updatecommand事件处理,或bll或dal里。比如,如果用户输入了一个“太贵”的价格,updatecommand 事件处理里的decimal.parse 会抛出formatexception 异常。如果用户忽略了product的name或者price是一个负数,dal会抛出异常。
当异常发生时,我们希望显示自己定义的信息。添加一个id为exceptiondetails的label控件到页面上,通过设置cssclass属性为warning css类来将text设置为红色,特大,加粗的意大利字体。这个类在styles.css文件里定义。
异常发生时,我们只希望这个 label显示一次。也就是说,在后面postback的时候,label的警告信息需要隐藏起来。这个可以通过清除label的text属性或者将visible属性设为false(在page_load里)(如我们在在asp.net页面中处理bll/dal层的异常 里做的那样)或者禁用label的view state来实现。我们这里用后一种方法。
<asp:label id="exceptiondetails" enableviewstate="false" cssclass="warning" runat="server" />
异常发生时,我们将异常的信息显示在label的text属性上。由于view state被禁用了,后面再postback的话,text属性会自动的丢失,回到缺省值(空字符串),这样就隐藏了警告信息。
异常发生时将信息显示在页面上,我们需要在updatecommand事件处理里添加try....catch块。try的那部分包含可能抛出异常的代码,catch部分包含当出现异常时需要执行的代码。更多的try..catch块信息参考exception handling fundamentals 。
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; }
最后仅仅只需要调用displayexceptiondetails方法。
完成try..catch后,用户会看到更有用的错误信息,如图4,5所示。注意在异常出现时,datalist仍然保持在可编辑的模式下。这是因为异常发生时,控制流程马上转到catch块里,忽略了将datalist转到编辑前状态的代码。
图 4: 用户忽略了必填字段时的错误信息
图 5: 输入非法价格时的错误信息
总结
gridview和objectdatasource提供了在更新和删除过程中包含异常信息的事件处理和指明异常是否被处理的属性。而直接使用bll的datalist没有这些特性,我们需要亲自处理这些异常。
本章我们学习了如何在datalist的更新过程中在updatecommand里添加try...catch块来处理异常。如果有任何异常发生,catch块里的代码会执行,将错误信息显示在exceptiondetails label上。
现在,datalist并没有在第一时间阻止异常的发生。尽管我们知道一个负的price值会产生异常,我们也没有添加阻止用户输入这样的值的功能。在后面的几章里,我们将学习如何通过在edititemtemplate里添加验证控件来验证用户的输入从而减少这些异常的发生。
祝编程愉快!
作者简介
本系列教程作者 scott mitchell,著有六本asp/asp.net方面的书,是4guysfromrolla.com的创始人,自1998年以来一直应用 微软web技术。大家可以点击查看全部教程《[翻译]scott mitchell 的asp.net 2.0数据教程》,希望对大家的学习asp.net有所帮助。