Share via


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

作成者: Scott Mitchell

PDF のダウンロード

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

はじめに

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

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

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

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

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

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

前のチュートリアルでは、 と の 2 つのフィールド ProductName のみを含む編集可能な GridView を作成しました UnitPrice。 これには、各製品フィールドのUpdateProductパラメーターではなく、3 つの入力パラメーター (製品名、単価、ID) のみを受け入れたクラスのメソッドに対して追加のオーバーロードProductsBLLを作成する必要がありました。 このチュートリアルでは、この手法をもう一度練習して、製品名、単位あたりの数量、単価、在庫単位を表示する編集可能な 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 ページを作成する準備ができました。 フォルダー内のErrorHandling.aspxページをEditInsertDelete開き、Designerを使用してページに GridView を追加します。 GridView を新しい ObjectDataSource にバインドし、メソッドをSelect()クラスのGetProducts()メソッドにProductsBLL、メソッドをUpdate()先ほど作成したUpdateProductオーバーロードにマッピングします。

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

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

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

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

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

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

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

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 には BoundField が含まれています QuantityPerUnit が、この BoundField は表示目的でのみ使用し、ユーザーが編集することはできません。 これを配置するには、BoundFields の ReadOnly プロパティを に true設定するだけです。

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

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

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

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

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

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

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

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

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

編集可能な GridView は、ユーザーが編集した製品の名前、価格、および単位の有効な値を在庫に入力すると素晴らしく機能しますが、無効な値を入力すると例外が発生します。 たとえば、値をProductName省略すると、クラスAllowDBNullProductsRow プロパティのプロパティが に設定falseされているためProductNameNoNullAllowedException がスローされます。データベースがダウンしている場合、SqlExceptionデータベースに接続しようとすると TableAdapter によって がスローされます。 これらの例外は、何も行わずに、データ アクセス層からビジネス ロジック層にバブル アップし、次に [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 ページに Label を追加し、そのプロパティを IDExceptionDetails 設定し、そのプロパティを Text クリアします。 このメッセージにユーザーの目を引くには、その CssClass プロパティを に Warning設定します。これは、前のチュートリアルでファイルに Styles.css 追加した CSS クラスです。 この CSS クラスにより、ラベルのテキストが赤、斜体、太字、特大フォントで表示されることを思い出してください。

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

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

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

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

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

注意

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

Label コントロールを追加した次の手順では、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

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

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

このコードでは、 が ではないnullかどうかをException確認チェック必要があります。これは、操作の実行中に例外が発生したことを意味します。 この場合は、次の操作を行います。

  • ラベルにわかりやすいメッセージを表示する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

このイベント ハンドラーは、 が nullかどうかをe.Exception確認することから始まります。 そうでない場合、Label Visible の プロパティは にtrue設定され、ExceptionDetailsそのTextプロパティは "製品の更新中に問題が発生しました" に設定されます。スローされた実際の例外の詳細は、 e.Exception オブジェクトの InnerException プロパティにあります。 この内部例外が調べられ、特定の型の場合は、Label Text の プロパティにExceptionDetails追加の有用なメッセージが追加されます。 最後に、 ExceptionHandled プロパティと KeepInEditMode プロパティの両方が に true設定されます。

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

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

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

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

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

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

注意

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

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

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

このチュートリアルで作成したUpdateProductオーバーロードについて、フィールドが元UnitPriceの値の 2 倍以上の新しい値に設定されることを禁止UnitPriceするビジネス ルールを追加しましょう。 これを実現するには、このチェックを実行し、ルールに違反した場合に を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コードは、記述されているように、この例外を正しく検出し、 の Message プロパティ値をApplicationException表示します。 図 11 は、ユーザーが Chai の価格を 50.00 ドル (現在の価格の 19.95 ドルの 2 倍以上) に更新しようとしたときのスクリーン ショットを示しています。

ビジネス ルールでは、製品の価格が 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 は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。

特別な感謝

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