次の方法で共有


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

スコット・ミッチェル著

PDF をダウンロードする

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

イントロダクション

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

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

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

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

CategoriesTableAdapterに関するチュートリアルでを作成したときに、メイン クエリに基づいてINSERTUPDATE、およびDELETEステートメントを自動的に生成するように構成しました。 さらに、TableAdapter に DB ダイレクト アプローチを採用するよう指示しました。これにより、 InsertUpdate、および Deleteメソッドが作成されました。 これらのメソッドは自動生成された INSERTUPDATE、および DELETE ステートメントを実行し、メイン クエリによって返される列に基づいて入力パラメーターを受け入れます。 Uploading Files チュートリアルでは、CategoriesTableAdapter列を使用するようにBrochurePathのメイン クエリを拡張しました。

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

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

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

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

INSERT オプションを選択する

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

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

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

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

新しい TableAdapter メソッドに InsertWithPicture という名前を付けます

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

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

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

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Insert, False)> _
Public Sub InsertWithPicture(categoryName As String, description As String, _
    brochurePath As String, picture() As Byte)
    
    Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub

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

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

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

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

宣言構文をコピーしてDisplayOrDownload.aspxからUploadInDetailsView.aspxに貼り付ける

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

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

これで、各カテゴリとそのバイナリ データが表示されます

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

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

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

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

InsertWithPicture メソッドを使用するように ObjectDataSource を構成する

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

ウィザードを完了すると、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 の上にあるデザイナーに DetailsView をドラッグし、その ID プロパティを NewCategory に設定し、 HeightWidth プロパティの値をクリアします。 DetailsView のスマート タグから既存の CategoriesDataSource にバインドし、[挿入を有効にする] チェックボックスをオンにします。

CategoryID プロパティが NewCategory に設定され、[高さ] プロパティと [幅] プロパティの値が空で、[挿入を有効にする] チェック ボックスがオンになっている DetailsView のスクリーンショット。

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

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

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

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

TemplateField、Picture、HeaderText が強調表示されているフィールド ウィンドウを示すスクリーンショット。

図 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>

パンフレットフィールドと図フィールドに FileUpload コントロールを追加する

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

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

InsertItemTemplate に FileUpload コントロールを追加する

図 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 スキーマを拡張して、ファイルの種類をキャプチャする列を含め、この型をResponse.ContentTypeDisplayCategoryPicture.aspx経由でクライアントに送信できるようにする必要があります。 このような列がないため、特定のイメージ ファイルの種類のみを提供するようにユーザーを制限することをお勧めします。 Categories テーブルの既存のイメージはビットマップですが、JPG は Web 経由で提供されるイメージに適したファイル形式です。

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

理想的には、 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 controls
Dim BrochureUpload As FileUpload = _
    CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
    ' Make sure that a PDF has been uploaded
    If String.Compare(System.IO.Path.GetExtension _
        (BrochureUpload.FileName), ".pdf", True) <> 0 Then
        UploadWarning.Text = _
            "Only PDF documents may be used for a category's brochure."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
End If

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

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

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

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

Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
    System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
    brochurePath = String.Concat(BrochureDirectory, _
        fileNameWithoutExtension, "-", iteration, ".pdf")
    iteration += 1
End While

有効なファイル名が見つかったら、ファイルをファイル システムに保存し、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 の picture イベントの ObjectDataSource の ItemInserting パラメーターに割り当てる必要があります。 ただし、この割り当てを行う前に、アップロードした画像が JPG であり、他の画像の種類ではないことを最初に確認する必要があります。 手順 6 と同様に、アップロードした画像のファイル拡張子を使用して、その種類を確認します。

Categories テーブルではNULL列のPicture値を使用できます。現在、すべてのカテゴリに画像があります。 このページで新しいカテゴリを追加するときに、ユーザーに画像を提供させましょう。 次のコードは、画像がアップロードされていることと、適切な拡張機能があることを確認します。

' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
    CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
    ' Make sure that a JPG has been uploaded
    If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpg", True) <> 0 AndAlso _
        String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
            ".jpeg", True) <> 0 Then
        
        UploadWarning.Text = _
            "Only JPG documents may be used for a category's picture."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
Else
    ' No picture uploaded!
    UploadWarning.Text = _
        "You must provide a picture for the new category."
    UploadWarning.Visible = True
    e.Cancel = True
    Exit Sub
End If

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

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

' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes

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

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

Protected Sub NewCategory_ItemInserting _
    (sender As Object, e As DetailsViewInsertEventArgs) _
    Handles NewCategory.ItemInserting
    
    ' Reference the FileUpload controls
    Dim PictureUpload As FileUpload = _
        CType(NewCategory.FindControl("PictureUpload"), FileUpload)
    If PictureUpload.HasFile Then
        ' Make sure that a JPG has been uploaded
        If  String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpg", True) <> 0 AndAlso _
            String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
                ".jpeg", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only JPG documents may be used for a category's picture."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
    Else
        ' No picture uploaded!
        UploadWarning.Text = _
            "You must provide a picture for the new category."
        UploadWarning.Visible = True
        e.Cancel = True
        Exit Sub
    End If
    ' Set the value of the picture parameter
    e.Values("picture") = PictureUpload.FileBytes
    ' Reference the FileUpload controls
    Dim BrochureUpload As FileUpload = _
        CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
    If BrochureUpload.HasFile Then
        ' Make sure that a PDF has been uploaded
        If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _
            ".pdf", True) <> 0 Then
            
            UploadWarning.Text = _
                "Only PDF documents may be used for a category's brochure."
            UploadWarning.Visible = True
            e.Cancel = True
            Exit Sub
        End If
        Const BrochureDirectory As String = "~/Brochures/"
        Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
        Dim fileNameWithoutExtension As String = _
            System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
        Dim iteration As Integer = 1
        While System.IO.File.Exists(Server.MapPath(brochurePath))
            brochurePath = String.Concat(BrochureDirectory, _
                fileNameWithoutExtension, "-", iteration, ".pdf")
            iteration += 1
        End While
        ' Save the file to disk and set the value of the brochurePath parameter
        BrochureUpload.SaveAs(Server.MapPath(brochurePath))
        e.Values("brochurePath") = brochurePath
    End If
End Sub

手順 8: DisplayCategoryPicture.aspxPage を修正する

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

無効なファイルの種類がアップロードされた場合に警告メッセージが表示される

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

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

新しいカテゴリの画像が表示されない

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

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

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

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim categoryID As Integer = Convert.ToInt32(Request.QueryString("CategoryID"))
    ' Get information about the specified category
    Dim categoryAPI As New CategoriesBLL()
    Dim categories As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = categories(0)
    If categoryID <= 8 Then
        ' 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 OleHeaderLength As Integer = 78
        Dim strippedImageLength As Integer = _
            category.Picture.Length - OleHeaderLength
        Dim strippedImageData(strippedImageLength) As Byte
        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)
    End If
End Sub

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

新しいカテゴリの JPG イメージが正しくレンダリングされる

図 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 Sub NewCategory_ItemInserted _
    (sender As Object, e As DetailsViewInsertedEventArgs) _
    Handles NewCategory.ItemInserted
    
    If e.Exception IsNot Nothing Then
        ' Need to delete brochure file, if it exists
        If e.Values("brochurePath") IsNot Nothing Then
            System.IO.File.Delete(Server.MapPath _
                (e.Values("brochurePath").ToString()))
        End If
    End If
End Sub

概要

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

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

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

プログラミングに満足!

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Dave Gardner、テレサ マーフィー、ベルナデット リーでした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、mitchell@4GuysFromRolla.comにメッセージを送ってください。