ASP.NET ページで BLL レベルと DAL レベルの例外を処理する (VB)
このチュートリアルでは、ASP.NET データ Web コントロールの挿入、更新、または削除操作中に例外が発生した場合に、わかりやすい有益なエラー メッセージを表示する方法について説明します。
はじめに
階層化されたアプリケーション アーキテクチャを使用して ASP.NET Web アプリケーションのデータを操作するには、次の 3 つの一般的な手順が必要です。
- 呼び出す必要があるビジネス ロジック層のメソッドと、それを渡すパラメータ値を決定します。 パラメータ値は、ハード コーディング、プログラムによる割り当て、またはユーザーの入力によって入力できます。
- メソッドを呼び出します。
- 結果を処理します。 データを返す BLL メソッドを呼び出すときに、データをデータ Web コントロールにバインドする必要がある場合があります。 データを変更する BLL メソッドの場合は、戻り値に基づいて何らかのアクションを実行し、手順 2 で発生した例外を適切に処理できます。
前のチュートリアルで説明したように、ObjectDataSource コントロールとデータ Web コントロールの両方が、手順 1 と 3 の拡張性ポイントを提供します。 たとえば、GridView は、ObjectDataSource UpdateParameters
の RowUpdating
コレクションにフィールド値を割り当てる前にイベントを発生させます。その RowUpdated
イベントは、ObjectDataSource が操作を完了した後に発生します。
手順 1 で発生するイベントについては既に調べ、それらを入力パラメータのカスタマイズや操作の取り消しに使用する方法について確認しました。 このチュートリアルでは、操作が完了した後に発生するイベントに注目します。 これらの事後レベルのイベント ハンドラーには多数の用途があり、中でも、操作中に例外が発生したかどうかを判断し、それらを適切に処理し、標準の ASP.NET 例外ページでなく、わかりやすい有益なエラー メッセージを画面に表示できます。
これらの事後レベル イベントの操作を説明するために、編集可能な GridView 内に製品を一覧表示するページを作成しましょう。 製品を更新するときに、例外が発生した場合、ASP.NET ページには、問題が発生したことを説明する短いメッセージが GridView の上に表示されます。 それでは始めましょう。
手順 1: 製品の編集可能な GridView の作成
前のチュートリアルでは、ProductName
と UnitPrice
の 2 つのフィールドのみを含む編集可能な GridView を作成しました。 これには、各製品フィールドのパラメータでなく、3 つの入力パラメータ (製品名、単価、ID) のみを受け入れる ProductsBLL
クラスの UpdateProduct
メソッドに対して追加のオーバーロードを作成する必要がありました。 このチュートリアルでは、この手法の演習をもう一度行い、製品の名前、単位あたりの数量、単価、在庫単位を表示する編集可能な GridView を作成します。ただし編集できるのは、名前、単価、在庫単位のみにします。
このシナリオに対応するには、UpdateProduct
メソッドのもう 1 つのオーバーロードが必要です。これは、製品の名前、単価、在庫単位、ID の 4 つのパラメータを受け取ります。 次のメソッドを 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
このメソッドが完成すると、これら 4 つの特定の製品フィールドを編集できる ASP.NET ページを作成できます。 EditInsertDelete
フォルダー内の ErrorHandling.aspx
ページを開き、デザイナーを使用してページに GridView を追加します。 GridView を新しい ObjectDataSource にバインドし、Select()
メソッドを ProductsBLL
クラスの GetProducts()
メソッドにマッピングし、Update()
メソッドを先ほど作成した UpdateProduct
オーバーロードにマッピングします。
図 1: 4 つの入力パラメータを受け入れる UpdateProduct
メソッド オーバーロードを使用する (フルサイズの画像を表示するにはクリック)
これにより、4 つのパラメータを持つ UpdateParameters
コレクションと、各製品フィールドのための 1 つのフィールドを持つ GridView を含む ObjectDataSource が作成されます。 ObjectDataSource の宣言型マークアップは、OldValuesParameterFormatString
プロパティに値 original_{0}
を割り当てます。これにより、BLL クラスでは渡される original_productID
という名前の入力パラメータが予期されないため、例外が発生します。 宣言構文からこの設定を完全に削除することを忘れないでください (または既定値 {0}
に設定してください)。
次に、GridView を下に移動して ProductName
、QuantityPerUnit
、UnitPrice
、UnitsInStock
の BoundFields のみを含めます。 また、必要と思われるフィールド レベルの書式設定 (HeaderText
プロパティの変更など) も自由に適用できます。
前のチュートリアルでは、UnitPrice
BoundField を読み取り専用モードと編集モードの両方で通貨として書式設定する方法について説明しました。 ここでも同じ操作を行います。 図 2 に示すように、BoundField の DataFormatString
プロパティを {0:c}
に設定し、その HtmlEncode
プロパティを false
に設定し、その ApplyFormatInEditMode
を true
に設定する必要があることを思い出してください。
図 2: 通貨として表示するための UnitPrice
BoundField を構成する (フルサイズの画像を表示するにはクリック)
編集インターフェイスで UnitPrice
を通貨として書式設定するには、通貨形式の文字列を decimal
値に解析する GridView の RowUpdating
イベントのイベント ハンドラーを作成する必要があります。 1 つ前のチュートリアルの 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
に設定します。
図 3: QuantityPerUnit
BoundField を読み取り専用にする (フルサイズの画像を表示するにはクリック)
最後に、GridView のスマート タグの [編集を有効にする] チェック ボックスをオンにします。 これらの手順を完了すると、ErrorHandling.aspx
ページのデザイナーは図 4 のようになります。
図 4: 必要な BoundFields 以外のすべての BoundFields を削除し、[編集を有効にする] チェック ボックスをオンにする (フルサイズの画像を表示するにはクリック)
この時点で、すべての製品の ProductName
、QuantityPerUnit
、UnitPrice
、UnitsInStock
の各フィールドのリストがあります。ただし、編集できるのは、ProductName
、UnitPrice
、UnitsInStock
の各フィールドのみです。
図 5: ユーザーは製品の名前、価格、在庫単位の各フィールドを簡単に編集できるようになりました (フルサイズの画像を表示するにはクリック)
手順 2: DAL レベルの例外の適切な処理
編集可能な GridView は、ユーザーが編集した製品の名前、価格、在庫単位の有効な値を入力するとうまく機能しますが、無効な値を入力すると例外が発生します。 たとえば、ProductName
値を省略すると、ProductsRow
クラスの ProductName
プロパティの AllowDBNull
プロパティが false
に設定されているため、NoNullAllowedException がスローされます。データベースがダウンしている場合、データベースに接続しようとすると、TableAdapter によって SqlException
がスローされます。 何らかの措置を講じないと、これらの例外は、データ アクセス層からビジネス ロジック層にバブル アップし、次に ASP.NET ページにバブル アップし、最後に ASP.NET ランタイムにバブル アップします。
Web アプリケーションの構成方法と、アプリケーションのアクセス元が localhost
かどうかに応じて、ハンドルされない例外のために一般的なサーバー エラー ページ、詳細なエラー レポート、ユーザーにわかりやすい Web ページのいずれかが発生する可能性があります。 ASP.NET ランタイムがキャッチされない例外に応答する方法の詳細については、ASP.NET での Web アプリケーション エラーの処理と customErrors 要素に関する記事を参照してください。
図 6 は、ProductName
値を指定せずに製品を更新しようとしたときに発生する画面を示しています。 これは、localhost
を経由したときに表示される既定の詳細なエラー レポートです。
図 6: 製品の名前を省略すると、例外の詳細が表示される (フルサイズの画像を表示するにはクリック)
このような例外の詳細は、アプリケーションをテストするときには役立ちますが、例外の表現としてエンド ユーザーにこのような画面を表示するのは、理想的ではありません。 エンド ユーザーは、NoNullAllowedException
が何かわからない、またはその原因がわからないと考えられます。 よりよい方法は、製品の更新を試みた際に問題が発生したことを説明するわかりやすいメッセージをユーザーに表示することです。
操作の実行時に例外が発生した場合、ObjectDataSource とデータ Web コントロールの両方の事後レベル イベントは、それを検出し、ASP.NET ランタイムに例外がバブリングするのを取り消す手段を提供します。 この例では、GridView の RowUpdated
イベントのイベント ハンドラーを作成して、例外が発生したかどうかを判断し、発生した場合は、Label Web コントロールに例外の詳細を表示します。
まず、ASP.NET ページにラベルを追加し、その ID
プロパティを ExceptionDetails
に設定し、その Text
プロパティをクリアします。 ユーザーの目をこのメッセージに向けるために、その CssClass
プロパティを Warning
に設定します。これは、前のチュートリアルで Styles.css
ファイルに追加した CSS クラスです。 この CSS クラスでは、ラベルのテキストが大きな赤、斜体、太字のフォントで表示されることを思い出してください。
図 7: ラベル Web コントロールをページに追加する (フルサイズの画像を表示するにはクリック)
この Label 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
に設定されるようにします。 GridView の RowUpdated
イベント ハンドラーで検出できる DAL レベルまたは BLL レベルの例外に直面した場合は、ExceptionDetails
コントロールの Visible
プロパティを true に設定します。 Web コントロール イベント ハンドラーは、ページ ライフサイクルの Page_Load
イベント ハンドラーの後に発生するため、そのラベルが表示されます。 ただし、次のポストバックでは、Page_Load
イベント ハンドラーは、Visible
プロパティを false
に戻し、再度非表示に戻します。
Note
または、こうする必要性は、ExceptionDetails
コントロールの Visible
プロパティを Page_Load
で設定することで排除できます。これは、その Visible
プロパティ false
を宣言構文内で割り当て、そのビュー状態 (その EnableViewState
プロパティを false
に設定) を無効にすることで実行できます。 この代替方法は、今後のチュートリアルで使用します。
Label コントロールを追加したら、次の手順として GridView の RowUpdated
イベントのイベント ハンドラーを作成します。 デザイナーで GridView を選択し、[プロパティ] ウィンドウに移動し、稲妻アイコンをクリックして GridView のイベントを一覧表示します。 このチュートリアルで前にこのイベントのイベント ハンドラーを作成したので、GridView の RowUpdating
イベントのエントリは既にあるはずです。 同様に、RowUpdated
イベントのイベント ハンドラーも作成します。
図 8: GridView の RowUpdated
イベントのイベント ハンドラーを作成する
Note
分離コード クラス ファイルの上部にあるドロップダウン リストを使用して、イベント ハンドラーを作成することもできます。 左側のドロップダウン リストから 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
このイベント ハンドラーの 2 番目の入力パラメーターは、GridViewUpdatedEventArgs 型のオブジェクトです。これには、例外を処理するための 3 つのプロパティがあります。
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.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
BoundField に値を含める必要がある (フルサイズの画像を表示するにはクリック)
図 10: 負の UnitPrice
値は許可されない (フルサイズの画像を表示するにはクリック)
e.ExceptionHandled
プロパティを true
に設定することで、RowUpdated
イベント ハンドラーは例外を処理したことを示しています。 そのため、例外は ASP.NET ランタイムに伝播されません。
Note
図 9 と図 10 は、無効なユーザー入力によって発生した例外を適切に処理する方法を示しています。 しかし、理想的には、このような無効な入力はビジネス ロジック層に到達しないようにする必要があります。ASP.NET ページは ProductsBLL
クラスの UpdateProduct
メソッドを呼び出す前に、ユーザーの入力が有効であることを確認するべきだからです。 次のチュートリアルでは、ビジネス ロジック層に送信されたデータがビジネス ルールに準拠していることを確認するために、編集インターフェイスと挿入インターフェイスに検証コントロールを追加する方法について説明します。 検証コントロールは、ユーザーが指定したデータが有効になるまで UpdateProduct
メソッドの呼び出しを防ぐだけでなく、データ入力の問題を特定するためのより有益なユーザー エクスペリエンスを提供します。
手順 3: BLL レベルの例外の適切な処理
データを挿入、更新、または削除すると、データ関連のエラーが発生した場合に、データ アクセス層によって例外がスローされる場合があります。 データベースがオフラインであるか、必要なデータベース テーブル列に値が指定されていないか、テーブル レベルの制約に違反している可能性があります。 ビジネス ロジック層では、厳密なデータ関連の例外に加えて、ビジネス ルールに違反した場合を示すためにも例外を使用できます。 たとえば、「ビジネス ロジック層を作成する」チュートリアルでは、元の UpdateProduct
オーバーロードにビジネス ルール チェックを追加しました。 具体的には、ユーザーが製品を廃止済みとしてマークした場合、そのサプライヤーが提供する唯一の製品がその製品であってはならないことを要求しました。 この条件に違反した場合は、ApplicationException
がスローされました。
このチュートリアルで作成した UpdateProduct
オーバーロードに対し、UnitPrice
フィールドが元の UnitPrice
値の 2 倍以上の新しい値に設定されないようにするビジネス ルールを追加します。 これを実現するには、このチェックを実行し、ルールに違反した場合に ApplicationException
をスローするように、UpdateProduct
オーバーロードを調整します。 更新されたメソッドは次のとおりです。
<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
この変更により、既存の価格の 2 倍以上の価格更新が発生すると、ApplicationException
がスローされます。 例外が DAL から発生したと同様に、この BLL で発生した ApplicationException
は、GridView の RowUpdated
イベント ハンドラーで検出および処理できます。 実際、RowUpdated
イベント ハンドラーのコードは、記述されているように、この例外を正しく検出し、ApplicationException
の Message
プロパティ値を表示します。 図 11 は、ユーザーが Chai の価格を現在の価格 19.95 ドルの 2 倍以上である 50.00 ドルに更新しようとしたときのスクリーンショットを示しています。
図 11: このビジネス ルールでは、製品価格の 2 倍を超える値上げは禁止される (フルサイズの画像を表示するにはクリック)
Note
ここでのビジネス ロジック ルールは、UpdateProduct
メソッドのオーバーロードから一般的なメソッドにリファクターされるのが理想的です。 これは、読者の演習課題として残されています。
まとめ
挿入、更新、削除の操作中に、データ Web コントロールと ObjectDataSource の両方が、実際の操作の両端に配置される事前レベルおよび事後レベルのイベントを発生させます。 このチュートリアルと前のチュートリアルで説明したように、編集可能な GridView を操作すると、GridView の RowUpdating
イベントが発生し、その後に ObjectDataSource の Updating
イベントが発生し、その時点で、ObjectDataSource の基になるオブジェクトに対して更新コマンドが実行されます。 この操作が完了すると、ObjectDataSource の Updated
イベントが発生し、その後に GridView の RowUpdated
イベントが発生します。
イベント ハンドラーは、入力パラメータをカスタマイズするために事前レベルのイベントに対して作成でき、操作の結果を検査して応答するために事後レベルのイベントに対して作成できます。 事後レベルのイベント ハンドラーは、操作中に例外が発生したかどうかを検出するために最も一般的に使用されます。 例外が発生した場合、これらの事後レベルのイベント ハンドラーは、必要に応じて独自に例外を処理できます。 このチュートリアルでは、わかりやすいエラー メッセージを表示して、このような例外を処理する方法について説明しました。
次のチュートリアルでは、データの書式設定の問題 (負の UnitPrice
の入力など) によって発生する例外の可能性を軽減する方法について説明します。 具体的には、編集インターフェイスと挿入インターフェイスに検証コントロールを追加する方法について説明します。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は Liz Shulok でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。