次の方法で共有


新しいレコードを追加するとき、ファイル アップロード オプションを含める (C#)

作成者: Scott Mitchell

PDF のダウンロード

このチュートリアルでは、テキスト データの入力とバイナリ ファイルのアップロードの両方をユーザーが行なうための、Web インターフェイスの作成方法について説明します。 バイナリ データを格納するために使用できるオプションを示すために、1 つのファイルをデータベースに保存し、もう 1 つのファイルをファイル システムに格納します。

はじめに

前の 2 つのチュートリアルでは、アプリケーションのデータ モデルに関連付けられているバイナリ データを格納する方法、FileUpload コントロールを使用してクライアントから Web サーバーにファイルを送信する方法、およびデータ Web コントロール内にバイナリ データを表示する方法について説明しました。 ただし、アップロードされたデータをデータ モデルに関連付ける方法については、まだ説明していません。

このチュートリアルでは、新しいカテゴリを追加する Web ページを作成していきます。 カテゴリの名前と説明のための TextBox に加えて、このページには、新しいカテゴリの画像用とパンフレット用に、2 つの FileUpload コントロールを含める必要があります。 アップロードされた画像は新しいレコードの Picture 列に直接保存されますが、パンフレットは、新しいレコードの BrochurePath 列に保存されたファイルへのパスとともに ~/Brochures フォルダーに保存されます。

この新しい Web ページを作成する前に、アーキテクチャを更新する必要があります。 CategoriesTableAdapter のメイン クエリでは、Picture 列が取得されません。 その結果、自動生成された Insert メソッドは、フィールド CategoryNameDescription、 および BrochurePath に対する 入力のみを含みます。 そのため、TableAdapter で、4 つの Categories フィールドすべてに対して入力を要求する追加のメソッドを作成する必要があります。 また、ビジネス ロジック レイヤーの CategoriesBLL クラスも更新する必要があります。

手順 1:CategoriesTableAdapterInsertWithPicture メソッドを追加する

前出の 「データ アクセス層の作成」チュートリアルで CategoriesTableAdapter を作成した際、メイン クエリに基づき INSERTUPDATEDELETE ステートメントを自動的に生成するような構成を行ないました。 さらに、InsertUpdateDelete メソッドを作成した DB ダイレクト アプローチを採用するように、TableAdapter に指示しました。 これらのメソッドは自動生成された INSERTUPDATE、および DELETE ステートメントを実行し、その結果として、メイン クエリによって返される列に基づいた入力パラメーターを受け入れます。 「ファイルのアップロード」チュートリアルでは、BrochurePath 列を使用するように、CategoriesTableAdapter のメイン クエリを拡張しました。

CategoriesTableAdapter のメイン クエリでは Picture 列が参照されないため、Picture 列の値を使用して新しいレコードを追加したり、既存のレコードを更新したりすることはできません。 この情報をキャプチャするためには、特にバイナリ データを含むレコードを挿入する際に使用される新しいメソッドを TableAdapter で作成するか、自動生成された INSERT ステートメントをカスタマイズします。 自動生成された INSERT ステートメントをカスタマイズする際に問題となるのは、ウィザードによってカスタマイズの内容が上書きされるリスクがあることです。 たとえば、Picture 列を使用するように INSERT ステートメントをカスタマイズしたとします。 これにより、そのカテゴリの画像のバイナリ データに対する追加の入力パラメーターを含むように、TableAdapter の Insert メソッドが更新されます。 その後は、ビジネス ロジック レイヤーにメソッドを作成し、この DAL メソッドを使用してプレゼンテーション レイヤー経由でこの BLL メソッドを呼び出すことができるので、すべてがうまく機能します。 この状態は、次に TableAdapter 構成ウィザードを使用して TableAdapter を構成するまで継続します。 ウィザードが完了するとすぐに、ステートメントの INSERT カスタマイズが上書きされ、 Insert メソッドは古い形式に戻るので、追加したコードはコンパイルされなくなります。

Note

アドホック SQL ステートメントの代わりにストアド プロシージャを使用する場合は、この煩わしさは問題でなくなります。 今後のチュートリアルでは、データ アクセス層のアドホック SQL ステートメントの代わりに、ストアド プロシージャを使用する方法について見ていきます。

この潜在的な困りごとを避けるため、自動生成された SQL ステートメントをカスタマイズするのではなく、代わりに TableAdapter に対して新しいメソッドを作成することにしましょう。 この、InsertWithPicture という名前のメソッドは、CategoryNameDescriptionBrochurePath、および Picture 列の値を受け取り、さらに INSERT ステートメントを実行して、新しいレコードに 4 つの値をすべて格納します。

型指定されたデータセットを開き、デザイナーから CategoriesTableAdapter ヘッダーを右クリックし、コンテキスト メニューから [クエリの追加] を選択します。 これにより、TableAdapter クエリ構成ウィザードが起動し、TableAdapter クエリがデータベースにアクセスする方法を確認してきます。 [SQL ステートメントを使用する] を選択して、[次へ] をクリックします。 次の手順では、生成するクエリの型を確認するプロンプトが表示されます。 ここでは、Categories テーブルに新しいレコードを追加するクエリを作成しているので、[INSERT] を選択して [次へ] をクリックします。

Select the INSERT Option

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

次に、INSERT SQL ステートメントを指定する必要があります。 ウィザードが、TableAdapter のメイン クエリに対応する INSERT ステートメントを自動的に提案します。 この場合は、CategoryNameDescription、および BrochurePath 値を挿入する INSERT ステートメントが提案されます。 次のように、@Picture パラメーターと共に Picture 列が含まれるようにステートメントを更新します。

INSERT INTO [Categories] 
    ([CategoryName], [Description], [BrochurePath], [Picture]) 
VALUES 
    (@CategoryName, @Description, @BrochurePath, @Picture)

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

Name the New TableAdapter Method InsertWithPicture

図 2: 新しい TableAdapter メソッドに名前を付ける InsertWithPicture (クリックするとフルサイズの画像が表示されます)

手順 2: ビジネス ロジック レイヤーを更新する

プレゼンテーション レイヤーは、データ アクセス層に直接バイパスして到達することはなく、ビジネス ロジックレイヤーとのみインターフェイスする必要があるため、先ほど作成した DAL メソッドを呼び出すために、BLL メソッドを作成する必要があります (InsertWithPicture)。 このチュートリアルでは、3 つの string と 1 つの byte 配列を入力として受け入れる、InsertWithPicture という名前の CategoriesBLL クラスにメソッドを作成します。 入力パラメーター string は、カテゴリの名前、説明、パンフレット ファイルのパス向けであり、一方 byte 配列は、カテゴリの画像のバイナリ コンテンツ用です。 次のコードに示すように、この BLL メソッドでは対応する DAL メソッドを呼び出します。

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Insert, false)] 
public void InsertWithPicture(string categoryName, string description, 
    string brochurePath, byte[] picture)
{
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture);
}

Note

InsertWithPicture メソッドを BLL に追加する前に、型指定されたデータセットが保存されていることを確認します。 CategoriesTableAdapter クラスのコードは、型指定されたデータ セットに基づいて自動生成されるため、この型指定されたデータ セットに対する変更を最初に保存しないと、 Adapter プロパティは InsertWithPicture メソッドを認識しません。

手順 3: 既存のカテゴリとそのバイナリ データを一覧表示する

このチュートリアルでは、エンド ユーザーが新しいカテゴリをシステムに追加するためのページを作成し、新しいカテゴリの画像とパンフレットを指定します。 前のチュートリアル では、TemplateField と ImageField で GridView を使用して、各カテゴリの名前、説明、画像、およびパンフレットをダウンロードするためのリンクを表示しました。 ここでは、このチュートリアルの機能を複製して、既存のすべてのカテゴリを一覧表示し、新しいカテゴリを作成できるページを作成しましょう。

まず、BinaryData フォルダーから DisplayOrDownload.aspx ページを開きます。 [ソース] ビューに移動し、GridView と ObjectDataSource の宣言構文をコピーし、それを UploadInDetailsView.aspx<asp:Content> 要素内に貼り付けます。 また、分離コード クラス DisplayOrDownload.aspx から GenerateBrochureLink メソッドを UploadInDetailsView.aspx にコピーすることを忘れないでください.

Copy and Paste the Declarative Syntax from DisplayOrDownload.aspx to UploadInDetailsView.aspx

図 3: DisplayOrDownload.aspx から UploadInDetailsView.aspx に宣言構文コピーおよび貼り付ける (クリックするとフルサイズの画像をを表示します)

宣言型の構文と GenerateBrochureLink メソッドを UploadInDetailsView.aspx ページに コピーした後、ブラウザーでページを表示して、すべてが正しくコピーされたことを確認します。 GridView により、パンフレットをダウンロードするためのリンクとカテゴリの画像を含む 8 つのカテゴリが、一覧表示されていることを確認してください。

You Should Now See Each Category Along with Its Binary Data

図 4: 各カテゴリとそのバイナリ データが表示されることを確認する (クリックするとフルサイズの画像を表示します)

手順 4: 挿入をサポートするように CategoriesDataSource を構成する

この時点で、Categories GridView で使用される CategoriesDataSource ObjectDataSource には、データを挿入する機能がありません。 このデータ ソース コントロール経由での挿入をサポートするには、その Insert メソッドを、基盤オブジェクト CategoriesBLL のメソッドにマップする必要があります。 具体的には、前の手順 2 InsertWithPicture で追加した CategoriesBLL メソッドに、これをマップします。

まず、ObjectDataSource のスマート タグから [データ ソースの構成] リンクをクリックします。 最初の画面には、データ ソースと連携するように構成されているオブジェクト CategoriesBLL が表示されます。 この設定はそのままにし、[次へ] をクリックして [データメソッドの定義] 画面に進みます。 [INSERT] タブに移動し、ドロップダウン リストから InsertWithPicture メソッドを選択します。 [完了] をクリックして、ウィザードを完了します。

Configure the ObjectDataSource to use the InsertWithPicture Method

図 5: InsertWithPicture メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)

Note

このウィザードを完了すると、Visual Studio は、データの Web コントロールフィールドを再生成するために、フィールドとキーを更新するかどうかを確認してきます。 [はい] を選択すると、実行済みのフィールドのカスタマイズがすべて上書きされてしまうので、ここでは [いいえ] を選択します。

次の宣言型マークアップが示すように、ウィザードが完了すると、ObjectDataSource には、その InsertMethod プロパティと 4 つのカテゴリ列の InsertParameters の値が含まれるようになります。

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories" 
    TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
    <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>
</asp:ObjectDataSource>

手順 5: 挿入インターフェイスを作成する

最初に「データの挿入、更新、削除の概要」で説明したように、DetailsView コントロールには、挿入をサポートするデータ ソース コントロールを操作するときに使用できる、組み込みの挿入インターフェイスが用意されています。 このページの GridView の上に、挿入インターフェイスを恒久的ににレンダリングする DetailsView コントロールを追加し、ユーザーが新しいカテゴリをすばやく追加できるようにします。 DetailsView に新しいカテゴリを追加すると、その下で GridView が自動的に更新され、新しいカテゴリが表示されます。

まず、GridView の上にある Designer に、ツールボックスから DetailsView をドラッグし、その ID プロパティを NewCategory に設定し、Height プロパティと Width プロパティの値をクリアします。 DetailsView のスマート タグから既存の CategoriesDataSource タグにバインドし、[挿入を有効にする] チェック ボックスをオンにします。

Screenshot showing DetailsView open with the CategoryID property set to NewCategory, empty Height and Width property values and the Enable Inserting checkbox selected.

図 6: DetailsView を CategoriesDataSource にバインドし、挿入を有効化する (クリックするとフルサイズの画像を表示します)

挿入インターフェイスで DetailsView を恒久的にレンダリングするには、その DefaultMode プロパティを Insert に設定します。

DetailsView にはCategoryIDCategoryNameDescriptionNumberOfProducts、および BrochurePath の、5 つの BoundFields がありますが、CategoryID の BoundField は挿入インターフェイスにレンダリングされないことに注意してください。これは、その InsertVisible プロパティが false に設定されているためです。 これらの BoundFields が存在するのは、GetCategories() メソッドによって返される列であるためで、このメソッドは、データを取得するために ObjectDataSource により呼び出されます。 ただし、挿入の場合は、ユーザーに NumberOfProducts の値を指定させたくありません。 加えて、新しいカテゴリの画像のアップロードと、パンフレット用 PDF のアップロードを、ユーザーに許可する必要があります。

DetailsView から NumberOfProducts BoundField を完全に削除し、CategoryName および BrochurePath BoundFields の HeaderText プロパティを、それぞれ Category と Brochure に更新します。 次に、BrochurePath BoundField を TemplateField に変換し、この TemplateField を画像に対し追加し、この新しい TemplateField に Picture の HeaderText 値を指定します。 BrochurePath TemplateField と CommandField の間になるように、Picture TemplateField を移動します。

Screenshot of the fields window with TemplateField, Picture, and HeaderText highlighted.

図 7: DetailsView を CategoriesDataSource にバインドし 挿入を有効にする

[フィールドの編集] ダイアログ ボックスを使用して BrochurePath BoundField を TemplateField に変換すると、その TemplateField には ItemTemplateEditItemTemplate、および InsertItemTemplate が含まれます。 ただし、必要なのは InsertItemTemplate だけなので、他の 2 つのテンプレートを削除してもまったく問題ありません。 この時点で、DetailsView の宣言型構文は次のようになります。

<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    DefaultMode="Insert">
    <Fields>
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
            <InsertItemTemplate>
                <asp:TextBox ID="TextBox1" runat="server"
                    Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
            </InsertItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Picture"></asp:TemplateField>
        <asp:CommandField ShowInsertButton="True" />
    </Fields>
</asp:DetailsView>

Brochure と Picture のフィールドに FileUpload コントロールを追加する

現在、 BrochurePath TemplateField の InsertItemTemplate には TextBox が含まれていますが、Picture TemplateField には、含まれているテンプレートはありません。 これら 2 つの TemplateField の InsertItemTemplate を、FileUpload コントロールを使用するように更新する必要があります。

DetailsView のスマート タグから、[テンプレートの編集] オプションを選択し、ドロップダウン リストから BrochurePath TemplateField の InsertItemTemplate を選択 します。 TextBox を削除し、ツールボックスからテンプレートに FileUpload コントロールをドラッグします。 FileUpload コントロールの IDBrochureUpload に設定します。. 同様に、FileUpload コントロールを Picture TemplateField の InsertItemTemplate に追加します。 この FileUpload コントロールの IDPictureUpload.に設定します。

Add a FileUpload Control to the InsertItemTemplate

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

これらの追加を行った後、2 つの TemplateField の宣言構文は次のようになります。

<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
    <InsertItemTemplate>
        <asp:FileUpload ID="BrochureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
    <InsertItemTemplate>
        <asp:FileUpload ID="PictureUpload" runat="server" />
    </InsertItemTemplate>
</asp:TemplateField>

ユーザーが新しいカテゴリを追加する際には、パンフレットと画像が正しいファイルの種類であることを確認する必要があります。 パンフレットに対しては、ユーザーは PDF を提供する必要があります。 画像の場合、ユーザーがそのファイルをアップロードする必要がありますが、任意の画像ファイルを許可するか、GIF や JPG など、特定の種類の画像ファイルのみを許可するかが問題です。 さまざまなファイルの種類を許可するには、Categories スキーマを拡張して、ファイルの種類をキャプチャする列を含める必要があります。これにより、この種類を DisplayCategoryPicture.aspxResponse.ContentType 経由でクライアントに送信できます。 ただ既定では、このような列はないため、特定のイメージ ファイルの種類のみを指定するように、ユーザーを制限することをお勧めします。 Categories テーブルの既存の画像はビットマップですが、Web 経由での画像提供には、JPG がより適したファイル形式です。

ユーザーが適切ではないファイルの種類をアップロードした場合は、その挿入をキャンセルし、問題を示すメッセージを表示する必要があります。 DetailsView の下に Label Web コントロールを追加します。 その ID プロパティを UploadWarning に設定し、その Text プロパティをクリアし、CssClass プロパティを Warning に設定して、Visible および EnableViewState プロパティを false に設定します。 Warning CSS クラスは Styles.css の中で定義されており、テキストを大きく、赤く、斜体かつ太字のフォントでレンダリングします。

Note

CategoryNameDescription の BoundFields は TemplateFields に変換され、それらの挿入インターフェイスがカスタマイズされるのが理想的です。 たとえば、Description の挿入インターフェイスは、複数行のテキスト ボックスを使用する方が適していると言えます。 また、CategoryName 列は NULL の値を受け入れないので、ユーザーに新しいカテゴリ名の値を指定させるように、 RequiredFieldValidator を追加する必要があります。 これらの手順は、ご自身の演習として実施してください。 データ変更インターフェイスの拡張の詳細については、「データ変更インターフェイスをカスタマイズする」を再確認してください。

手順 6: アップロードしたパンフレットを Web サーバーのファイル システムに保存する

ユーザーが新しいカテゴリの値を入力し、[挿入] ボタンをクリックすると、ポストバックが発生し、挿入ワークフローが展開されます。 まず、DetailsView の ItemInserting イベントが発生します。 次に、ObjectDataSource の Insert() メソッドが呼び出され、その結果、新しいレコードが Categories テーブルに追加されます。 その後、DetailsView の ItemInserted イベント が発生します。

ObjectDataSource の Insert() メソッドが呼び出される前に、まず適切なファイルの種類がユーザーによってアップロードされたことを確認してから、パンフレットの PDF を Web サーバーのファイル システムに保存する必要があります。 DetailsView の ItemInserting イベントのイベント ハンドラーを作成し、次のコードを追加します。

// Reference the FileUpload control
FileUpload BrochureUpload = 
    (FileUpload)NewCategory.FindControl("BrochureUpload");
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;
        e.Cancel = true;
        return;
    }
}

イベント ハンドラーは、まず、DetailsView のテンプレートから BrochureUpload FileUpload コントロールを参照します。 その後、パンフレットがアップロードされている場合には、アップロードされたファイルの拡張子が検証されます。 拡張子が .PDF ではない場合は、警告が表示され、挿入処理は取り消され、さらにイベント ハンドラーの実行が終了します。

Note

アップロードされたファイルの拡張子に依存することは、アップロードされたファイルが PDF ドキュメントであることを確認するための確実な手法ではありません。 ユーザーは、拡張子 .Brochure が付いた有効な PDF ドキュメントを使用したり、PDF 以外のドキュメントを取得して .pdf 拡張子を付けたりする可能性があります。 ファイルの種類をより確定的に検証するには、ファイルのバイナリ コンテンツをプログラムで調べる必要があります。 しかし、このような徹底的なアプローチは、多くの場合に過剰です。ほとんどのシナリオでは、拡張子を確認するだけで十分です。

ファイルをアップロードする」 のチュートリアルでの説明にあるとおり、1 人のユーザーのアップロードで別のユーザーのファイルが上書きされないように、ファイルをファイル システムに保存する際には注意を払う必要があります。 このチュートリアルでは、試しに、アップロード済みのファイルと同じ名前を使用しています。 この場合、~/Brochures ディレクトリに同じファイル名のファイルが既に存在する場合は、一意の名前が見つかるまで末尾に番号を追加します。 たとえば、ユーザーが Meats.pdf という名前のパンフレット ファイルをアップロードしたものの、~/Brochures フォルダーには既に Meats.pdf という名前のファイルがある場合は、ファイルの保存名を Meats-1.pdf に変更します。 この名前も存在する場合は Meats-2.pdf という名前を試し、このような処理を一意のファイル名が見つかるまで繰り返します。

次のコードでは、File.Exists(path) メソッドを使用して、指定したファイル名のファイルが既に存在するかどうかを判断します。 存在する場合は、競合を起こさなくなるまで、パンフレットの新しいファイル名を試し続けます。

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

有効なファイル名が見つかったら、ファイルをファイル システムに保存し、ObjectDataSource の brochurePath``InsertParameter 値を更新して、このファイル名をデータベースに書き込む必要があります。 「ファイルをアップロードする」チュートリアルで説明したように、ファイルは、FileUpload コントロールの SaveAs(path) メソッドを使用して保存できます。 ObjectDataSource の brochurePath パラメーターを更新するには、e.Values コレクションを使用します。

// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;

手順 7: アップロードした画像をデータベースに保存する

アップロードした画像を新しい Categories レコードに格納するには、DetailsView の ItemInserting イベントの中で、アップロードしたバイナリ コンテンツを ObjectDataSource の picture パラメーターに割り当てる必要があります。 ただし、この割り当てを行う前に、アップロードした画像が JPG であり他の種類の画像ではないことを、最初に確認する必要があります。 手順 6 と同様に、アップロードした画像のファイル拡張子を使用して、その種類を確認します。

Categories テーブルでは Picture 列の NULL 値が 許可されますが、現在、すべてのカテゴリの画像が存在しています。 このページで新しいカテゴリを追加する場合は、ユーザーが画像を提供しなければならないようにしましょう。 次のコードでは、画像がアップロード済みであることと、それに適切な拡張子が使用されていることを検査します。

// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
    // 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;
        e.Cancel = true;
        return;
    }
}
else
{
    // No picture uploaded!
    UploadWarning.Text = 
        "You must provide a picture for the new category.";
    UploadWarning.Visible = true;
    e.Cancel = true;
    return;
}

このコードは、手順 6 のコードの前に配置する必要があります。それにより、画像のアップロードに問題がある場合は、パンフレット ファイルがファイル システムに保存される前にイベント ハンドラーが終了します。

適切なファイルがアップロードされたと仮定して、次のコード行を使用し、アップロードしたバイナリ コンテンツを画像パラメーターの値に割り当てます。

// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;

完全な ItemInserting イベント ハンドラー

完全を期す目的で、ItemInserting イベント ハンドラー全体を次に示します。

protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    // Reference the FileUpload controls
    FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
    if (PictureUpload.HasFile)
    {
        // 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;
            e.Cancel = true;
            return;
        }
    }
    else
    {
        // No picture uploaded!
        UploadWarning.Text = 
            "You must provide a picture for the new category.";
        UploadWarning.Visible = true;
        e.Cancel = true;
        return;
    }
    // Set the value of the picture parameter
    e.Values["picture"] = PictureUpload.FileBytes;
    
    
    // Reference the FileUpload controls
    FileUpload BrochureUpload = 
        (FileUpload)NewCategory.FindControl("BrochureUpload");
    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;
            e.Cancel = true;
            return;
        }
        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));
        e.Values["brochurePath"] = brochurePath;
    }
}

手順 8: DisplayCategoryPicture.aspx ページを修正する

最後の数ステップで作成された挿入インターフェイスと ItemInserting イベント ハンドラーを、ここで一度テストしてみましょう。 ブラウザーから UploadInDetailsView.aspx ページにアクセスし、カテゴリの追加を試みます。ただしここでは、画像を省略するか、JPG 以外の画像または PDF 以外のパンフレットを指定します。 いずれの場合も、エラー メッセージが表示され、挿入ワークフローが取り消されます。

A Warning Message is Displayed If an Invalid File Type is Uploaded

図 9: 無効なファイルの種類がアップロードされた場合は警告メッセージが表示される (クリックするとフルサイズの画像を表示します)

ページに画像をアップロードする必要があり、また PDF と JPG 以外のファイルは受け付けられないことを確かめたら、[パンフレット] フィールドは空のままにして、有効な JPG 画像を含む新しいカテゴリを追加します。 [挿入] ボタンをクリックすると、ページがポストバックし、データベースに直接格納されたアップロード済み画像のバイナリ コンテンツとともに、新しいレコードが Categories テーブルに追加されます。 GridView が更新され、新しく追加されたカテゴリの行が表示されますが、図 10 に示すように、新しいカテゴリの画像は正しくレンダリングされません。

The New Category s Picture is not Displayed

図 10: 新しいカテゴリの画像が表示されない (クリックするとフルサイズの画像を表示します)

新しい画像が表示されないのは、指定したカテゴリの画像を返す DisplayCategoryPicture.aspx ページが、OLE ヘッダーを持つビットマップを処理するように構成されているため です。 この 78 バイトのヘッダーは、クライアントに送り返される前の段階で、Picture 列のバイナリ コンテンツから削除されます。 しかし、ここで新しいカテゴリ用にアップロードした JPG ファイルには、この OLE ヘッダーがありません。そのため、イメージのバイナリ データから、有効かつ必要なバイトが削除されています。

ここでは、ビットマップと JPG の両方が Categories テーブルに用意されているため、DisplayCategoryPicture.aspx を更新し、元の 8 つのカテゴリに対して OLE ヘッダーの削除を実行し、新しいカテゴリ レコードに対してこの削除をバイパスするようにする必要があります。 次のチュートリアルでは、既存のレコードの画像を更新する方法を確認し、すべての古いカテゴリの画像を JPG になるように更新します。 ただし、現時点では、DisplayCategoryPicture.aspx にあるコードを使用して、元の 8 つのカテゴリに対してのみ OLE ヘッダーを削除します。

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];
    if (categoryID <= 8)
    {
        // For older categories, we must strip the OLE header... images are bitmaps
        // Output HTTP headers providing information about the binary data
        Response.ContentType = "image/bmp";
        // Output the binary data
        // But first we need to strip out the OLE header
        const int OleHeaderLength = 78;
        int strippedImageLength = category.Picture.Length - OleHeaderLength;
        byte[] strippedImageData = new byte[strippedImageLength];
        Array.Copy(category.Picture, OleHeaderLength, strippedImageData, 
            0, strippedImageLength);
        Response.BinaryWrite(strippedImageData);
    }
    else
    {
        // 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);
    }
}

この変更により、JPG イメージが GridView 内で正しくレンダリングされるようになりました。

The JPG Images for New Categories are Correctly Rendered

図 11: 新しいカテゴリの JPG イメージが正しくレンダリングされる (クリックするとフルサイズの画像を表示します)

手順 9: 例外の発生に応じてパンフレットを削除する

Web サーバーのファイル システムにバイナリ データを格納する場合の課題の 1 つは、データ モデルとそのバイナリ データの間を切断する必要が生じることです。 つまり、レコードが削除されるたびに、ファイル システム上で対応しているバイナリ データも削除する必要があります。 これと同じことが、挿入の場合にも発生します。 次のシナリオを考えてみましょう。ユーザーが新しいカテゴリを追加し、有効な画像とパンフレットを指定したとします。 [挿入] ボタンをクリックするとポストバックが実行され、DetailsView の ItemInserting イベントが発生し、パンフレットが Web サーバーのファイル システムに保存されます。 次に、ObjectDataSource の Insert() メソッドが呼び出され、このメソッドにより CategoriesBLL クラスの InsertWithPicture メソッドが呼び出され、これにより CategoriesTableAdapterInsertWithPicture メソッドが呼び出されます。

ここで、データベースがオフラインになったら何が発生するでしょうか? または INSERT SQL ステートメントにエラーがあるとしたらどうでしょうか? INSERT は明らかに失敗します。そして、新しいカテゴリ行はデータベースに追加されません。 しかし、アップロードされたパンフレットファイルは、Web サーバーのファイルシステムに置かれたままとなります。 このファイルは、挿入ワークフロー中の例外が発生した時点で、削除される必要があります。

前に、ASP.NET ページのチュートリアル「BLL 例外と DAL-Level 例外の処理」で説明したように、アーキテクチャの深いレベルからスローされた例外は、さまざまなレイヤーを通過して、泡のように浮き上がってきます。 プレゼンテーション レイヤーでは、例外が、DetailsView の ItemInserted イベントから発生したのかどうかを確認できます。 このイベント ハンドラーは、ObjectDataSource の InsertParameters 値も提供します。 そのため、例外が存在するかどうかを確認する ItemInserted イベントのイベント ハンドラーを作成し、例外がある場合には、ObjectDataSource の brochurePath パラメーターで指定されたファイルを削除することができます。

protected void NewCategory_ItemInserted
    (object sender, DetailsViewInsertedEventArgs e)
{
    if (e.Exception != null)
    {
        // Need to delete brochure file, if it exists
        if (e.Values["brochurePath"] != null)
            System.IO.File.Delete(Server.MapPath(
                e.Values["brochurePath"].ToString()));
    }
}

まとめ

バイナリ データを含むレコードを追加するための Web ベースのインターフェイスを作成するには、いくつかの手順を実行する必要があります。 バイナリ データがデータベースに直接格納されているのなら、アーキテクチャを更新し、バイナリ データが挿入されている場合に、それを処理する特定のメソッドを追加する必要があります。 アーキテクチャが更新されたら、次の手順は挿入インターフェイスの作成です。これは、各バイナリ データ フィールドの FileUpload コントロールを含むようにカスタマイズされた DetailsView を使用して実行できます。 アップロードされたデータは、Web サーバーのファイル システムに保存されるか、DetailsView の ItemInserting イベント ハンドラーのデータ ソース パラメーターに割り当てられます。

ファイル システムへのバイナリ データの保存には、データをデータベースに直接保存する場合より計画性が必要です。 あるユーザーのアップロードが別のユーザーのデータを上書きしないようにするには、名前付けのスキームを選択する必要があります。 また、データベースの挿入が失敗した場合は、アップロードしたファイルを削除するための追加の手順を実行する必要があります。

ここまでで、パンフレットと画像を使用して新しいカテゴリをシステムに追加できるようになりました。しかし、既存のカテゴリのバイナリ データを更新する方法や、削除されたカテゴリのバイナリ データを正しく除去する方法をまだ確認していません。 この 2 つのトピックについては、次のチュートリアルで説明します。

プログラミングに満足!

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell 氏は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。

特別な感謝

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