Share via


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

作成者: 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 の作成

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

このシナリオに対応するには、UpdateProduct メソッドのもう 1 つのオーバーロードが必要です。これは、製品の名前、単価、在庫単位、ID の 4 つのパラメータを受け取ります。 次のメソッドを 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;
}

このメソッドが完成すると、これら 4 つの特定の製品フィールドを編集できる ASP.NET ページを作成できます。 EditInsertDelete フォルダー内の ErrorHandling.aspx ページを開き、デザイナーを使用してページに GridView を追加します。 GridView を新しい ObjectDataSource にバインドし、Select() メソッドを ProductsBLL クラスの GetProducts() メソッドにマッピングし、Update() メソッドを先ほど作成した UpdateProduct オーバーロードにマッピングします。

Use the UpdateProduct Method Overload That Accepts Four Input Parameters

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

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

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

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

Configure the UnitPrice BoundField to Display as a Currency

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

編集インターフェイスで UnitPrice を通貨として書式設定するには、通貨形式の文字列を decimal 値に解析する GridView の RowUpdating イベントのイベント ハンドラーを作成する必要があります。 1 つ前のチュートリアルの RowUpdating イベント ハンドラーも、ユーザーが UnitPrice 値を確実に指定するための確認を行ったのを思い出してください。 ただし、このチュートリアルでは、ユーザーが価格を省略できるようにします。

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 BoundField が含まれますが、この BoundField は表示目的でのみ使用し、ユーザーが編集可能にはしません。 これを行うには、BoundFields の ReadOnly プロパティを true に設定します。

Make the QuantityPerUnit BoundField Read-Only

図 3: QuantityPerUnit BoundField を読み取り専用にする (フルサイズの画像を表示するにはクリック)

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

Remove All But the Needed BoundFields and Check the Enable Editing Checkbox

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

この時点で、すべての製品の ProductNameQuantityPerUnitUnitPriceUnitsInStock の各フィールドのリストがあります。ただし、編集できるのは、ProductNameUnitPriceUnitsInStock の各フィールドのみです。

Users Can Now Easily Edit Products' Names, Prices, and Units In Stock Fields

図 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 を経由したときに表示される既定の詳細なエラー レポートです。

Omitting the Product's Name Will Display Exception Details

図 6: 製品の名前を省略すると、例外の詳細が表示される (フルサイズの画像を表示するにはクリック)

このような例外の詳細は、アプリケーションをテストするときには役立ちますが、例外の表現としてエンド ユーザーにこのような画面を表示するのは、理想的ではありません。 エンド ユーザーは、NoNullAllowedException が何かわからない、またはその原因がわからないと考えられます。 よりよい方法は、製品の更新を試みた際に問題が発生したことを説明するわかりやすいメッセージをユーザーに表示することです。

操作の実行時に例外が発生した場合、ObjectDataSource とデータ Web コントロールの両方の事後レベル イベントは、それを検出し、ASP.NET ランタイムに例外がバブリングするのを取り消す手段を提供します。 この例では、GridView の RowUpdated イベントのイベント ハンドラーを作成して、例外が発生したかどうかを判断し、発生した場合は、Label Web コントロールに例外の詳細を表示します。

まず、ASP.NET ページにラベルを追加し、その ID プロパティを ExceptionDetails に設定し、その Text プロパティをクリアします。 ユーザーの目をこのメッセージに向けるために、その CssClass プロパティを Warning に設定します。これは、前のチュートリアルで Styles.css ファイルに追加した CSS クラスです。 この CSS クラスでは、ラベルのテキストが大きな赤、斜体、太字のフォントで表示されることを思い出してください。

Add a Label Web Control to the Page

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

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

protected void Page_Load(object sender, EventArgs e)
{
    ExceptionDetails.Visible = false;
}

このコードでは、ページへの初回アクセス時とその後のポストバック時に、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 イベントのイベント ハンドラーも作成します。

Create an Event Handler for the GridView's RowUpdated Event

図 8: GridView の RowUpdated イベントのイベント ハンドラーを作成する

Note

分離コード クラス ファイルの上部にあるドロップダウン リストを使用して、イベント ハンドラーを作成することもできます。 左側のドロップダウン リストから GridView を選択し、右側のドロップダウン リストから RowUpdated イベントを選択します。

このイベント ハンドラーを作成すると、ASP.NET ページの分離コード クラスに次のコードが追加されます。

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}

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

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

次は、このコードで Exceptionnull でないことを確認する必要があります。これは、操作の実行中に例外が発生したことを意味します。 その場合は、次の措置が実行されるようにします。

  • ExceptionDetails ラベルにわかりやすいメッセージを表示する
  • 例外が処理済みであることを示す
  • GridView 行を編集モードのままにする

次のコードは、これらの目標を達成します。

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.Exceptionnull かどうかを確認することから始まります。 そうでない場合、ExceptionDetails ラベルの Visible プロパティは true に設定され、その Text プロパティは "There was a problem updating the product." に設定されます。スローされた実際の例外の詳細は、e.Exception オブジェクトの InnerException プロパティ内にあります。 この内部例外が調べられ、これが特定の種類の場合、ExceptionDetails ラベルの Text プロパティに追加の有用なメッセージが追加されます。 最後に、ExceptionHandled プロパティと KeepInEditMode プロパティの両方が true に設定されます。

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

The ProductName BoundField Must Contain a Value

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

Negative UnitPrice Values are Not Allowed

図 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 オーバーロードを調整します。 更新されたメソッドは次のとおりです。

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;
}

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

The Business Rules Disallow Price Increases That More Than Double a Product's Price

図 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行をドロップしてください。