處理 ASP.NET 頁面中 BLL 和 DAL 層級的例外狀況 (VB)

作者:Scott Mitchell

下載 PDF

在本教學課程中,我們將瞭解如何在插入、更新或刪除 ASP.NET 數據 Web 控件的插入、更新或刪除作業期間顯示易記、有資訊的錯誤訊息。

簡介

使用階層式應用程式架構處理來自 ASP.NET Web 應用程式的數據牽涉到下列三個一般步驟:

  1. 判斷需要叫用哪些商業規則層方法,以及要傳遞哪些參數值。 參數值可以是硬式編碼、以程式設計方式指派,或使用者輸入的輸入。
  2. 叫用方法。
  3. 處理結果。 呼叫傳回數據的 BLL 方法時,這可能牽涉到將數據系結至數據 Web 控制件。 對於修改數據的 BLL 方法,這可能包括根據傳回值執行某些動作,或適當地處理步驟 2 中出現的任何例外狀況。

上一個教學課程中所見,ObjectDataSource 和數據 Web 控件都提供步驟 1 和 3 的擴充點。 例如,GridView 會在將域值指派給 ObjectDataSource 的UpdateParameters集合之前引發其RowUpdating事件;其RowUpdated事件會在 ObjectDataSource 完成作業之後引發。

我們已檢查在步驟 1 期間引發的事件,並了解它們如何用來自定義輸入參數或取消作業。 在本教學課程中,我們將注意作業完成後引發的事件。 透過這些後置事件處理程式,我們可以判斷作業期間是否發生例外狀況,並正常處理,並在畫面上顯示易記且具資訊性的錯誤訊息,而不是預設為標準 ASP.NET 例外狀況頁面。

為了說明使用這些層級事件,讓我們建立一個頁面,以列出可編輯的 GridView 中的產品。 更新產品時,如果引發例外狀況,我們的 ASP.NET 頁面會顯示 GridView 上方的簡短訊息,說明發生問題。 現在就開始吧!

步驟 1:建立可編輯的產品 GridView

在上一個教學課程中,我們建立了只有兩個字段和 UnitPrice的可編輯 GridView。 ProductName 這需要為 ProductsBLL 類別 UpdateProduct 的 方法建立額外的多載,其中一個只接受三個輸入參數, (產品名稱、單價和標識符) ,而不是每個產品欄位的參數。 在本教學課程中,讓我們再次練習這項技術,建立可編輯的 GridView 來顯示產品名稱、每單位數量、單價和庫存單位,但只允許編輯庫存中的名稱、單價和單位。

為了配合此案例,我們需要方法的另一個多載 UpdateProduct ,一個接受四個參數:產品名稱、單價、庫存單位和標識符。 將下列方法新增至 ProductsBLL 類別:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct _
    (ByVal productName As String, ByVal unitPrice As Nullable(Of Decimal), _
ByVal unitsInStock As Nullable(Of Short), ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

完成此方法之後,我們就可以建立 ASP.NET 頁面,以便編輯這四個特定產品欄位。 ErrorHandling.aspx開啟資料夾中的頁面EditInsertDelete,並透過 Designer 將 GridView 新增至頁面。 將 GridView 系結至新的 ObjectDataSource,將 Select() 方法對應至 ProductsBLL 類別 GetProducts() 的 方法和 Update() 剛建立的多 UpdateProduct 載。

使用接受四個輸入參數的 UpdateProduct 方法多載

圖 1:使用 UpdateProduct 接受四個輸入參數的方法多載 (按兩下即可檢視大小完整的影像)

這會建立具有四個參數集合的 UpdateParameters ObjectDataSource,以及具有每個產品欄位的 GridView。 ObjectDataSource 的宣告式標記會將 值original_{0}指派給 OldValuesParameterFormatString 屬性,這會導致例外狀況,因為我們的 BLL 類別不預期傳入名為 original_productID 的輸入參數。 別忘了從宣告式語法中移除此設定, (或將其設定為預設值, {0}) 。

接下來,剖析 GridView 只包含 ProductNameQuantityPerUnitUnitPriceUnitsInStock BoundFields。 您也可以隨意套用您認為必要 (的任何欄位層級格式,例如變更 HeaderText 屬性) 。

在上一個教學課程中,我們探討如何以只讀模式和編輯模式將 BoundField 格式化 UnitPrice 為貨幣。 讓我們在這裡執行相同的動作。 回想一下,這需要將 BoundField 的 DataFormatString 屬性設定為 {0:c}、其 HtmlEncode 屬性設定為 false,並將其 ApplyFormatInEditMode 設定為 true,如圖 2 所示。

將 UnitPrice BoundField 設定為貨幣顯示

圖 2:將 UnitPrice BoundField 設定為貨幣顯示 (按鍵即可檢視大小完整的影像)

UnitPrice將 格式化為編輯介面中的貨幣需要建立 GridView RowUpdating 事件的事件處理程式,以將貨幣格式字串剖析為decimal值。 回想一下, RowUpdating 最後一個 UnitPrice 教學課程中的事件處理程式也已檢查,以確保使用者已提供值。 不過,在本教學課程中,讓我們允許使用者省略價格。

Protected Sub GridView1_RowUpdating(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) _
    Handles GridView1.RowUpdating
    If e.NewValues("UnitPrice") IsNot Nothing Then
        e.NewValues("UnitPrice") = _
            Decimal.Parse(e.NewValues("UnitPrice").ToString(), _
            System.Globalization.NumberStyles.Currency)
    End If

我們的 GridView 包含 QuantityPerUnit BoundField,但此 BoundField 應該僅供顯示之用,且使用者不應編輯。 若要排列這一點,只要將 BoundFields 的 ReadOnly 屬性設定為 true

將 QuantityPerUnit BoundField 設為唯讀

圖 3:讓 QuantityPerUnit BoundField Read-Only (按兩下即可檢視大小完整的影像)

最後,核取 GridView 智慧標記中的 [啟用編輯] 複選框。 完成這些步驟之後,ErrorHandling.aspx頁面的 Designer 看起來應該類似圖 4。

拿掉所有必要的 BoundFields 並核取 [啟用編輯] 複選框

圖 4:移除所有必要 BoundFields 並核取 [啟用編輯] 複選框 (按兩下即可檢視大小完整的影像)

此時,我們有所有產品的、、 和 欄位的清單;不過,只能ProductName編輯、 UnitPriceUnitsInStock 欄位。UnitsInStockUnitPriceQuantityPerUnitProductName

用戶現在可以輕鬆地編輯產品的名稱、價格和庫存欄位中的單位

圖 5:用戶現在可以輕鬆地編輯產品的名稱、價格和庫存中的單位, (按兩下即可檢視大小完整的影像)

步驟 2:正常處理 DAL-Level 例外狀況

雖然我們的可編輯 GridView 在使用者輸入已編輯產品名稱、價格和單位的合法值時,輸入不合法的值會導致例外狀況。 例如,省ProductName略值會導致 NoNullAllowedException 擲回,因為 ProductName 類別中的 ProductsRow 屬性設定AllowDBNullfalse為 ;如果資料庫已關閉,SqlException則 TableAdapter 嘗試連線到資料庫時,將會擲回 。 若不採取任何動作,這些例外狀況會從數據存取層反升到商業規則層,然後升至 [ASP.NET] 頁面,最後會升至 ASP.NET 運行時間。

根據 Web 應用程式的設定方式,以及您是否從 瀏覽應用程式 localhost,未處理的例外狀況可能會導致一般伺服器錯誤頁面、詳細錯誤報告或使用者易記的網頁。 如需 ASP.NET 運行時間如何回應未攔截例外狀況的詳細資訊,請參閱 ASP.NET 中的 Web 應用程式錯誤處理customErrors 元素

圖 6 顯示嘗試更新產品而不指定 ProductName 值時遇到的畫面。 這是傳入 localhost時所顯示的預設詳細錯誤報告。

省略產品名稱會顯示例外狀況詳細數據

圖 6:省略產品名稱會顯示例外狀況詳細數據 (按兩下即可檢視大小完整的影像)

雖然這類例外狀況詳細數據在測試應用程式時很有説明,但對例外狀況的臉部呈現這類螢幕的使用者並不理想。 使用者可能不知道 是什麼 NoNullAllowedException ,或造成的原因。 更好的方法是向用戶顯示更方便使用的訊息,說明嘗試更新產品時發生問題。

如果在執行作業時發生例外狀況,ObjectDataSource 和數據 Web 控件中的後置事件都會提供一種偵測並取消例外狀況的方法,使其無法反升至 ASP.NET 運行時間。 在我們的範例中,讓我們為 GridView 的事件 RowUpdated 建立事件處理程式,以判斷是否引發例外狀況,如果是的話,則會在標籤 Web 控制件中顯示例外狀況詳細數據。

首先,將標籤新增至 ASP.NET 頁面,並將其 ID 屬性設定為 ExceptionDetails ,並清除其 Text 屬性。 若要對這個訊息繪製使用者的眼睛,請將其 CssClass 屬性 Warning設定為 ,這是我們在上一個教學課程中新增至 Styles.css 檔案的 CSS 類別。 回想一下,這個 CSS 類別會導致標籤的文字以紅色、斜體、粗體、超大型字型顯示。

將標籤 Web 控制項新增至頁面

圖 7:將標籤 Web 控制項新增至頁面 (按一下以檢視大小完整的影像)

由於我們想要在發生例外狀況之後立即看到此卷標 Web 控件,因此請在事件處理程式中Page_Load將其Visible屬性設定為 false:

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    ExceptionDetails.Visible = False
End Sub

使用此程式代碼時,在第一頁造訪和後續回傳時, ExceptionDetails 控制件的 屬性會 Visible 設定為 false。 在 DAL 或 BLL 層級例外狀況的臉部中,我們可以在 GridView 的RowUpdated事件處理程式中偵測到此例外狀況,我們將控件的 Visible 屬性設定ExceptionDetails為 true。 由於網頁控件事件處理程式會在頁面生命週期中的事件處理程序之後 Page_Load 發生,因此會顯示標籤。 不過,在下一次回傳時, Page_Load 事件處理程式會將 屬性還原 Visiblefalse,並再次從檢視中隱藏它。

注意

或者,我們可以藉由在宣告式語法中指派控件的 屬性,並停用其Visible檢視狀態, (將其屬性設定ExceptionDetailsEnableViewStatefalse) ,來移除 設定控件Visible屬性Page_Loadfalse的必要條件。 我們將在未來的教學課程中使用這個替代方法。

新增標籤之後,下一個步驟是建立 GridView 事件的 RowUpdated 事件處理程式。 選取 Designer 中的 GridView,移至 屬性視窗,然後按兩下閃電圖示,列出 GridView 的事件。 在此教學課程稍早建立此事件的事件處理程式時,GridView RowUpdating 事件應該已經有專案。 建立事件的事件處理程式 RowUpdated

為 GridView 的 RowUpdated 事件建立事件處理程式

圖 8:建立 GridView 事件的 RowUpdated 事件處理程式

注意

您也可以透過程式代碼後置類別檔案頂端的下拉式清單建立事件處理程式。 從左側的下拉式清單中選取 GridView,並從 RowUpdated 右側的下拉式清單中選取事件。

建立此事件處理程式會將下列程式代碼新增至頁面的程式代碼後置類別 ASP.NET:

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
End Sub

此事件處理程式的第二個輸入參數是 GridViewUpdatedEventArgs 類型的物件,其具有處理例外狀況的三個屬性:

  • Exception 擲回例外狀況的參考;如果沒有擲回例外狀況,這個屬性的值會是 null
  • ExceptionHandled 布爾值,指出是否在事件處理程式中 RowUpdated 處理例外狀況;如果 false (預設) ,則會重新擲回例外狀況,並排列至 ASP.NET 運行時間
  • KeepInEditMode 如果設定為 true 已編輯的 GridView 數據列仍處於編輯模式;如果 false (預設) ,GridView 數據列會還原回其只讀模式

然後,我們的程式代碼應該檢查是否 Exception 不是 null,這表示在執行作業時引發例外狀況。 如果是這種情況,我們想要:

  • 在標籤中 ExceptionDetails 顯示使用者易記的訊息
  • 指出已處理例外狀況
  • 讓 GridView 資料列保持編輯模式

下列程式代碼會完成這些目標:

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
    If e.Exception IsNot Nothing Then
        ExceptionDetails.Visible = True
        ExceptionDetails.Text = "There was a problem updating the product. "
        If e.Exception.InnerException IsNot Nothing Then
            Dim inner As Exception = e.Exception.InnerException
            If TypeOf inner Is System.Data.Common.DbException Then
                ExceptionDetails.Text &= _
                "Our database is currently experiencing problems." & _
                "Please try again later."
            ElseIf TypeOf inner _
             Is System.Data.NoNullAllowedException Then
                ExceptionDetails.Text += _
                    "There are one or more required fields that are missing."
            ElseIf TypeOf inner Is ArgumentException Then
                Dim paramName As String = CType(inner, ArgumentException).ParamName
                ExceptionDetails.Text &= _
                    String.Concat("The ", paramName, " value is illegal.")
            ElseIf TypeOf inner Is ApplicationException Then
                ExceptionDetails.Text += inner.Message
            End If
        End If
        e.ExceptionHandled = True
        e.KeepInEditMode = True
    End If
End Sub

這個事件處理程式的開頭是檢查 是否 e.Exceptionnull。 如果不是,ExceptionDetails則 Label 的 Visible 屬性會設定為 true ,並將其 Text 屬性設定為 「更新產品時發生問題」。擲回的實際例外狀況詳細數據位於物件的 InnerException 屬性中e.Exception。 系統會檢查這個內部例外狀況,如果它是特定類型,則會將額外的實用訊息附加至 ExceptionDetails Label 的 Text 屬性。 最後, ExceptionHandledKeepInEditMode 屬性都設定為 true

圖 9 顯示省略產品名稱時此頁面的螢幕快照;圖 10 顯示輸入不合法的 UnitPrice 值 (-50) 時的結果。

ProductName BoundField 必須包含值

圖 9ProductName BoundField 必須包含值 (按兩下即可檢視完整大小的影像)

不允許負的 UnitPrice 值

圖 10:按兩下即可檢視全大小影像 () 不允許負UnitPrice

藉由將 e.ExceptionHandled 屬性設定為 trueRowUpdated 事件處理程式表示它已處理例外狀況。 因此,例外狀況不會傳播到 ASP.NET 運行時間。

注意

圖 9 和 10 顯示因使用者輸入無效而引發之例外狀況的正常方式。 不過,在理想情況下,這類無效的輸入一開始永遠不會到達商業規則層,因為 ASP.NET 頁面應該在叫用 ProductsBLL 類別的 UpdateProduct 方法之前,確保使用者的輸入有效。 在下一個教學課程中,我們將瞭解如何將驗證控件新增至編輯和插入介面,以確保提交至商業規則層的數據符合商務規則。 驗證控件不僅會防止叫用 UpdateProduct 方法,直到使用者提供的數據有效為止,也提供更豐富的用戶體驗來識別數據輸入問題。

步驟 3:正常處理 BLL-Level 例外狀況

插入、更新或刪除資料時,數據存取層可能會在發生數據相關錯誤時擲回例外狀況。 資料庫可能離線、必要的資料庫資料表資料行可能未指定值,或可能違反資料表層級條件約束。 除了嚴格數據相關例外狀況之外,商業規則層還可以使用例外狀況來指出違反商務規則的時機。 例如,在 建立商業規則層 教學課程中,我們已將商務規則檢查新增至原始 UpdateProduct 多載。 具體而言,如果使用者將產品標示為已停止,我們要求產品不是其供應商所提供的產品。 如果違反此條件, ApplicationException 則會擲回 。

針對本教學課程中建立的多 UpdateProduct 載,讓我們新增商務規則,禁止 UnitPrice 將字段設定為超過原始 UnitPrice 值兩倍的新值。 若要完成這項作業,請調整 UpdateProduct 多載,使其執行這項檢查,並在違反規則時擲回 ApplicationException 。 更新的方法如下:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(ByVal productName As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    If unitPrice.HasValue AndAlso Not product.IsUnitPriceNull() Then
        If unitPrice > product.UnitPrice * 2 Then
            Throw New ApplicationException( _
                "When updating a product price," & _
                " the new price cannot exceed twice the original price.")
        End If
    End If
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

透過這項變更,任何超過兩倍現有價格的價格更新都會擲 ApplicationException 回 。 就像從 DAL 引發的例外狀況一樣,您可以在 GridView 的RowUpdated事件處理程式中偵測並處理這個 BLL 引發ApplicationException。 事實上, RowUpdated 事件處理程式的程式代碼如所撰寫,會正確偵測此例外狀況並顯示 ApplicationExceptionMessage 屬性值。 圖 11 顯示當使用者嘗試將 Chai 的價格更新為 $50.00 時螢幕快照,其目前價格超過 $19.95 的倍數。

商務規則不允許價格增加超過產品價格的倍數

圖 11:商務規則不允許價格增加超過產品價格的倍數 (按兩下即可檢視大小完整的影像)

注意

在理想情況下,我們的商業規則會從 UpdateProduct 方法多載和一般方法中重構。 這會保留為讀者的練習。

摘要

在插入、更新和刪除作業期間,數據 Web 控件和 ObjectDataSource 都會引發預定實際作業的前置和後置事件。 如本教學課程和上述教學課程中所見,使用可編輯的 GridView 時,會引發 GridView RowUpdating 的事件,然後引發 ObjectDataSource 的 Updating 事件,此時會將更新命令建立至 ObjectDataSource 的基礎物件。 作業完成之後,ObjectDataSource 的事件 Updated 就會引發,後面接著 GridView 的事件 RowUpdated

我們可以為預先層級事件建立事件處理程式,以自定義輸入參數或後續層級事件,以檢查和響應作業的結果。 層級後事件處理程式最常用來偵測作業期間是否發生例外狀況。 在遇到例外狀況時,這些後置事件處理程式可以選擇自行處理例外狀況。 在本教學課程中,我們已瞭解如何藉由顯示易記的錯誤訊息來處理這類例外狀況。

在下一個教學課程中,我們將瞭解如何降低數據格式化問題 (導致例外狀況的可能性,例如輸入負 UnitPrice) 。 具體而言,我們將探討如何將驗證控件新增至編輯和插入介面。

快樂的程序設計!

關於作者

Scott Mitchell 是 1998 年以來,1998 年與 Microsoft Web 技術合作的 篇 ASP/ASP.NET 書籍和 4GuysFromRolla.com 作者。 Scott 是獨立的顧問、訓練者和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格連到,也可以透過其部落格來存取,網址為 http://ScottOnWriting.NET

特別感謝

本教學課程系列是由許多實用的檢閱者所檢閱。 本教學課程的首席檢閱者是 Liz Shulok。 想要檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行放在 mitchell@4GuysFromRolla.com。