次の方法で共有


ASP.NET ページで BLL レベルと DAL レベルの例外を処理する (VB)

スコット・ミッチェル著

PDF をダウンロードする

このチュートリアルでは、ASP.NET データ Web コントロールの挿入、更新、または削除操作中に例外が発生した場合に、わかりやすい有益なエラー メッセージを表示する方法について説明します。

イントロダクション

階層化されたアプリケーション アーキテクチャを使用して ASP.NET Web アプリケーションのデータを操作するには、次の 3 つの一般的な手順が必要です。

  1. 呼び出す必要があるビジネス ロジック レイヤーのメソッドと、それを渡すパラメーター値を決定します。 パラメーター値は、ハード コーディング、プログラムによる割り当て、またはユーザーが入力した入力を行うことができます。
  2. メソッドを呼び出します。
  3. 結果を処理します。 データを返す BLL メソッドを呼び出すときに、データをデータ Web コントロールにバインドする必要がある場合があります。 データを変更する BLL メソッドの場合は、戻り値に基づいて何らかのアクションを実行したり、手順 2 で発生した例外を適切に処理したりできます。

前のチュートリアルで説明したように、ObjectDataSource コントロールとデータ Web コントロールの両方に、手順 1 と 3 の機能拡張ポイントが用意されています。 たとえば、GridView は、ObjectDataSource の RowUpdating コレクションにフィールド値を割り当てる前に、UpdateParameters イベントを発生させます。そのRowUpdated イベントは、ObjectDataSource が操作を完了した後に発生します。

手順 1 で発生するイベントを既に調べ、入力パラメーターのカスタマイズや操作の取り消しにどのように使用できるかを確認しました。 このチュートリアルでは、操作が完了した後に発生するイベントに注目します。 これらの事後レベルのイベント ハンドラーを使用すると、特に、操作中に例外が発生したかどうかを判断し、正常に処理し、標準の ASP.NET 例外ページではなく、わかりやすい有益なエラー メッセージを画面に表示できます。

これらのポストレベル イベントの操作を説明するために、編集可能な GridView 内の製品を一覧表示するページを作成しましょう。 製品を更新するときに、例外が発生した場合、ASP.NET ページには、問題が発生したことを説明する短いメッセージが GridView の上に表示されます。 それでは始めましょう。

手順 1: 編集可能な製品の GridView を作成する

前のチュートリアルでは、 ProductNameUnitPriceの 2 つのフィールドのみを含む編集可能な GridView を作成しました。 これには、 ProductsBLL クラスの UpdateProduct メソッドに対して追加のオーバーロードを作成する必要がありました。1 つは、各製品フィールドのパラメーターではなく、3 つの入力パラメーター (製品名、単価、ID) のみを受け入れたものです。 このチュートリアルでは、この手法をもう一度練習して、製品の名前、単位あたりの数量、単価、在庫単位を表示する編集可能な GridView を作成しますが、編集できるのは、名前、単価、在庫単位のみです。

このシナリオに対応するには、 UpdateProduct メソッドのもう 1 つのオーバーロードが必要です。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 ページを作成する準備ができました。 ErrorHandling.aspx フォルダーのEditInsertDelete ページを開き、デザイナーを使用してページに GridView を追加します。 GridView を新しい ObjectDataSource にバインドし、 Select() メソッドを ProductsBLL クラスの GetProducts() メソッドにマッピングし、 Update() メソッドを先ほど作成した UpdateProduct オーバーロードにマッピングします。

4 つの入力パラメーターを受け入れる UpdateProduct メソッド オーバーロードを使用する

図 1: 4 つの入力パラメーターを受け入れる UpdateProduct メソッドのオーバーロードを使用する (フルサイズの画像を表示する をクリックします)。

これにより、4 つのパラメーターを持つ UpdateParameters コレクションと、各製品フィールドのフィールドを持つ GridView を含む ObjectDataSource が作成されます。 ObjectDataSource の宣言型マークアップにより、 OldValuesParameterFormatString プロパティに値 original_{0}が割り当てられます。これにより、BLL クラスでは original_productID という名前の入力パラメーターが渡されるとは想定されないため、例外が発生します。 宣言構文からこの設定を完全に削除することを忘れないでください (または既定値 {0}に設定してください)。

次に、GridView を整理して ProductNameQuantityPerUnitUnitPrice、および UnitsInStock の BoundFields のみを含めます。 また、必要と思われるフィールド レベルの書式設定 ( HeaderText プロパティの変更など) も自由に適用できます。

前のチュートリアルでは、読み取り専用モードと編集モードの両方で、 UnitPrice BoundField を通貨として書式設定する方法について説明しました。 ここで同じ操作を行いましょう。 図 2 に示すように、BoundField の DataFormatString プロパティを {0:c} に設定し、その HtmlEncode プロパティを false に設定し、その ApplyFormatInEditModetrue に設定する必要があることを思い出してください。

通貨として表示するように UnitPrice BoundField を構成する

図 2: UnitPrice BoundField を通貨として表示するように構成する (フルサイズの画像を表示する をクリックします)。

編集インターフェイスでUnitPriceを通貨として書式設定するには、通貨形式の文字列をRowUpdating値に解析する GridView の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 には BoundField QuantityPerUnit が含まれていますが、この BoundField は表示目的でのみ使用し、ユーザーが編集することはできません。 これを配置するには、BoundFields の ReadOnly プロパティを true に設定するだけです。

QuantityPerUnit BoundField を読み取り専用にする

図 3: QuantityPerUnit BoundField Read-Only にする (フルサイズの画像を表示する をクリックします)

最後に、GridView のスマート タグの [編集を有効にする] チェック ボックスをオンにします。 これらの手順を完了すると、 ErrorHandling.aspx ページのデザイナーは図 4 のようになります。

必要な BoundField を除くすべてを削除し、[編集を有効にする] チェック ボックスをオンにします

図 4: 必要な BoundField を除くすべてを削除し、[編集を有効にする] チェック ボックスをオンにします (フルサイズの画像を表示する をクリックします)。

この時点で、製品のすべての ProductNameQuantityPerUnitUnitPrice、および UnitsInStock フィールドの一覧が表示されますが、編集できるのは ProductNameUnitPrice、および UnitsInStock フィールドのみです。

ユーザーは在庫フィールドで製品の名前、価格、単位を簡単に編集できるようになりました

図 5: 在庫フィールドの製品の名前、価格、ユニットをユーザーが簡単に編集できるようになりました (フルサイズの画像を表示する をクリックします)。

手順 2: DAL-Level 例外を適切に処理する

編集可能な GridView は、ユーザーが編集した製品の名前、価格、および在庫単位の有効な値を入力するとうまく機能しますが、無効な値を入力すると例外が発生します。 たとえば、ProductName値を省略すると、 クラスのProductName プロパティが ProductsRowAllowDBNull に設定されているため、false がスローされます。データベースがダウンしている場合、データベースに接続しようとすると 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 クラスでは、ラベルのテキストが赤、斜体、太字、特大フォントで表示されることを思い出してください。

ラベル Web コントロールをページに追加する

図 7: ラベル Web コントロールをページに追加する (フルサイズの画像を表示する をクリックします)

この Label Web コントロールは例外が発生した直後にのみ表示されるようにするため、Visible イベント ハンドラーでそのPage_Load プロパティを false に設定します。

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

このコードを使用すると、最初のページの visit および後続のポストバックで、 ExceptionDetails コントロールの Visible プロパティが false に設定されます。 GridView の RowUpdated イベント ハンドラーで検出できる DAL レベルまたは BLL レベルの例外が発生した場合は、 ExceptionDetails コントロールの Visible プロパティを true に設定します。 Web コントロール イベント ハンドラーは、ページ ライフサイクルの Page_Load イベント ハンドラーの後に発生するため、ラベルが表示されます。 ただし、次のポストバックでは、 Page_Load イベント ハンドラーは、 Visible プロパティを falseに戻し、再度非表示に戻します。

または、宣言構文でExceptionDetailsプロパティVisibleを割り当て、そのビューステートを無効にすることで、Page_LoadVisible コントロールのfalse プロパティを設定する必要性を排除することもできます (EnableViewState プロパティをfalseに設定します)。 この代替方法は、今後のチュートリアルで使用します。

Label コントロールを追加したら、次の手順として GridView の RowUpdated イベントのイベント ハンドラーを作成します。 デザイナーで 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

このイベント ハンドラーの 2 番目の入力パラメーターは、 GridViewUpdatedEventArgs 型のオブジェクトであり、例外を処理するための 3 つのプロパティがあります。

  • Exception スローされた例外への参照。例外がスローされていない場合、このプロパティの値は null
  • ExceptionHandled RowUpdated イベント ハンドラーで例外が処理されたかどうかを示すブール値。false (既定値) の場合、例外は再スローされ、ASP.NET ランタイムまでパーコレートされます。
  • KeepInEditMode 編集した GridView 行 true に設定されている場合は編集モードのままです。 false (既定値) の場合、GridView 行は読み取り専用モードに戻ります

その後、コードは、 Exceptionnullされていないかどうかを確認する必要があります。つまり、操作の実行中に例外が発生しました。 その場合は、次の操作を行います。

  • 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 プロパティは "製品の更新中に問題が発生しました" に設定されます。スローされた実際の例外の詳細は、 e.Exception オブジェクトの InnerException プロパティにあります。 この内部例外が調べられ、特定の種類の場合は、 ExceptionDetails Label の Text プロパティに追加の有用なメッセージが追加されます。 最後に、 ExceptionHandled プロパティと KeepInEditMode プロパティの両方が trueに設定されます。

図 9 は、製品の名前を省略したときのこのページのスクリーン ショットを示しています。図 10 は、無効な UnitPrice 値 (-50) を入力したときの結果を示しています。

ProductName BoundField には値を含む必要があります

図 9: ProductName BoundField には値を含める必要があります (フルサイズの画像を表示する をクリックします)。

負の UnitPrice 値は使用できません

図 10: 負の UnitPrice 値は許可されません (フルサイズの画像を表示する をクリックします)

e.ExceptionHandled プロパティを true に設定することで、RowUpdated イベント ハンドラーは例外を処理したことを示しています。 そのため、例外は ASP.NET ランタイムに反映されません。

図 9 と図 10 は、無効なユーザー入力によって発生した例外を適切に処理する方法を示しています。 理想的には、そのような無効な入力はそもそもビジネスロジック層に到達しないはずです。ASP.NET ページは、ProductsBLL クラスの UpdateProduct メソッドを呼び出す前に、ユーザーの入力が有効であることを確認する必要があります。 次のチュートリアルでは、ビジネス ロジック レイヤーに送信されたデータがビジネス ルールに準拠していることを確認するために、編集インターフェイスと挿入インターフェイスに検証コントロールを追加する方法について説明します。 検証コントロールは、ユーザーが指定したデータが有効になるまで、 UpdateProduct メソッドの呼び出しを防ぐだけでなく、データ入力の問題を特定するためのより有益なユーザー エクスペリエンスを提供します。

手順 3: BLL-Level 例外を適切に処理する

データを挿入、更新、または削除する際に、データ関連のエラーが発生すると、データアクセス層は例外をスローします。 データベースがオフラインであるか、必要なデータベース テーブル列に値が指定されていないか、テーブル レベルの制約に違反している可能性があります。 ビジネス ロジック レイヤーでは、厳密にデータ関連の例外に加えて、例外を使用して、ビジネス ルールに違反したタイミングを示すことができます。 たとえば、 ビジネス ロジック レイヤーの作成 チュートリアルでは、元の UpdateProduct オーバーロードにビジネス ルール チェックを追加しました。 具体的には、ユーザーが製品を廃止済みとしてマークしている場合は、その製品がサプライヤーによって提供される唯一の製品ではないことを要求しました。 この条件に違反した場合は、ApplicationException がスローされる。

このチュートリアルで作成した UpdateProduct オーバーロードについては、 UnitPrice フィールドが元の UnitPrice 値の 2 倍以上の新しい値に設定されないようにするビジネス ルールを追加しましょう。 これを実現するには、このチェックを実行し、ルールに違反した場合に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

この変更により、既存の価格の 2 倍以上の価格更新が発生すると、 ApplicationException がスローされます。 DAL から発生した例外と同様に、この BLL で発生した ApplicationException は、GridView の RowUpdated イベント ハンドラーで検出および処理できます。 実際、 RowUpdated イベント ハンドラーのコードは、記述されているように、この例外を正しく検出し、 ApplicationExceptionMessage プロパティ値を表示します。 図 11 は、ユーザーが Chai の価格を $50.00 に更新しようとしたときのスクリーンショットを示しています。これは、現在の価格の 2 倍以上の $19.95 です。

ビジネスルールは、製品の価格の2倍以上の価格上昇を禁止します

図 11: ビジネス ルールは、製品の価格の 2 倍以上の値上げを禁止します (フルサイズの画像を表示する をクリックします)。

ビジネス ロジック ルールは、 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 は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は Liz Shulok でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、mitchell@4GuysFromRolla.comにメッセージを送ってください。