次の方法で共有


既存のバイナリ データの更新と削除 (C#)

作成者: Scott Mitchell

PDF のダウンロード

前のチュートリアルでは、GridView コントロールを使用してテキスト データを簡単に編集および削除する方法について説明しました。 このチュートリアルでは、バイナリ データがデータベースに保存されているか、ファイル システムに格納されているかに関係なく、GridView コントロールを使用してバイナリ データを編集および削除する方法について説明します。

はじめに

過去 3 回のチュートリアルでは、バイナリ データを操作するための機能をかなり追加しました。 最初に、BrochurePath 列を Categories テーブルに追加して、それに合わせてアーキテクチャを更新しました。 また、画像ファイルのバイナリ コンテンツを保持するカテゴリ テーブルの既存の Picture 列を操作するためのデータ アクセス層とビジネス ロジック層のメソッドを追加しました。 バイナリ データを GridView (カテゴリの画像が <img> 要素に表示されているパンフレットのダウンロード リンク) に表示する Web ページを作成し、ユーザーが新しいカテゴリを追加してパンフレットと画像のデータをアップロードできるようにするための DetailsView を追加しました。

まだ実装できていないものは、既存のカテゴリを編集および削除する機能だけです。今回のチュートリアルでは、GridView の組み込みの編集および削除の機能を使用してこれを実装します。 カテゴリの編集時に、ユーザーは必要に応じて新しい画像をアップロードしたり、カテゴリで既存のものを引き続き使用したりできるようになります。 パンフレットについては、既存のパンフレットを使用するか、新しいパンフレットをアップロードするか、関連付けられたパンフレットがカテゴリになくなったことを示すかを選択できます。 では、始めましょう。

手順 1: データ アクセス層を更新する

DAL には、自動生成された InsertUpdateDelete メソッドがありますが、これらのメソッドは、Picture 列を含まない CategoriesTableAdapter のメイン クエリに基づいて生成されたものです。 そのため、Insert および Update メソッドには、カテゴリの画像のバイナリ データを指定するためのパラメーターは含まれません。 前のチュートリアルで行ったように、バイナリ データを指定するときに Categories テーブルを更新するための新しい TableAdapter メソッドを作成する必要があります。

型指定されたデータセットを開き、デザイナーから CategoriesTableAdapter のヘッダーを右クリックし、コンテキスト メニューから [クエリの追加] を選択して TableAdapter クエリ構成ウィザードを起動します。 最初に、TableAdapter クエリがデータベースにどのようにアクセスするかについてウィザードに尋ねられます。 [SQL ステートメントを使用する] を選択して、[次へ] をクリックします。 次の手順で、生成するクエリの種類について尋ねられます。 Categories テーブルに新しいレコードを追加するクエリを作成するので、[UPDATE] を選択して [次へ] をクリックします。

Select the UPDATE Option

図 1: UPDATE オプションの選択 (クリックするとフルサイズの画像が表示されます)

次に、UPDATE SQL ステートメントを指定する必要があります。 ウィザードが、TableAdapter のメイン クエリ (CategoryNameDescriptionBrochurePath の値を更新するもの) に対応する UPDATE ステートメントを自動的に提案します。 以下のように、@Picture パラメーターとともに Picture 列が含れるようにステートメントを変更します。

UPDATE [Categories] SET 
    [CategoryName] = @CategoryName, 
    [Description] = @Description, 
    [BrochurePath] = @BrochurePath ,
    [Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))

ウィザードの最後の画面で、新しい TableAdapter メソッドの名前を付けるように求められます。 「UpdateWithPicture」と入力し、[完了] をクリックします。

Name the New TableAdapter Method 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 つは、CategoryNameDescriptionBrochurePath の値のみを受け入れて CategoriesTableAdapter クラスの自動生成された Update ステートメントを使用するものです。 状況によっては、ユーザーによりカテゴリの画像がそれ以外のフィールドと一緒に更新される場合がありますが、その場合、そのユーザーは新しい画像をアップロードする必要があります。これが、2 つのメソッドを使用する理由です。 アップロードされた画像のバイナリ データは、その後 UPDATE ステートメントで使用できます。 それ以外の状況の場合、ユーザーは名前や説明などのみの更新を行いたいということになります。 ただ、UPDATE ステートメントが Picture 列のバイナリ データを受け入れることも想定している場合、その情報も提供する必要があります。 この場合、編集されるレコードの画像データを戻すための、データベースへの追加のアクセスが必要になります。 そのため、2 つの UPDATE メソッドが必要です。 カテゴリの更新時に画像データが提供されるかどうかに基づいて、ビジネス ロジック層がどれを使用するかを決定します。

これを容易にするために、2 つのメソッドを CategoriesBLL クラスに追加します。どちらも UpdateCategory という名前です。 1 つ目は、3 つの stringbyte 配列、int を入力パラメーターとして受け入れます。2 つ目は、3 つの stringint のみです。 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 から作業を引き続き進めるのではなく、このチュートリアルの変更を同じフォルダー ~/BinaryDataUpdatingAndDeleting.aspx ページに配置してみましょう。 宣言型マークアップとコードをコピーして UploadInDetailsView.aspx から UpdatingAndDeleting.aspx に貼り付けます。

まず、UploadInDetailsView.aspx ページを開きます。 図 3 に示すように、 <asp:Content> 要素内のすべての宣言構文をコピーします。 次に、UpdatingAndDeleting.aspx を開き、このマークアップをその <asp:Content> 要素内に貼り付けます。 同様に、UploadInDetailsView.aspx ページの分離コード クラスのコードを UpdatingAndDeleting.aspx に貼り付けます。

Copy the Declarative Markup from UploadInDetailsView.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 でこのウィザードに戻り、更新のサポートを追加します。

Configure the ObjectDataSource to Use the DeleteCategory Method

図 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 が追加されます。

Enable Support for Deleting in the GridView

図 5: GridView での削除のサポートを有効にする (クリックするとフルサイズの画像が表示されます)

少し時間をかけて削除機能をテストします。 Products テーブルの CategoryIDCategories テーブルの CategoryID の間には外部キーがあるため、最初の 8 つのカテゴリのいずれかを削除しようとすると、外部キー制約違反の例外が発生します。 この機能をテストするために、新しいカテゴリを追加して、パンフレットと画像の両方を追加します。 図 6 に示すテスト カテゴリには、Test.pdf という名前のテスト パンフレット ファイルとテスト画像が含まれています。 図 7 は、テスト カテゴリが追加された後の GridView を示しています。

Add a Test Category with a Brochure and Image

図 6: パンフレットと画像を指定してテスト カテゴリを追加する (クリックするとフルサイズの画像が表示されます)

After Inserting the Test Category, it is Displayed in the GridView

図 7: テスト カテゴリを挿入した後。GridView 内に表示されている (クリックするとフルサイズの画像が表示されます)

Visual Studio で、ソリューション エクスプローラーを更新します。 これで、~/Brochures フォルダーに新しいファイル Test.pdf が表示されるようになります (図 8 を参照)。

次に、[テスト カテゴリ] 行の [削除] リンクをクリックすると、ページがポストバックされ、CategoriesBLL クラスの DeleteCategory メソッドが起動します。 これにより DAL の Delete メソッドが呼び出され、適切な DELETE ステートメントがデータベースに送信されます。 その後、データは GridView に再バインドされ、マークアップはテスト カテゴリが存在しなくなった状態でクライアントに送り返されます。

削除のワークフローにより、テスト カテゴリのレコードが Categories テーブルから正常に削除されていますが、Web サーバーのファイル システムからパンフレット ファイルは削除されていません。 ソリューション エクスプローラーを更新すると、Test.pdf がまだ ~/Brochures フォルダーにあることがわかります。

The Test.pdf File Was Not Deleted from the Web Server s File System

図 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 つのパラメーターを持つオーバーロードを使用するようにします。

Configure the ObjectDataSource to Use the UpdateCategory Method that Includes a Parameter for Picture

図 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 に設定され、その結果として、[編集] ボタン (および、編集される行の [更新] および [キャンセル] ボタン) が追加されます。

Configure the GridView to Support Editing

図 10: 編集をサポートするように GridView を構成する (クリックするとフルサイズの画像が表示されます)

ブラウザーからページにアクセスし、いずれかの行の [編集] ボタンをクリックします。 CategoryName および Description の BoundField はテキスト ボックスとしてレンダリングされます。 BrochurePath TemplateField に EditItemTemplate が不足しているため、ItemTemplate (パンフレットへのリンク) が引き続き表示されます。 Picture ImageField は、Text プロパティに ImageField の DataImageUrlField 値 (この場合は CategoryID) の値が割り当てられたテキスト ボックスとしてレンダリングされます。

The GridView Lacks an Editing Interface for BrochurePath

図 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 それぞれで追加します。

  • 現在のパンフレットを使用する
  • 現在のパンフレットを削除する
  • 新しいパンフレットをアップロードする

最初の ListItemSelected プロパティを true に設定します。

Add Three ListItems to the RadioButtonList

図 12: 3 つの ListItem を RadioButtonList に追加する

RadioButtonList の下に、BrochureUpload という名前の FileUpload コントロールを追加します。 Visible プロパティを false に設定します。

Add a RadioButtonList and FileUpload Control to the EditItemTemplate

図 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 (新しいパンフレットのアップロードの ListItemValue)である場合のみ、FileUpload コントロールの Visible プロパティは true に設定されます。

このコードを配置して、編集インターフェイスをテストしてみましょう。 行の [編集] ボタンをクリックします。 最初に、[現在のパンフレットを使用] オプションが選択されているはずです。 選択されているインデックスを変更すると、ポストバックが発生します。 3 番目のオプションを選択すると、FileUpload コントロールが表示されます。それ以外の場合は表示されません。 図 14 は、[編集] ボタンが最初にクリックされたときの編集インターフェイスを示しています。図 15 は、[新しいパンフレットのアップロード] オプションが選択された後のインターフェイスを示しています。

Initially, the Use current brochure Option is Selected

図 14: 最初に、[現在のパンフレットを使用] オプションが選択されている (クリックするとフルサイズの画像が表示されます)

Choosing the Upload new brochure Option Displays the FileUpload Control

図 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 に設定することで達成できます。これにより、データベース NULLUPDATE ステートメントで使用されるようになります。 削除される既存のパンフレット ファイルがある場合、その既存のファイルを削除する必要があります。 ただし、例外が発生することなく更新が完了した場合にのみこれを行います。
  • SelectedValue が 3 の場合は、ユーザーが PDF ファイルをアップロードしたことを確認して、それをファイル システムに保存し、レコードの BrochurePath 列の値を更新します。 さらに、置き換えられる既存のパンフレット ファイルがある場合は、以前のそのファイルを削除する必要があります。 ただし、例外が発生することなく更新が完了した場合にのみこれを行います。

RadioButtonList の SelectedValue が 3 の場合に実行する必要がある手順は、DetailsView の ItemInserting イベント ハンドラーで使用されるものと実質的に同じです。 このイベント ハンドラーは、前のチュートリアルで追加した DetailsView コントロールから新しいカテゴリ レコードが追加されたときに実行されます。 そのため、この機能を個別のメソッドにリファクタリングする必要があります。 具体的には、共通の機能を 2 つのメソッドに次のように移しました。

  • ProcessBrochureUpload(FileUpload, out bool) は、FileUpload コントロール インスタンスを入力として受け取り、削除または編集の操作を続行するかどうか、何かしらの検証エラーのために操作のキャンセルを実行する必要があるかを指定する出力のブール値を受け取ります。 このメソッドは、保存されたファイルへのパスを返します。ファイルが保存されていない場合は null を返します。
  • DeleteRememberedBrochurePath は、deletedCategorysPdfPathnull ではない場合に、ページ変数 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 はこのパラメーター (および CategoryNameDescription パラメーター) を送信する前に、DataKeys コレクションからの値を追加します。 そのため、CategoryID パラメーターは、基になる現在の行の CategoryID 値である 10 で上書きされます。 要するに、このチュートリアルの場合では、ImageField の編集インターフェイスは編集のワークフローに影響しません。これは、ImageField の DataImageUrlField プロパティとグリッドの DataKey 値の名前がまったく同じであるためです。

ImageField を使用すれば、データベースのデータに基づいて画像を簡単に表示させることはできますが、編集インターフェイスにテキスト ボックスを表示させたくはありません。 そうではなく、カテゴリの画像を変更するためにエンド ユーザーが使用できる、FileUpload コントロールを提供します。 BrochurePath 値とは異なり、これらのチュートリアルでは、各カテゴリに画像が含まれることを必須としています。 そのため、関連付けられた画像がないことをユーザーが示せるようにする必要はありません。ユーザーは、新しい画像のアップロードを行うか、現在の画像をそのままにするかのいずれかを行います。

ImageField の編集インターフェイスをカスタマイズするには、これを TemplateField に変換する必要があります。 GridView のスマート タグで、[列の編集] リンクをクリックし、[ImageField] を選択して、[このフィールドを TemplateField に変換] リンクをクリックします。

Convert the ImageField Into a 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 コントロールをツールボックスからテンプレートにドラッグし、その IDPictureUpload に設定します。 また、「カテゴリの画像を変更するには、新しい画像を指定してください。 カテゴリの画像を同じままにするには、フィールドを空のままにしてください。」というテキストをテンプレートに追加します。

Add a FileUpload Control to the EditItemTemplate

図 17: EditItemTemplate に FileUpload コントロールを追加する (クリックするとフルサイズの画像が表示されます)

編集インターフェイスをカスタマイズしたら、ブラウザーで進行状況を確認します。 読み取り専用モードで行を表示すると、カテゴリの画像は前と同じように表示されます。ただ、[編集] ボタンをクリックすると、画像の列がテキストとして、FileUpload コントロールとともにレンダリングされます。

The Editing Interface Includes a FileUpload Control

図 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 が使用されます。 画像がアップロードされた場合で ValidPictureUploadtrue を返す場合、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 に変換できます。

  1. ビットマップ画像をハード ドライブに保存します。 ブラウザーの UpdatingAndDeleting.aspx ページにアクセスして、最初の 8 つのカテゴリごとに画像を右クリックし、画像の保存を選択します。
  2. 任意のイメージ エディターで画像を開きます。 たとえば、Microsoft Paint を使用できます。
  3. ビットマップを JPG 画像として保存します。
  4. その 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 に変換する必要があります。 CategoryNameNULL 値を許可しないため、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行をドロップしてください。