導(dǎo)言
在一個使用了分層體系架構(gòu)的ASP.NET web應(yīng)用系統(tǒng)里處理數(shù)據(jù),一般遵循以下幾步:
1.確定業(yè)務(wù)邏輯層需要調(diào)用哪個方法,并且需要出入哪些參數(shù)。這些參數(shù)可以通過硬編碼設(shè)置,程序自動設(shè)定,或者由用戶輸入。
2.調(diào)用此方法。
3.處理結(jié)果。當(dāng)調(diào)用一個返回數(shù)據(jù)的BLL方法時,這包括綁定數(shù)據(jù)到Data Web服務(wù)器控件。而對于修改數(shù)據(jù)的BLL方法而言,這包括基于返回值的基礎(chǔ)上執(zhí)行某些動作,或者適當(dāng)?shù)靥幚碓诘诙街幸l(fā)的異常。
正如我們在前一節(jié)里看到的,無論ObjectDataSource控件還是數(shù)據(jù)Web服務(wù)器控件,都為第1和第3步提供了可擴(kuò)展性。例如GridView控件,觸發(fā)它的RowUpdating事件之前把它的字段的值賦值到ObjectDataSource的UpdateParameters集合;在ObjectDataSource完成它的操作之后觸發(fā)RowUpdated事件。
我們已經(jīng)檢測到第1步中觸發(fā)的事件,并且看過了如何使用它們實現(xiàn)自定義出入?yún)?shù)或者取消操作。這一節(jié)我們將把我們的注意力轉(zhuǎn)到操作完成后所觸發(fā)的事件。通過這些post級的event handler和其它,可以判斷在操作過程中是否產(chǎn)生了一個異常,并且適當(dāng)?shù)靥幚硭?,在屏幕中顯示友好的錯誤信息要優(yōu)于轉(zhuǎn)到ASP.NET的默認(rèn)錯誤處理頁。
為了舉例說明這些post級事件的工作方式,讓我們創(chuàng)建一個頁面,它在一個可編輯的GridView中列出產(chǎn)品信息。當(dāng)更新一個產(chǎn)品時,如果引發(fā)了一個異常,我們的ASP.NET頁面會在GridView控件的上方顯示一個簡短的信息,說明出現(xiàn)了一個問題。好吧,讓我們開始!
第一步: 為產(chǎn)品創(chuàng)建一個可編輯的GridView
這一節(jié)里我們創(chuàng)建一個可編輯的GridView,它僅僅包含兩個的字段,ProductName和UnitPrice。這需要為ProductsBLL類的UpdateProduct方法增加一個額外的重載,它僅僅接受3個輸入?yún)?shù)(product's name,unit price,和ID),相對于接受每一個產(chǎn)品的字段的方法。在本節(jié)里讓我們再一次練習(xí)一下這些技巧,創(chuàng)建一個可編輯的GridView,它顯示產(chǎn)品的name、quantity per unit、unit price、和units in stock,但僅僅允許name,unit price,和units in stock可編輯。
為了提供這個場景,我們需要對UpdateProduct方法的另一個重載,它接收4個參數(shù): 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;
}
完成了此方法后,我們可以創(chuàng)建一個ASP.NET頁面,它允許編輯這四個產(chǎn)品字段。打開EditInsertDelete文件夾里的ErrorHandling.aspx頁面,并通過設(shè)計器添加一個GridView控件到頁面中。綁定這個GridView到一個新的ObjectDataSource控件,映射Select()方法到ProductsBLL類的GetProducts()方法,方法Update()映射到剛剛創(chuàng)建的UpdateProduct重載。
圖1: 使用UpdateProduct方法重載,它接受四個輸入?yún)?shù)
這將創(chuàng)建一個ObjectDataSource,它包含四個參數(shù)的UpdateParameters集合,還有一個一個GridView,它包含產(chǎn)品的每一個字段。ObjectDataSource的聲明標(biāo)記給OldValuesParameterFormatString屬性賦值為original_{0},它將引發(fā)一個異常,因為我們的BLL類沒有一個名為original_productID的輸入?yún)?shù)需要傳入。別忘了從聲明語法里把這些設(shè)置通通刪除(或者把它們設(shè)置為默認(rèn)值:{0})。
然后,減少GridView的綁定列,僅包含ProductName,QuantityPerUnit,UnitPrice和UnitsInStock這幾列。隨意設(shè)置一些你認(rèn)為必要的字段級的格式(例如更改HeaderText屬性)。
在之前的章節(jié)里我們已經(jīng)看過了如何在只讀和編輯兩種模式下格式化UnitPrice綁定列為貨幣格式。在這里我們同樣這樣做。這需要設(shè)置綁定列的DataFormatString屬性為{0:c},它的HtmlEncode屬性為false,還有它的ApplyFormatInEditMode屬性為true,如圖2所示。
圖2: UnitPrice綁定列配置為顯示一個貨幣金額
要在編輯界面將UnitPrice格式化為貨幣,這需要為GridView的RowUpdating事件創(chuàng)建一個事件處理,它將一個貨幣格式的字符串轉(zhuǎn)換成decimal?;叵肷弦还?jié),RowUpdating事件處理也用來檢測并確保用戶輸入的是一個UnitPrice的值。不過,本節(jié)我們可以允許用戶忽略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綁定列,但它僅僅用作顯示,不能被用戶編輯。為了實現(xiàn)這一點(diǎn),只需要簡單地將該綁定列的ReadOnly屬性設(shè)置為true。
圖 3: 設(shè)置QuantityPerUnit綁定列為只讀
最后,從GridView的智能標(biāo)記里勾選上“啟用編輯”。完成了這些步驟后,ErrorHandling.aspx頁面在設(shè)計視圖里將如圖4所示。
圖 4: 刪除除了必需的綁定列之外的其它列并啟用編輯
在這里我們顯示產(chǎn)品的所有列,ProductName、QuantityPerUnit、UnitPrice和UnitsInStock;不過僅僅ProductName、UnitPrice和UnitsInStock這幾列可以編輯。
圖 5: 用戶現(xiàn)在可以很方便地編輯Products' Names、Prices和Units In Stock字段
第二步:適當(dāng)?shù)靥幚鞤AL層異常
這時我們的可編輯的GridView在用戶輸入合法的product's name、price和units in stock時表現(xiàn)極佳,輸入不合法的值時則導(dǎo)致一個異常。例如,遺漏了ProductName值則引發(fā)拋出一個NoNullAllowedException異常,因為ProdcutsRow類的ProductName屬性設(shè)置了它的AllowDBNull屬性為false;如果數(shù)據(jù)庫不正常運(yùn)作,則在試圖連接數(shù)據(jù)庫時通過TableAdapter拋出一個SqlException異常。沒有任何的動作,這些異常都會從數(shù)據(jù)訪問層冒出到業(yè)務(wù)邏輯層,然后到ASP.NET頁面,最后到ASP.NET運(yùn)行時。
取決于你的web應(yīng)用程序如何配置以及是否從localhost訪問該應(yīng)用,一個未經(jīng)處理的異常會出現(xiàn)在一類服務(wù)器錯誤處理頁,一個詳細(xì)的錯誤報表,或者一個對用戶友好的web頁面。查看Web Application Error Handling in ASP.NET 和 customErrors Element 獲得更多的關(guān)于ASP.NET頁面如何響應(yīng)一個未捕獲的異常的相關(guān)信息。
圖6展示的是試圖不指定ProductName的值更新一個產(chǎn)品時屏幕的狀況。這顯示的是通過localhost訪問時的默認(rèn)詳細(xì)錯誤報表。
圖 6: 省略Product's Name將顯示異常明細(xì)
雖然這樣的異常明細(xì)在我們測試應(yīng)用程序的時候是很有用的,然而當(dāng)一個最終用戶面對這樣的異常呈現(xiàn)時卻是無所適從的。一個最終用戶很可能并不知道NoNullAllowedException是什么,或者它是如何引起的。更好的方法是呈現(xiàn)給用戶一個更友好的信息說明試圖更新產(chǎn)品時出現(xiàn)了問題。
如果在執(zhí)行這項操作時出現(xiàn)了一個異常,ObjectDataSource 和數(shù)據(jù)Web控件的post級事件都提供了發(fā)現(xiàn)并不讓它出現(xiàn)在ASP.NET運(yùn)行時的方法。在我們的例子里,讓我們?yōu)镚ridView的RowUpdated事件創(chuàng)建一個事件處理程序,它判斷是否激發(fā)了一個異常,如果是,則在一個Label服務(wù)器控件中顯示異常詳細(xì)信息。
首先,添加一個Label控件到ASP.NET頁面,設(shè)置它的ID屬性為ExceptionDetails并清空它的Text屬性。為了吸引用戶的實現(xiàn)到此信息,設(shè)置其CssClass為Warning,這是我們在之前的章節(jié)里添加到Styles.css文件的一個CSS類別。記得這個CSS類別讓Label的text顯示為紅色、斜體、加粗的較大的字體。
圖 7: 添加一個Label服務(wù)器控件到頁面
因為我們希望這個Label控件僅在異常出現(xiàn)時顯示,在Page_Load事件處理中設(shè)置它的Visible屬性為false:
protected void Page_Load(object sender, EventArgs e)
{
ExceptionDetails.Visible = false;
}
通過這些代碼,當(dāng)?shù)谝淮卧L問頁面和隨后的回傳后,ExceptionDetails控件的Visible屬性都將被設(shè)置為false。當(dāng)在GridView的RowUpdated事件處理程序中檢測到一個DAL/BLL層的異常時,我們將設(shè)置ExceptionDetails控件的Visible屬性為true。因為頁面生命周期里Web服務(wù)器控件的事件處理出現(xiàn)在Page_Load事件處理之后,該Label將會顯示。不過,下一次回傳,Page_Load事件處理將重新將Visible屬性設(shè)置回false,再次隱藏它。
注意: 我們也可以不必在Page_Load里設(shè)置ExceptionDetails控件的Visible屬性,作為另一種選擇,可以在聲明語法里設(shè)置其Visible屬性為false并禁用視圖狀態(tài)(設(shè)置它的EnableViewState屬性為false)。我們將在以后的章節(jié)里使用這種方法。
通過添加這個Label控件,我們下一步是為GridView的RowUpdated事件添加一個事件處理程序。在設(shè)計視圖中選中GridView控件,打開屬性窗口,點(diǎn)擊黃色閃電狀圖標(biāo),列出GridView的所有事件。在GridView的RowUpdating事件里我們可以看到已經(jīng)存在一個入口,因為我們在本節(jié)較早的時候已經(jīng)為此事件創(chuàng)建了一個事件處理程序。為RowUpdated事件創(chuàng)建一個事件處理程序。
圖 8: 為GridView的事件創(chuàng)建一個事件處理
注意: 你也可以通過代碼隱藏文件頂處的下拉列表創(chuàng)建這個事件處理。從左邊的下拉列表中選擇這個GridView控件,并從右邊的下拉列表中選擇RowUpdated事件。
創(chuàng)建這個事件處理將添加下面這些代碼到ASP.NET頁面的代碼隱藏類中:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}
這個事件處理程序的第二個輸入?yún)?shù)是一個GridViewUpdatedEventArgs類型的對象,它有三個關(guān)于處理異常的屬性:
·Exception –獲取更新操作過程中引發(fā)的異常;如果沒有拋出異常,該屬性的值為null
·ExceptionHandled –獲取或設(shè)置一個值,它指示在更新操作過程中所引發(fā)的異常是否已在RowUpdated事件處理程序中得到處理;如果設(shè)為false(默認(rèn)值),該異常將被重新引發(fā),漏出到ASP.NET運(yùn)行時
·KeepInEditMode – 如果設(shè)置為true,GridView當(dāng)前編輯行將維持在編輯模式;如果設(shè)置為false(默認(rèn)值),當(dāng)前行將恢復(fù)到只讀模式
那么我們的代碼應(yīng)該檢測Exception是否為null,不是null則意味著執(zhí)行此操作時引發(fā)了一個異常。如果是這樣,我們則希望:
·在ExceptionDetails控件中顯示一個對用戶友好的提示信息
·指示異常已經(jīng)被處理
·讓當(dāng)前行保持編輯模式
下面的代碼實現(xiàn)了上述的目的:
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。如果不是,設(shè)置ExceptionDetails控件的Visible屬性為true、設(shè)置它的Text屬性為“There was a problem updating the product.”。當(dāng)前拋出的異常詳細(xì)信息則保存在e.Exception對象的InnerException屬性里。檢查這個內(nèi)部異常,如果它是特定的類型,則把一些額外的有用的信息附加到ExceptionDetails標(biāo)簽的Text屬性。最后,ExceptionHandled和KeepInEditMode屬性都設(shè)置為true。
圖9展示的是遺漏了產(chǎn)品名稱時的頁面的截屏;圖10則顯示輸入一個不合法的UnitPrice值(-50)時的結(jié)果。
圖 9: ProductName綁定列必須包含一個值
圖 10: UnitPrice值不接受負(fù)數(shù)
通過設(shè)置屬性為,事件處理程序指示該異常已經(jīng)被處理。因此,這個異常不會傳送到ASP.NET運(yùn)行時。
注意: 圖9和圖10顯示了一種得體的方式處理不正確的用戶輸入所引發(fā)的異常??墒牵硐氲?,這些不正確的輸入不應(yīng)該到達(dá)業(yè)務(wù)邏輯層,因為ASP.NET頁面應(yīng)該在調(diào)用ProductsBLL類的UpdateProduct方法之前就確保用戶的輸入是有效的。我們在下一節(jié)里將會看看如何添加validation控件到編輯和插入界面從而保證提交到業(yè)務(wù)邏輯層的數(shù)據(jù)遵循業(yè)務(wù)規(guī)則。validation控件不但可以阻止調(diào)用UpdateProduct方法直到用戶提供有效的數(shù)據(jù),還可以為定位數(shù)據(jù)輸入問題提供一個更充滿提示性的用戶體驗。
第三步: 適當(dāng)?shù)靥幚鞡LL層異常
當(dāng)插入、更新或刪除數(shù)據(jù)時,面對一個數(shù)據(jù)相關(guān)的錯誤時數(shù)據(jù)訪問層會拋出一個異常。數(shù)據(jù)庫可能未連線,一個必需的數(shù)據(jù)庫表字段可能未指定值,或者違反了某個表間約束。除了確定的數(shù)據(jù)相關(guān)的異常外,業(yè)務(wù)邏輯層也使用異常指示違反了業(yè)務(wù)邏輯。在創(chuàng)建一個業(yè)務(wù)邏輯層 這一節(jié)里,作為例子,我們添加了一個業(yè)務(wù)規(guī)則檢查最初的UpdateProduct重載。特別地,如果用戶標(biāo)記一個產(chǎn)品為停止供應(yīng),我們要求這個產(chǎn)品不能是該供應(yīng)商唯一供應(yīng)的產(chǎn)品。如果違反了這個條件,拋出一個ApplicationException異常。
在這一節(jié)里,我們給UpdateProduct重載增加一個業(yè)務(wù)規(guī)則:禁止把UnitPrice字段的值設(shè)置為超過原來的兩倍。為了實現(xiàn)這一點(diǎn),調(diào)整UpdateProduct重載以使它可以執(zhí)行這個檢查并且在違反該規(guī)則時拋出一個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;
}
通過這個修改,任何超過現(xiàn)有價格兩倍的價格更新都回引發(fā)一個ApplicationException異常被拋出。就像DAL中引發(fā)的異常一樣,這個BLL引發(fā)的ApplicationException異常可以在GridView的RowUpdated事件處理程序中被偵測并處理。實際上,我們已有的RowUpdated事件處理程序的代碼可以正確地發(fā)現(xiàn)到這個異常并顯示ApplicationException的Message屬性的值。圖11顯示的是當(dāng)一個用戶試圖將產(chǎn)品“Chai”的價格更新為$50.00時的截屏,這超過了它原有價格$19.95的兩倍。
圖 11: 這個業(yè)務(wù)規(guī)則不接受價格增長超出產(chǎn)品現(xiàn)有價格的兩倍
注意: 理想化地我們的業(yè)務(wù)規(guī)則不應(yīng)該在UpdateProduct方法重載里而應(yīng)該在一個公共的方法中。這留作讀者練習(xí)。
總結(jié)
在插入、更新或刪除操作的過程中,數(shù)據(jù)Web控件和ObjectDataSource控件都包含了pre- 和post-級的事件,它們記錄著當(dāng)前的操作。正如我們在本節(jié)和前面的一節(jié)里所看到的,當(dāng)使用一個可編輯的GridView時,GridView的RowUpdating事件在ObjectDataSource的Updating事件之后觸發(fā),此時update命令發(fā)送到ObjectDataSource的隱含對象。完成了此操作,在GridView的RowUpdated事件之后,觸發(fā)ObjectDataSource的Updated事件。
我們可以為這些發(fā)生在操作之前的事件創(chuàng)建事件處理程序,目的是自定義輸入?yún)?shù);為發(fā)生在
操作之后的事件創(chuàng)建事件處理,目的是檢測和相應(yīng)操作的結(jié)果。Post-level的事件處理程序通常用作偵測在操作過程中是否出現(xiàn)了一個異常。當(dāng)面對一個異常時,這些post-level的事件處理程序可以隨意地處理該異常。在本節(jié)里我們看過了如何處理這樣的一個異常,顯示一個友好的錯誤提示信息。
在下一節(jié)里我們將看看如何降低因數(shù)據(jù)格式的問題引起異常的可能性(例如在UnitPrice輸入一個負(fù)數(shù))。特別地,我們將看看如何添加validation控件到編輯和插入界面。
祝編程快樂!
作者簡介
Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創(chuàng)始人,自1998年以來一直應(yīng)用微軟Web技術(shù)。Scott是個獨(dú)立的技 術(shù)咨詢顧問,培訓(xùn)師,作家,最近完成了將由Sams出版社出版的新作,24小時內(nèi)精通ASP.NET 2.0。他的聯(lián)系電郵為mitchell@4guysfromrolla.com,也可以通過他的博客http://ScottOnWriting.NET與他聯(lián)系。
您可能感興趣的文章:- asp.net服務(wù)器上幾種常見異常的解決方案.
- 在 .NET Framework 2.0 中未處理的異常導(dǎo)致基于 ASP.NET 的應(yīng)用程序意外退出
- ASP.NET生成eurl.axd Http異常錯誤的處理方法
- asp.net Http異常eurl.axd出錯信息解決方法
- Asp.net Mvc 身份驗證、異常處理、權(quán)限驗證(攔截器)實現(xiàn)代碼
- ASP.NET mvc異常處理的方法示例介紹
- asp.net 錯誤:0x8007000B 異常的解決方法
- asp.net開發(fā)中常見公共捕獲異常方式總結(jié)(附源碼)
- ASP.NET MVC異常處理模塊詳解
- 在ASP.NET 2.0中操作數(shù)據(jù)之三十八:處理BLL和DAL的異常