既存のバイナリ データの更新と削除 (C#)
前のチュートリアルでは、GridView コントロールを使用してテキスト データを簡単に編集および削除する方法について説明しました。 このチュートリアルでは、バイナリ データがデータベースに保存されているか、ファイル システムに格納されているかに関係なく、GridView コントロールを使用してバイナリ データを編集および削除する方法について説明します。
はじめに
過去 3 回のチュートリアルでは、バイナリ データを操作するための機能をかなり追加しました。 最初に、BrochurePath
列を Categories
テーブルに追加して、それに合わせてアーキテクチャを更新しました。 また、画像ファイルのバイナリ コンテンツを保持するカテゴリ テーブルの既存の Picture
列を操作するためのデータ アクセス層とビジネス ロジック層のメソッドを追加しました。 バイナリ データを GridView (カテゴリの画像が <img>
要素に表示されているパンフレットのダウンロード リンク) に表示する Web ページを作成し、ユーザーが新しいカテゴリを追加してパンフレットと画像のデータをアップロードできるようにするための DetailsView を追加しました。
まだ実装できていないものは、既存のカテゴリを編集および削除する機能だけです。今回のチュートリアルでは、GridView の組み込みの編集および削除の機能を使用してこれを実装します。 カテゴリの編集時に、ユーザーは必要に応じて新しい画像をアップロードしたり、カテゴリで既存のものを引き続き使用したりできるようになります。 パンフレットについては、既存のパンフレットを使用するか、新しいパンフレットをアップロードするか、関連付けられたパンフレットがカテゴリになくなったことを示すかを選択できます。 では、始めましょう。
手順 1: データ アクセス層を更新する
DAL には、自動生成された Insert
、Update
、Delete
メソッドがありますが、これらのメソッドは、Picture
列を含まない CategoriesTableAdapter
のメイン クエリに基づいて生成されたものです。 そのため、Insert
および Update
メソッドには、カテゴリの画像のバイナリ データを指定するためのパラメーターは含まれません。 前のチュートリアルで行ったように、バイナリ データを指定するときに Categories
テーブルを更新するための新しい TableAdapter メソッドを作成する必要があります。
型指定されたデータセットを開き、デザイナーから CategoriesTableAdapter
のヘッダーを右クリックし、コンテキスト メニューから [クエリの追加] を選択して TableAdapter クエリ構成ウィザードを起動します。 最初に、TableAdapter クエリがデータベースにどのようにアクセスするかについてウィザードに尋ねられます。 [SQL ステートメントを使用する] を選択して、[次へ] をクリックします。 次の手順で、生成するクエリの種類について尋ねられます。 Categories
テーブルに新しいレコードを追加するクエリを作成するので、[UPDATE] を選択して [次へ] をクリックします。
図 1: UPDATE オプションの選択 (クリックするとフルサイズの画像が表示されます)
次に、UPDATE
SQL ステートメントを指定する必要があります。 ウィザードが、TableAdapter のメイン クエリ (CategoryName
、Description
、BrochurePath
の値を更新するもの) に対応する UPDATE
ステートメントを自動的に提案します。 以下のように、@Picture
パラメーターとともに Picture
列が含れるようにステートメントを変更します。
UPDATE [Categories] SET
[CategoryName] = @CategoryName,
[Description] = @Description,
[BrochurePath] = @BrochurePath ,
[Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))
ウィザードの最後の画面で、新しい TableAdapter メソッドの名前を付けるように求められます。 「UpdateWithPicture
」と入力し、[完了] をクリックします。
図 2: 新しい TableAdapter メソッドの名前を付ける UpdateWithPicture
(クリックするとフルサイズの画像が表示されます)
手順 2: ビジネス ロジック層メソッドを追加する
DAL の更新に加えて、カテゴリを更新および削除するためのメソッドを含むように BLL を更新する必要があります。 これらは、プレゼンテーション層からから呼び出されるメソッドです。
カテゴリの削除には、CategoriesTableAdapter
の自動生成された Delete
メソッドを使用できます。 次のメソッドを CategoriesBLL
クラスに追加します:
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Delete, true)]
public bool DeleteCategory(int categoryID)
{
int rowsAffected = Adapter.Delete(categoryID);
// Return true if precisely one row was deleted, otherwise false
return rowsAffected == 1;
}
このチュートリアルでは、カテゴリを更新するための 2 つのメソッドを作成します。1 つ目はバイナリ画像データを受け入れて、先ほど CategoriesTableAdapter
に追加した UpdateWithPicture
メソッドを呼び出すもので、もう 1 つは、CategoryName
、Description
、BrochurePath
の値のみを受け入れて CategoriesTableAdapter
クラスの自動生成された Update
ステートメントを使用するものです。 状況によっては、ユーザーによりカテゴリの画像がそれ以外のフィールドと一緒に更新される場合がありますが、その場合、そのユーザーは新しい画像をアップロードする必要があります。これが、2 つのメソッドを使用する理由です。 アップロードされた画像のバイナリ データは、その後 UPDATE
ステートメントで使用できます。 それ以外の状況の場合、ユーザーは名前や説明などのみの更新を行いたいということになります。 ただ、UPDATE
ステートメントが Picture
列のバイナリ データを受け入れることも想定している場合、その情報も提供する必要があります。 この場合、編集されるレコードの画像データを戻すための、データベースへの追加のアクセスが必要になります。 そのため、2 つの UPDATE
メソッドが必要です。 カテゴリの更新時に画像データが提供されるかどうかに基づいて、ビジネス ロジック層がどれを使用するかを決定します。
これを容易にするために、2 つのメソッドを CategoriesBLL
クラスに追加します。どちらも UpdateCategory
という名前です。 1 つ目は、3 つの string
、byte
配列、int
を入力パラメーターとして受け入れます。2 つ目は、3 つの string
と int
のみです。 string
入力パラメーターは、カテゴリの名前、説明、パンフレットのファイル パス用であり、byte
配列は、カテゴリの画像のバイナリ コンテンツ用です。int
は、更新するレコードの CategoryID
を識別します。 渡される byte
配列が null
の場合、最初のオーバーロードが 2 番目を呼び出します。
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, byte[] picture, int categoryID)
{
// If no picture is specified, use other overload
if (picture == null)
return UpdateCategory(categoryName, description, brochurePath, categoryID);
// Update picture, as well
int rowsAffected = Adapter.UpdateWithPicture
(categoryName, description, brochurePath, picture, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateCategory(string categoryName, string description,
string brochurePath, int categoryID)
{
int rowsAffected = Adapter.Update
(categoryName, description, brochurePath, categoryID);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
手順 3: 挿入および表示の機能をコピーする
前のチュートリアルでは、GridView のすべてのカテゴリを一覧表示する UploadInDetailsView.aspx
という名前のページを作成して、新しいカテゴリをシステムに追加するための DetailsView を用意しました。 このチュートリアルでは、GridView を拡張して、編集と削除のサポートを含めます。 UploadInDetailsView.aspx
から作業を引き続き進めるのではなく、このチュートリアルの変更を同じフォルダー ~/BinaryData
の UpdatingAndDeleting.aspx
ページに配置してみましょう。 宣言型マークアップとコードをコピーして UploadInDetailsView.aspx
から UpdatingAndDeleting.aspx
に貼り付けます。
まず、UploadInDetailsView.aspx
ページを開きます。 図 3 に示すように、 <asp:Content>
要素内のすべての宣言構文をコピーします。 次に、UpdatingAndDeleting.aspx
を開き、このマークアップをその <asp:Content>
要素内に貼り付けます。 同様に、UploadInDetailsView.aspx
ページの分離コード クラスのコードを UpdatingAndDeleting.aspx
に貼り付けます。
図 3: 宣言型マークアップを UploadInDetailsView.aspx
からコピーする (クリックするとフルサイズの画像が表示されます)
宣言型マークアップとコードをコピーした後、UpdatingAndDeleting.aspx
にアクセスします。 前のチュートリアルの UploadInDetailsView.aspx
ページと同じ出力が表示され、同じユーザー エクスペリエンスが得られます。
手順 4: ObjectDataSource および GridView に削除のサポートを追加する
「データの挿入、更新、削除の概要」のチュートリアルで説明したように、GridView には組み込みの削除機能が用意されており、これらの機能は、グリッドの基になるデータ ソースで削除がサポートされている場合にチェック ボックスをオンにすることで有効にできます。 現在、GridView がバインドされている ObjectDataSource は (CategoriesDataSource
)は削除をサポートしていません。
これを解決するには、ObjectDataSource のスマート タグの [データ ソースの構成] オプションをクリックしてウィザードを起動します。 最初の画面は、ObjectDataSource が CategoriesBLL
クラスで動作するように構成されていることを示しています。 [次へ] をクリックします。 現時点では、ObjectDataSource の InsertMethod
および SelectMethod
プロパティのみが指定されています。 ただ、[UPDATE] および [DELETE] タブのドロップダウン リストには、ウィザードにより UpdateCategory
および DeleteCategory
メソッドがそれぞれ自動的に設定されています。 これは、CategoriesBLL
クラスで、更新および削除の既定のメソッドとして DataObjectMethodAttribute
を使用してこれらのメソッドをマークしたためです。
ここでは、[UPDATE] タブのドロップダウン リストを (None) に設定しますが、[DELETE] タブのドロップダウン リストは DeleteCategory
に設定したままにします。 手順 6 でこのウィザードに戻り、更新のサポートを追加します。
図 4: DeleteCategory
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
Note
ウィザードを完了すると、Visual Studio によりフィールドとキーを更新するかどうかを尋ねるメッセージをが表示されることがありますが、これは、データ Web コントロール フィールドの再生成を行うものです。 [はい] を選択すると、行ったフィールドのカスタマイズが上書きされるため、[いいえ] を選択します。
これで、ObjectDataSource に DeleteParameter
とともに DeleteMethod
プロパティの値が含まれるようになりました。 ウィザードを使用してメソッドを指定すると、Visual Studio により ObjectDataSource の OldValuesParameterFormatString
プロパティが original_{0}
に設定され、更新および削除メソッドの呼び出しに問題が発生することを思い出してください。 そのため、このプロパティを完全に消去するか、既定値の {0}
にリセットします。 この ObjectDataSource プロパティについて復習する場合は、「データの挿入、更新、削除の概要」のチュートリアルを参照してください。
ウィザードを完了して OldValuesParameterFormatString
を修正すると、ObjectDataSource の宣言型マークアップは次のようになります。
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
ObjectDataSource を構成した後、GridView のスマート タグから [削除を有効にする] チェック ボックスをオンにして、GridView に削除の機能を追加します。 これにより、ShowDeleteButton
プロパティが true
に設定された GridView に CommandField が追加されます。
図 5: GridView での削除のサポートを有効にする (クリックするとフルサイズの画像が表示されます)
少し時間をかけて削除機能をテストします。 Products
テーブルの CategoryID
と Categories
テーブルの CategoryID
の間には外部キーがあるため、最初の 8 つのカテゴリのいずれかを削除しようとすると、外部キー制約違反の例外が発生します。 この機能をテストするために、新しいカテゴリを追加して、パンフレットと画像の両方を追加します。 図 6 に示すテスト カテゴリには、Test.pdf
という名前のテスト パンフレット ファイルとテスト画像が含まれています。 図 7 は、テスト カテゴリが追加された後の GridView を示しています。
図 6: パンフレットと画像を指定してテスト カテゴリを追加する (クリックするとフルサイズの画像が表示されます)
図 7: テスト カテゴリを挿入した後。GridView 内に表示されている (クリックするとフルサイズの画像が表示されます)
Visual Studio で、ソリューション エクスプローラーを更新します。 これで、~/Brochures
フォルダーに新しいファイル Test.pdf
が表示されるようになります (図 8 を参照)。
次に、[テスト カテゴリ] 行の [削除] リンクをクリックすると、ページがポストバックされ、CategoriesBLL
クラスの DeleteCategory
メソッドが起動します。 これにより DAL の Delete
メソッドが呼び出され、適切な DELETE
ステートメントがデータベースに送信されます。 その後、データは GridView に再バインドされ、マークアップはテスト カテゴリが存在しなくなった状態でクライアントに送り返されます。
削除のワークフローにより、テスト カテゴリのレコードが Categories
テーブルから正常に削除されていますが、Web サーバーのファイル システムからパンフレット ファイルは削除されていません。 ソリューション エクスプローラーを更新すると、Test.pdf
がまだ ~/Brochures
フォルダーにあることがわかります。
図 8: Test.pdf
ファイルが Web サーバーのファイル システムから削除されていない
手順 5: 削除されたカテゴリのパンフレット ファイルを削除する
バイナリ データをデータベースの外部に格納することの欠点の 1 つは、関連付けられているデータベース レコードが削除されたときに、これらのファイルをクリーンアップするための追加の手順を行わなければならないことです。 GridView と ObjectDataSource は、delete コマンドが実行される前と後の両方で発生するイベントを提供します。 実際には、アクションの前と後のイベントの両方のイベント ハンドラーを作成する必要があります。 Categories
レコードが削除される前に PDF ファイルのパスを決定する必要がありますが、何かしらの例外があり、カテゴリが削除されていない場合は、カテゴリが削除される前に PDF が削除されないようにする必要があります。
GridView の RowDeleting
イベントは、ObjectDataSource の delete コマンドが呼び出される前に発生しますが、その RowDeleted
イベント は後に発生します。 次のコードを使用して、これら 2 つのイベントのイベント ハンドラーを作成します。
// A page variable to "remember" the deleted category's BrochurePath value
string deletedCategorysPdfPath = null;
protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
// Determine the PDF path for the category being deleted...
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
// Delete the brochure file if there were no problems deleting the record
if (e.Exception == null)
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
}
RowDeleting
イベント ハンドラーでは、削除される行の CategoryID
が GridView の DataKeys
コレクションから取得されます。これは、e.Keys
コレクションを介してこのイベント ハンドラーでアクセスできます。 次に、CategoriesBLL
クラスの GetCategoryByCategoryID(categoryID)
が呼び出され、削除されるレコードに関する情報が返されます。 返された CategoriesDataRow
オブジェクトに NULL``BrochurePath
以外の値がある場合、それはページ変数 deletedCategorysPdfPath
に格納されます。これは、ファイルを RowDeleted
イベント ハンドラーで削除できるようにするためです。
Note
RowDeleting
イベント ハンドラーで削除される Categories
レコードの BrochurePath
の詳細を取得するのではなく、その代わりに BrochurePath
を GridView の DataKeyNames
プロパティに追加して、e.Keys
コレクションを介してレコードの値にアクセスすることもできます。 これにより、GridView のビュー状態のサイズが若干大きくなりますが、必要なコードの量が減り、データベースへのアクセスも削減されます。
ObjectDataSource の基になる delete コマンドが呼び出されると、GridView の RowDeleted
イベント ハンドラーが起動します。 データの削除に例外がなく、deletedCategorysPdfPath
の値がある場合、PDF はファイル システムから削除されます。 この追加のコードは、その画像に関連付けられているカテゴリのバイナリ データをクリーンアップするためには必要ではないことに注意してください。 画像データはデータベースに直接格納されており、そのため Categories
行を削除するとそのカテゴリの画像データも削除されるためです。
2 つのイベント ハンドラーを追加した後、このテスト ケースをもう一度実行します。 カテゴリを削除すると、それに関連付けられている PDF も削除されます。
既存のレコードの関連付けられているバイナリ データを更新すると、いくつかの興味深い課題が発生します。 このチュートリアルの残りの部分では、パンフレットと画像への更新機能の追加について詳しく説明します。 手順 6 では、パンフレット情報を更新する手法について説明します。手順 7 では、画像の更新について説明します。
手順 6: カテゴリのパンフレットを更新する
「データの挿入、更新、削除の概要」のチュートリアルで説明したように、GridView には組み込みの行レベルの編集のサポートが用意されており、これは、基になるデータ ソースが適切に構成されている場合にチェック ボックスをオンにすることで実装できます。 現時点で、CategoriesDataSource
ObjectDataSource は更新のサポートを含むように構成されていないため、これを追加してみましょう。
ObjectDataSource のウィザードから [データ ソースの構成] リンクをクリックして、2 番目の手順に進みます。 CategoriesBLL
で使用されている DataObjectMethodAttribute
のため、[UPDATE] ドロップダウン リストには (Picture
以外のすべての列の) 4 つの入力パラメーターを受け入れる UpdateCategory
オーバーロードが自動的に設定されているはずです。 これを変更して、5 つのパラメーターを持つオーバーロードを使用するようにします。
図 9: Picture
のパラメーターを含む UpdateCategory
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
これで、ObjectDataSource に、対応する UpdateParameter
とともに UpdateMethod
プロパティの値が含まれるようになりました。 手順 4 で説明したように、Visual Studio は、データ ソースの構成ウィザードを使用するときに ObjectDataSource の OldValuesParameterFormatString
プロパティを original_{0}
に設定します。 これにより、更新および削除のメソッドの呼び出しに問題が発生します。 そのため、このプロパティを完全に消去するか、既定値の {0}
にリセットします。
ウィザードを完了して OldValuesParameterFormatString
を修正すると、ObjectDataSource の宣言型マークアップは次のようになります。
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
<asp:Parameter Name="categoryID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
GridView の組み込みの編集機能を有効にするには、GridView のスマート タグから [編集を有効にする] オプションをオンにします。 これにより、CommandField の ShowEditButton
プロパティが true
に設定され、その結果として、[編集] ボタン (および、編集される行の [更新] および [キャンセル] ボタン) が追加されます。
図 10: 編集をサポートするように GridView を構成する (クリックするとフルサイズの画像が表示されます)
ブラウザーからページにアクセスし、いずれかの行の [編集] ボタンをクリックします。 CategoryName
および Description
の BoundField はテキスト ボックスとしてレンダリングされます。 BrochurePath
TemplateField に EditItemTemplate
が不足しているため、ItemTemplate
(パンフレットへのリンク) が引き続き表示されます。 Picture
ImageField は、Text
プロパティに ImageField の DataImageUrlField
値 (この場合は CategoryID
) の値が割り当てられたテキスト ボックスとしてレンダリングされます。
図 11: GridView で BrochurePath
用の編集インターフェイスが不足している (クリックするとフルサイズの画像が表示されます)
BrochurePath
の編集インターフェイスのカスタマイズ
ユーザーが以下の操作のいずれかを行えるようにするための、BrochurePath
TemplateField 用の編集インターフェイスを作成する必要があります。
- カテゴリのパンフレットをそのままの状態にする
- 新しいパンフレットをアップロードしてカテゴリのパンフレットを更新する
- カテゴリのパンフレットを完全に削除する (そのカテゴリに関連するパンフレットがなくなった場合).
また、Picture
ImageField の編集インターフェイスを更新する必要がありますが、これについては手順 7 で説明します。
GridView のスマート タグで、[テンプレートの編集] リンクをクリックし、ドロップダウン リストから BrochurePath
TemplateField の EditItemTemplate
を選択します。 RadioButtonList Web コントロールをこのテンプレートに追加し、ID
プロパティを BrochureOptions
に、AutoPostBack
プロパティを true
に設定します。 プロパティ ウィンドウから Items
プロパティの省略記号をクリックして、ListItem
コレクション エディターを表示します。 次の 3 つのオプションを Value
の 1、2、3 それぞれで追加します。
- 現在のパンフレットを使用する
- 現在のパンフレットを削除する
- 新しいパンフレットをアップロードする
最初の ListItem
の Selected
プロパティを true
に設定します。
図 12: 3 つの ListItem
を RadioButtonList に追加する
RadioButtonList の下に、BrochureUpload
という名前の FileUpload コントロールを追加します。 Visible
プロパティを false
に設定します。
図 13: RadioButtonList および FileUpload コントロールを EditItemTemplate
に追加する (クリックするとフルサイズの画像が表示されます)
この RadioButtonList が、ユーザーに 3 つのオプションを提供します。 FileUpload コントロールが、最後のオプションの [新しいパンフレットのアップロード] が選択されている場合のみ表示されるようにします。 これを実現するために、RadioButtonList の SelectedIndexChanged
イベントのイベント ハンドラーを作成し、次のコードを追加します。
protected void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e)
{
// Get a reference to the RadioButtonList and its Parent
RadioButtonList BrochureOptions = (RadioButtonList)sender;
Control parent = BrochureOptions.Parent;
// Now use FindControl("controlID") to get a reference of the
// FileUpload control
FileUpload BrochureUpload =
(FileUpload)parent.FindControl("BrochureUpload");
// Only show BrochureUpload if SelectedValue = "3"
BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3");
}
RadioButtonList および FileUpload コントロールははテンプレート内に存在するため、これらのコントロールにプログラムでアクセスするためのコードを少し記述する必要があります。 SelectedIndexChanged
イベント ハンドラーには、sender
入力パラメーターで RadioButtonList の参照が渡されます。 FileUpload コントロールを取得するには、RadioButtonList の親コントロールを取得して、そこから FindControl("controlID")
メソッドを使用する必要があります。 RadioButtonList と FileUpload コントロールの両方への参照を取得すると、RadioButtonList の SelectedValue
が 3 (新しいパンフレットのアップロードの ListItem
の Value
)である場合のみ、FileUpload コントロールの Visible
プロパティは true
に設定されます。
このコードを配置して、編集インターフェイスをテストしてみましょう。 行の [編集] ボタンをクリックします。 最初に、[現在のパンフレットを使用] オプションが選択されているはずです。 選択されているインデックスを変更すると、ポストバックが発生します。 3 番目のオプションを選択すると、FileUpload コントロールが表示されます。それ以外の場合は表示されません。 図 14 は、[編集] ボタンが最初にクリックされたときの編集インターフェイスを示しています。図 15 は、[新しいパンフレットのアップロード] オプションが選択された後のインターフェイスを示しています。
図 14: 最初に、[現在のパンフレットを使用] オプションが選択されている (クリックするとフルサイズの画像が表示されます)
図 15: [新しいパンフレットのアップロード] オプションを選択して、FileUpload コントロールを表示する (クリックするとフルサイズの画像が表示されます)
パンフレット ファイルの保存と BrochurePath
列の更新
GridView の [更新] ボタンをクリックすると、その RowUpdating
イベントが発生します。 ObjectDataSource の update コマンドが呼び出され、その後 GridView の RowUpdated
イベントが発生します。 削除のワークフローと同様に、これらの両方のイベントのイベント ハンドラーを作成する必要があります。 RowUpdating
イベント ハンドラーで、BrochureOptions
RadioButtonList の SelectedValue
に基づいて実行するアクションを決定する必要があります。
SelectedValue
が 1 の場合は、同じBrochurePath
設定を引き続き使用します。 そのため、ObjectDataSource のbrochurePath
パラメーターを更新されるレコードの既存のBrochurePath
値に設定する必要があります。 ObjectDataSource のbrochurePath
パラメータは、e.NewValues["brochurePath"] = value
を使用して設定できます。SelectedValue
が 2 の場合は、レコードのBrochurePath
値をNULL
に設定します。 これは、ObjectDataSource のbrochurePath
パラメーターをNothing
に設定することで達成できます。これにより、データベースNULL
がUPDATE
ステートメントで使用されるようになります。 削除される既存のパンフレット ファイルがある場合、その既存のファイルを削除する必要があります。 ただし、例外が発生することなく更新が完了した場合にのみこれを行います。SelectedValue
が 3 の場合は、ユーザーが PDF ファイルをアップロードしたことを確認して、それをファイル システムに保存し、レコードのBrochurePath
列の値を更新します。 さらに、置き換えられる既存のパンフレット ファイルがある場合は、以前のそのファイルを削除する必要があります。 ただし、例外が発生することなく更新が完了した場合にのみこれを行います。
RadioButtonList の SelectedValue
が 3 の場合に実行する必要がある手順は、DetailsView の ItemInserting
イベント ハンドラーで使用されるものと実質的に同じです。 このイベント ハンドラーは、前のチュートリアルで追加した DetailsView コントロールから新しいカテゴリ レコードが追加されたときに実行されます。 そのため、この機能を個別のメソッドにリファクタリングする必要があります。 具体的には、共通の機能を 2 つのメソッドに次のように移しました。
ProcessBrochureUpload(FileUpload, out bool)
は、FileUpload コントロール インスタンスを入力として受け取り、削除または編集の操作を続行するかどうか、何かしらの検証エラーのために操作のキャンセルを実行する必要があるかを指定する出力のブール値を受け取ります。 このメソッドは、保存されたファイルへのパスを返します。ファイルが保存されていない場合はnull
を返します。DeleteRememberedBrochurePath
は、deletedCategorysPdfPath
がnull
ではない場合に、ページ変数deletedCategorysPdfPath
のパスで指定されたファイルを削除します。
これら 2 つのメソッドのコードは次のとおりです。 ProcessBrochureUpload
と、前のチュートリアルの DetailsView の ItemInserting
イベント ハンドラーの類似性に着目してください。 このチュートリアルでは、これらの新しいメソッドを使用するように DetailsView のイベント ハンドラーが更新されています。 このチュートリアルに関連付けられているコードをダウンロードして、DetailsView のイベント ハンドラーに加えられた変更を確認します。
private string ProcessBrochureUpload
(FileUpload BrochureUpload, out bool CancelOperation)
{
CancelOperation = false; // by default, do not cancel operation
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
CancelOperation = true;
return null;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
return brochurePath;
}
else
{
// No file uploaded
return null;
}
}
private void DeleteRememberedBrochurePath()
{
// Is there a file to delete?
if (deletedCategorysPdfPath != null)
{
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath));
}
}
GridView の RowUpdating
および RowUpdated
イベント ハンドラーは、次のコードに示すように、ProcessBrochureUpload
および DeleteRememberedBrochurePath
メソッドを使用します。
protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
// Reference the RadioButtonList
RadioButtonList BrochureOptions =
(RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions");
// Get BrochurePath information about the record being updated
int categoryID = Convert.ToInt32(e.Keys["CategoryID"]);
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (BrochureOptions.SelectedValue == "1")
{
// Use current value for BrochurePath
if (category.IsBrochurePathNull())
e.NewValues["brochurePath"] = null;
else
e.NewValues["brochurePath"] = category.BrochurePath;
}
else if (BrochureOptions.SelectedValue == "2")
{
// Remove the current brochure (set it to NULL in the database)
e.NewValues["brochurePath"] = null;
}
else if (BrochureOptions.SelectedValue == "3")
{
// Reference the BrochurePath FileUpload control
FileUpload BrochureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload");
// Process the BrochureUpload
bool cancelOperation = false;
e.NewValues["brochurePath"] =
ProcessBrochureUpload(BrochureUpload, out cancelOperation);
e.Cancel = cancelOperation;
}
else
{
// Unknown value!
throw new ApplicationException(
string.Format("Invalid BrochureOptions value, {0}",
BrochureOptions.SelectedValue));
}
if (BrochureOptions.SelectedValue == "2" ||
BrochureOptions.SelectedValue == "3")
{
// "Remember" that we need to delete the old PDF file
if (category.IsBrochurePathNull())
deletedCategorysPdfPath = null;
else
deletedCategorysPdfPath = category.BrochurePath;
}
}
protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
// If there were no problems and we updated the PDF file,
// then delete the existing one
if (e.Exception == null)
{
DeleteRememberedBrochurePath();
}
}
RowUpdating
イベント ハンドラーが一連の条件付きステートメントを使用して、BrochureOptions
RadioButtonList の SelectedValue
プロパティ値に基づきどのようにして適切なアクションを実行するかに着目してください。
このコードを配置することで、カテゴリを編集して、現在のパンフレットを使用するか、パンフレットを使用しないか、新しいものをアップロードするかを選べるようになります。 先に進んで試してみましょう。ワークフローについて理解できるように、RowUpdating
および RowUpdated
イベント ハンドラーにブレークポイントを設定します。
手順 7: 新しい画像をアップロードする
Picture
ImageField の編集インターフェイスは、DataImageUrlField
プロパティの値が入力されたテキスト ボックスとしてレンダリングされます。 編集ワークフロー中、GridView はパラメーターを ObjectDataSource に渡します。このパラメーターの名前は ImageField の DataImageUrlField
プロパティの値に、パラメーターの値は編集インターフェイスのテキスト ボックスに入力された値になります。 この動作は、画像がファイル システムにファイルとして保存されていて、DataImageUrlField
にその画像の完全な URL が含まれる場合には適しています。 そのような場合、編集インターフェイスのテキスト ボックスには画像の URL が表示されます。ユーザーはこれを変更することでデータベースに保存し直すことができます。 確かに、この既定のインターフェイスでユーザーは新しい画像をアップロードすることはできませんが、画像の URL を現在の値から別のものに変更することは可能です。 ただ、今回のチュートリアルの場合、Picture
バイナリ データはデータベースに直接格納され、DataImageUrlField
プロパティは CategoryID
のみを保持するため、ImageField の既定の編集インターフェイスは十分ではありません。
今回のチュートリアルの場合で、ユーザーが ImageField を含む行を編集すると何が発生するか理解するために、CategoryID
が 10 の行をユーザーが編集する結果として、Picture
ImageField が値が 10 の状態でテキスト ボックスとしてレンダリングされる例を考えてみましょう。 ユーザーがこのテキスト ボックスの値を 50 に変更し、[更新] ボタンをクリックするとします。 ポストバックが発生し、GridView が値が 50 で CategoryID
という名前のパラメーターを最初に作成します。 ただ、GridView はこのパラメーター (および CategoryName
と Description
パラメーター) を送信する前に、DataKeys
コレクションからの値を追加します。 そのため、CategoryID
パラメーターは、基になる現在の行の CategoryID
値である 10 で上書きされます。 要するに、このチュートリアルの場合では、ImageField の編集インターフェイスは編集のワークフローに影響しません。これは、ImageField の DataImageUrlField
プロパティとグリッドの DataKey
値の名前がまったく同じであるためです。
ImageField を使用すれば、データベースのデータに基づいて画像を簡単に表示させることはできますが、編集インターフェイスにテキスト ボックスを表示させたくはありません。 そうではなく、カテゴリの画像を変更するためにエンド ユーザーが使用できる、FileUpload コントロールを提供します。 BrochurePath
値とは異なり、これらのチュートリアルでは、各カテゴリに画像が含まれることを必須としています。 そのため、関連付けられた画像がないことをユーザーが示せるようにする必要はありません。ユーザーは、新しい画像のアップロードを行うか、現在の画像をそのままにするかのいずれかを行います。
ImageField の編集インターフェイスをカスタマイズするには、これを TemplateField に変換する必要があります。 GridView のスマート タグで、[列の編集] リンクをクリックし、[ImageField] を選択して、[このフィールドを TemplateField に変換] リンクをクリックします。
図 16: ImageField を TemplateField に変換する
この方法で ImageField を TemplateField に変換すると、2 つのテンプレートを含む TemplateField が生成されます。 次の宣言構文に示すように、ItemTemplate
には、ImageField の DataImageUrlField
および DataImageUrlFormatString
プロパティに基づくデータ バインド構文を使用して、ImageUrl
プロパティが割り当てられている Image Web コントロールが含まれています。 EditItemTemplate
には、DataImageUrlField
プロパティにより指定された値にバインドされた Text
プロパティを持つテキスト ボックスが含まれます。
<asp:TemplateField>
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Eval("CategoryID") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# Eval("CategoryID",
"DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
</ItemTemplate>
</asp:TemplateField>
FileUpload コントロールを使用するように EditItemTemplate
を更新する必要があります。 GridView のスマート タグから、[テンプレートの編集] リンクをクリックし、ドロップダウン リストから Picture
TemplateField の EditItemTemplate
を選択します。 テンプレートにテキスト ボックスが表示されるので、これを削除します。 次に、FileUpload コントロールをツールボックスからテンプレートにドラッグし、その ID
を PictureUpload
に設定します。 また、「カテゴリの画像を変更するには、新しい画像を指定してください。 カテゴリの画像を同じままにするには、フィールドを空のままにしてください。」というテキストをテンプレートに追加します。
図 17: EditItemTemplate
に FileUpload コントロールを追加する (クリックするとフルサイズの画像が表示されます)
編集インターフェイスをカスタマイズしたら、ブラウザーで進行状況を確認します。 読み取り専用モードで行を表示すると、カテゴリの画像は前と同じように表示されます。ただ、[編集] ボタンをクリックすると、画像の列がテキストとして、FileUpload コントロールとともにレンダリングされます。
図 18: 編集インターフェイスに FileUpload コントロールが含まれている (クリックするとフルサイズの画像が表示されます)
ObjectDataSource は、入力としての画像のバイナリ データを byte
配列として受け入れる CategoriesBLL
クラスの UpdateCategory
メソッドを呼び出すように構成されていることを思い出してください。 ただ、この配列に null
値がある場合、代替の UpdateCategory
オーバーロードが呼び出され、これにより Picture
列を変更しない UPDATE
SQL ステートメントが発行されるため、カテゴリの現在の画像がそのままの状態で残ります。 そのため、GridView の RowUpdating
イベント ハンドラーで、プログラムによって PictureUpload
FileUpload コントロールを参照し、ファイルがアップロードされたかどうかを判断する必要があります。 アップロードされていない場合、picture
パラメーターの値を指定しません。 一方で、ファイルが PictureUpload
FileUpload コントロールにアップロードされた場合には、それが JPG ファイルであることを確かめる必要があります。 そうである場合は、そのバイナリ コンテンツをpicture
パラメーターを使用して ObjectDataSource に送信できます。
手順 6 で使用するコードと同様で、ここで必要なコードのほとんどはDetailsView の ItemInserting
イベント ハンドラーにずでに存在します。 そのため、共通の機能を新しいメソッド ValidPictureUpload
にリファクタリングして、このメソッドを使用するように ItemInserting
イベント ハンドラーを更新しました。
GridView の RowUpdating
イベント ハンドラーの先頭に次のコードを追加します。 このコードを、パンフレット ファイルを保存するコードよりも前に配置することが重要であり、その理由は、無効な画像ファイルがアップロードされた場合にはパンフレットを Web サーバーのファイル システムに保存しないようにするためです。
// Reference the PictureUpload FileUpload
FileUpload PictureUpload =
(FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure the picture upload is valid
if (ValidPictureUpload(PictureUpload))
{
e.NewValues["picture"] = PictureUpload.FileBytes;
}
else
{
// Invalid file upload, cancel update and exit event handler
e.Cancel = true;
return;
}
}
ValidPictureUpload(FileUpload)
メソッドは、FileUpload コントロールを唯一の入力パラメーターとして受け取り、アップロードされたファイルの拡張子をチェックして、アップロードされたファイルが JPG であることを確認します。これは、画像ファイルがアップロードされた場合のみ呼び出されます。 ファイルがアップロードされていない場合、picture パラメーターは設定されないため、既定値の null
が使用されます。 画像がアップロードされた場合で ValidPictureUpload
が true
を返す場合、picture
パラメーターにはアップロードされた画像のバイナリ データが割り当てられます。メソッドが false
を返す場合、更新ワークフローがキャンセルされ、イベント ハンドラーが終了します。
DetailsView の ItemInserting
イベント ハンドラーからリファクタリングされた ValidPictureUpload(FileUpload)
メソッドのコードは次のとおりです。
private bool ValidPictureUpload(FileUpload PictureUpload)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
return false;
}
else
{
return true;
}
}
手順 8: 元のカテゴリの画像を JPG に置き換える
元の 8 つのカテゴリの画像は、OLE ヘッダーにラップされたビットマップ ファイルであることを思い出してください。 既存のレコードの画像を編集する機能を追加したので、少し時間をかけてこれらのビットマップを JPG に置き換えてみましょう。 現在のカテゴリの画像を引き続き使用する場合は、次の手順を実行してそれらを JPG に変換できます。
- ビットマップ画像をハード ドライブに保存します。 ブラウザーの
UpdatingAndDeleting.aspx
ページにアクセスして、最初の 8 つのカテゴリごとに画像を右クリックし、画像の保存を選択します。 - 任意のイメージ エディターで画像を開きます。 たとえば、Microsoft Paint を使用できます。
- ビットマップを JPG 画像として保存します。
- その JPG ファイルを使用して、編集インターフェイスからカテゴリの画像を更新します。
カテゴリを編集して JPG 画像をアップロードした後、その画像はブラウザーでレンダリングされません。これは、DisplayCategoryPicture.aspx
ページにより、最初の 8 つのカテゴリの画像から最初の 78 バイトが削除されるためです。 これを修正するには、OLE ヘッダーの削除を実行するコードを削除します。 これを行った後、DisplayCategoryPicture.aspx``Page_Load
イベント ハンドラーには次のコードだけが含まれるようになります。
protected void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories = _
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
// For new categories, images are JPGs...
// Output HTTP headers providing information about the binary data
Response.ContentType = "image/jpeg";
// Output the binary data
Response.BinaryWrite(category.Picture);
}
Note
UpdatingAndDeleting.aspx
ページの挿入と編集のインターフェイスには、もう少し作業が必要になるかもしれません。 DetailsView および GridView の CategoryName
および Description
BoundField を、TemplateField に変換する必要があります。 CategoryName
は NULL
値を許可しないため、RequiredFieldValidator を追加する必要があります。 そして、Description
テキスト ボックスはおそらく、複数行のテキスト ボックスに変換する必要があります。 この最後の仕上げについてはここで扱わないので、演習として行ってみてください。
まとめ
このチュートリアルでは、バイナリ データの操作に関する説明を完了しました。 今回のチュートリアル、およびこれまでの 3 回のチュートリアルで、バイナリ データがどのようにファイル システムに格納されるか、あるいはデータベース内のディレクトリに直接格納されるかを説明しました。 ユーザーは、ハード ドライブからファイルを選択して Web サーバーにアップロードすることで、バイナリ データをシステムに提供します。これは、ファイル システムに格納することも、データベースに挿入することもできます。 ASP.NET 2.0 には、ドラッグ アンド ドロップ操作で簡単にそのようなインターフェイスを作成できる、FileUpload コントロールが用意されています。 ただ、「ファイルのアップロード」のチュートリアルで説明したように、FileUpload コントロールは、比較的小さいファイルのアップロードにのみ適しており、MB を超えないのが理想的です。 また、アップロードされたデータを基になるデータ モデルと関連付ける方法と、既存のレコードからのバイナリ データを編集および削除する方法についても説明しました。
次の一連のチュートリアルでは、さまざまなキャッシュの手法について説明します。 キャッシュを使用することで、負荷の高い操作の結果を取得し、より迅速にアクセスできる場所にこれを格納することができるため、アプリケーションの全体としてのパフォーマンスを改善させることができます。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Teresa Murphy でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。