BLL レベルと DAL レベルの例外を処理する (C#)
このチュートリアルでは、編集可能な DataList の更新ワークフロー中に発生する例外を適切に処理する方法について説明します。
はじめに
DataList のデータの編集と削除の概要のチュートリアルでは、簡単な編集と削除の機能を提供する DataList を作成しました。 完全に機能する一方で、編集または削除プロセス中に発生したエラーによって未処理の例外が発生しているため、ユーザーフレンドリとは言い難いものでした。 たとえば、製品名を省略したり、製品を編集するときに価格の値を「手頃な価格」と入力すると、例外がスローされます。 この例外はコードでキャッチされないため、ASP.NET ランタイムに吹き出しが表示され、Web ページに例外の詳細が表示されます。
「ASP.NET ページで BLL レベルと DAL レベルの例外を処理する」のチュートリアルで説明したとおり、ビジネス ロジックまたはデータ アクセス層の深部から例外が発生した場合、例外の詳細は ObjectDataSource に返され、次に GridView に返されます。 ObjectDataSource または GridView の Updated
または RowUpdated
イベント ハンドラーを作成し、例外を確認し、例外が処理されたことを示すことによって、これらの例外を正常に処理する方法について説明しました。
ただし、DataList チュートリアルでは、データの更新と削除に ObjectDataSource を使用していません。 代わりに、BLL に対して直接働きかけています。 BLL または DAL から発生した例外を検出するには、ASP.NET ページのコードビハインド内に例外処理コードを実装する必要があります。 このチュートリアルでは、編集可能な DataList の更新ワークフロー中に発生する例外をより適切に処理する方法について説明します。
Note
「DataList のデータの編集と削除の概要」のチュートリアルでは、DataList からデータを編集および削除するためのさまざまな手法について説明しました。これには、ObjectDataSource を使用して更新と削除を行う手法がいくつかあります。 これらの手法を採用する場合は、ObjectDataSource の Updated
または Deleted
イベント ハンドラーを使用して BLL または DAL からの例外を処理できます。
手順 1: 編集可能な DataList の作成
更新ワークフロー中に発生する例外の処理を心配する前に、まずは編集可能な DataList を作成しましょう。 EditDeleteDataList
フォルダー内の ErrorHandling.aspx
ページを開き、Designer に DataList を追加し、その ID
プロパティを Products
に設定して、ProductsDataSource
という名前の新しい ObjectDataSource を追加します。 レコードを選択するために ProductsBLL
クラスの GetProducts()
メソッドを使用するように ObjectDataSource を構成します。INSERT タブ、UPDATE タブ、DELETE タブのドロップダウン リストを (None) に設定します。
図 1: GetProducts()
メソッドを使用して製品情報を返す (クリックするとフルサイズの画像が表示されます)
ObjectDataSource ウィザードが完了すると、Visual Studio は DataList 用の ItemTemplate
オブジェクトを自動的に作成します。 これを ItemTemplate
に置き換えると、各製品の名前と価格が表示され、[編集] ボタンが表示されます。 次に、名前と価格の TextBox Web コントロールと、更新ボタンとキャンセル ボタンを持つ EditItemTemplate
を作成します。 最後に、DataList の RepeatColumns
プロパティを 2 に設定します。
これらの変更後、GridView の宣言型マークアップは次のようになります。 [編集]、[キャンセル]、[更新] ボタンの CommandName
プロパティがそれぞれ [編集]、[キャンセル]、[更新] に設定されていることを確認するには、ダブルチェックします。
<asp:DataList ID="Products" runat="server" DataKeyField="ProductID"
DataSourceID="ProductsDataSource" RepeatColumns="2">
<ItemTemplate>
<h5>
<asp:Label runat="server" ID="ProductNameLabel"
Text='<%# Eval("ProductName") %>' />
</h5>
Price:
<asp:Label runat="server" ID="Label1"
Text='<%# Eval("UnitPrice", "{0:C}") %>' />
<br />
<asp:Button runat="server" id="EditProduct" CommandName="Edit"
Text="Edit" />
<br />
<br />
</ItemTemplate>
<EditItemTemplate>
Product name:
<asp:TextBox ID="ProductName" runat="server"
Text='<%# Eval("ProductName") %>' />
<br />
Price:
<asp:TextBox ID="UnitPrice" runat="server"
Text='<%# Eval("UnitPrice", "{0:C}") %>' />
<br />
<br />
<asp:Button ID="UpdateProduct" runat="server" CommandName="Update"
Text="Update" />
<asp:Button ID="CancelUpdate" runat="server" CommandName="Cancel"
Text="Cancel" />
</EditItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
SelectMethod="GetProducts" TypeName="ProductsBLL"
OldValuesParameterFormatString="original_{0}">
</asp:ObjectDataSource>
Note
このチュートリアルでは、DataList のビュー状態を有効にする必要があります。
ブラウザー経由で進行状況を確認します (図 2 参照)。
図 2: [編集] ボタンを含む各製品 (クリックするとフルサイズの画像が表示されます)
現在、[編集] ボタンはポストバックを行うだけで、製品を編集可能にすることはありません。 編集を可能にするには、DataListの EditCommand
、CancelCommand
、および UpdateCommand
イベント用のイベント ハンドラーを作成する必要があります。 EditCommand
および CancelCommand
イベントは、単純に DataList の EditItemIndex
プロパティを更新し、データを DataList に再バインドします。
protected void Products_EditCommand(object source, DataListCommandEventArgs e)
{
// Set the DataList's EditItemIndex property to the
// index of the DataListItem that was clicked
Products.EditItemIndex = e.Item.ItemIndex;
// Rebind the data to the DataList
Products.DataBind();
}
protected void Products_CancelCommand(object source, DataListCommandEventArgs e)
{
// Set the DataList's EditItemIndex property to -1
Products.EditItemIndex = -1;
// Rebind the data to the DataList
Products.DataBind();
}
UpdateCommand
イベント ハンドラーはもう少し複雑です。 編集された製品の ProductID
を DataKeys
コレクションから EditItemTemplate
の TextBox から製品の名前および価格と共に読み込み、DataList を編集前の状態に戻す前に ProductsBLL
クラスの UpdateProduct
メソッドを呼び出します。
ここでは、DataList のデータの編集と削除の概要の UpdateCommand
に関するチュートリアルのイベント ハンドラーとまったく同じコードを使ってみましょう。 手順 2 で、例外を正常に処理するためのコードを追加します。
protected void Products_UpdateCommand(object source, DataListCommandEventArgs e)
{
// Read in the ProductID from the DataKeys collection
int productID = Convert.ToInt32(Products.DataKeys[e.Item.ItemIndex]);
// Read in the product name and price values
TextBox productName = (TextBox)e.Item.FindControl("ProductName");
TextBox unitPrice = (TextBox)e.Item.FindControl("UnitPrice");
string productNameValue = null;
if (productName.Text.Trim().Length > 0)
productNameValue = productName.Text.Trim();
decimal? unitPriceValue = null;
if (unitPrice.Text.Trim().Length > 0)
unitPriceValue = Decimal.Parse(unitPrice.Text.Trim(),
System.Globalization.NumberStyles.Currency);
// Call the ProductsBLL's UpdateProduct method...
ProductsBLL productsAPI = new ProductsBLL();
productsAPI.UpdateProduct(productNameValue, unitPriceValue, productID);
// Revert the DataList back to its pre-editing state
Products.EditItemIndex = -1;
Products.DataBind();
}
単価の書式が不適切であったり、-$5.00 のように不正な単価が入力されたり、製品名が省略されたりした場合、例外が発生します。 この時点では、UpdateCommand
イベント ハンドラーに例外処理コードが含まれていないため、例外はエンド ユーザーに表示される ASP.NET ランタイムに吹き出しで表示されます (図 3 参照)。
図 3: 未処理の例外が発生した場合にエンド ユーザーに表示されたエラー ページ
手順 2: UpdateCommand イベント ハンドラーでの正常な例外の処理
更新ワークフロー中に、UpdateCommand
イベント ハンドラー、BLL、または DAL で例外が発生する可能性があります。 たとえば、ユーザが [高すぎる] 価格を入力した場合、UpdateCommand
イベント ハンドラーの Decimal.Parse
ステートメントは FormatException
例外をスローします。 ユーザーが製品名を省略したり、価格が負の値である場合、DAL で例外が発生します。
例外が発生した場合は、ページ自体に情報メッセージが表示されます。 ID
が ExceptionDetails
に設定されているページに Label Web コントロールを追加します。 その CssClass
プロパティを Styles.css
ファイルで定義されている Warning
CSS クラスに割り当てて、ラベルのテキストを赤、特大、太字、斜体で表示するように構成します。
エラーが発生した場合は、ラベルを 1 回だけ表示します。 つまり、その後のポストバックでは、ラベルの警告メッセージは表示されなくなります。 これは、ラベルの Text
プロパティをクリアするか、Page_Load
イベント ハンドラーでその Visible
プロパティを False
に設定するか (チュートリアル「ASP.NET ページで BLL レベルと DAL レベルの例外を処理する」で行ったように)、ラベルのビュー状態のサポートを無効にすることで実現できます。 後者のオプションを使用してみましょう。
<asp:Label ID="ExceptionDetails" EnableViewState="False" CssClass="Warning"
runat="server" />
例外が発生したら、例外の詳細を ExceptionDetails
Label コントロールの Text
プロパティに割り当てます。 そのビュー状態は無効であるため、その後のポストバックでは Text
プロパティのプログラムによる変更は失われ、既定のテキスト (空の文字列) に戻り、警告メッセージは非表示になります。
ページに役立つメッセージを表示するために、エラーが発生したタイミングを判断するには、Try ... Catch
ブロックを UpdateCommand
イベント ハンドラーに追加する必要があります。 Try
の部分には、例外につながる可能性のあるコードが含まれ、Catch
ブロックには、例外が発生した場合に実行されるコードが含まれます。 Try ... Catch
ブロックの詳細については、.NET Framework ドキュメントの「例外処理の基本事項」セクションを参照してください。
protected void Products_UpdateCommand(object source, DataListCommandEventArgs e)
{
// Handle any exceptions raised during the editing process
try
{
// Read in the ProductID from the DataKeys collection
int productID = Convert.ToInt32(Products.DataKeys[e.Item.ItemIndex]);
... Some code omitted for brevity ...
}
catch (Exception ex)
{
// TODO: Display information about the exception in ExceptionDetails
}
}
Try
ブロック内のコードで何らかの型の例外がスローされると、Catch
ブロックのコードが実行を開始します。 DbException
、NoNullAllowedException
、ArgumentException
とスローされる例外のタイプは、最初にエラーを引き起こした内容によって異なります。 データベース レベルで問題が発生した場合は、DbException
がスローされます。 UnitPrice
、UnitsInStock
、UnitsOnOrder
、ReorderLevel
フィールドに不正な値が入力された場合、ArgumentException
がスローされます。それは、ProductsDataTable
クラスにこれらのフィールド値を検証するコードを追加したためです (チュートリアル「ビジネス ロジック層を作成する」参照)。
キャッチされた例外の種類に基づいてメッセージ テキストを基にすることで、エンド ユーザーにさらに役立つ説明を提供できます。 チュートリアル「ASP.NET ページで BLL レベルと DAL レベルの例外を処理する」でほぼ同じ形式で使用した次のコードでは、次のレベルの詳細が提供されます。
private void DisplayExceptionDetails(Exception ex)
{
// Display a user-friendly message
ExceptionDetails.Text = "There was a problem updating the product. ";
if (ex is System.Data.Common.DbException)
ExceptionDetails.Text += "Our database is currently experiencing problems.
Please try again later.";
else if (ex is NoNullAllowedException)
ExceptionDetails.Text += "There are one or more required fields that are
missing.";
else if (ex is ArgumentException)
{
string paramName = ((ArgumentException)ex).ParamName;
ExceptionDetails.Text +=
string.Concat("The ", paramName, " value is illegal.");
}
else if (ex is ApplicationException)
ExceptionDetails.Text += ex.Message;
}
このチュートリアルを完了するには、Catch
ブロックから DisplayExceptionDetails
メソッドを呼び出し、キャッチした Exception
インスタンス (ex
) を渡すだけです。
Try ... Catch
ブロックを配置すると、図 4 と図 5 に示すように、ユーザーには詳細なエラー メッセージが表示されます。 例外が発生しても DataList は編集モードのままであることに注意してください。 これは、例外が発生すると、制御フローが直ちに Catch
ブロックにリダイレクトされ、DataList を編集前の状態に戻すコードがバイパスされるためです。
図 4: ユーザーが必須フィールドを省略した場合に表示されるエラー メッセージ (クリックするとフルサイズの画像が表示されます)
図 5: 負の価格を入力した場合に表示されるエラー メッセージ (クリックするとフルサイズの画像が表示されます)
まとめ
GridView と ObjectDataSource には、更新と削除のワークフロー中に発生した例外に関する情報と、例外が処理されたかどうかを示すために設定できるプロパティを含む、ポスト レベルのイベント ハンドラーが用意されています。 ただし、これらの機能は、DataList を操作して BLL を直接使用する場合は使用できません。 代わりに、例外処理を実装する責任があります。
このチュートリアルでは、Try ... Catch
ブロックを UpdateCommand
イベント ハンドラーに追加することによって、編集可能な DataList の更新ワークフローに例外処理を追加する方法について説明しました。 更新ワークフロー中に例外が発生した場合、Catch
ブロックのコードが実行され、ExceptionDetails
ラベルに役立つ情報が表示されます。
この時点で、DataList では例外の発生を未然に防ぐ取り組みは行われていません。 マイナスの価格を入力すると例外が発生することがわかっていても、ユーザーがそのような無効な入力をするのを未然に防ぐ機能はまだ追加されていません。 次のチュートリアルでは、EditItemTemplate
に検証コントロールを追加することで、無効なユーザー入力によって例外が発生しにくくする方法について説明します。
プログラミングに満足!
もっと読む
この記事で説明したトピックの詳細については、次のリソースを参照してください。
- 例外のデザインのガイドライン
- エラー ログ モジュールとハンドラー (ELMAH) (エラーをログに記録するためのオープン ソース ライブラリ)
- .NET Framework 2.0 用 Enterprise Library (例外管理アプリケーション ブロックを含む)
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Ken Pespisas でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。