このチュートリアルでは、テキスト データの入力とバイナリ ファイルのアップロードの両方をユーザーに許可する Web インターフェイスを作成する方法について説明します。 バイナリ データを格納するために使用できるオプションを示すために、1 つのファイルがデータベースに保存され、もう 1 つのファイルがファイル システムに格納されます。
イントロダクション
前の 2 つのチュートリアルでは、アプリケーションのデータ モデルに関連付けられているバイナリ データを格納する方法、FileUpload コントロールを使用してクライアントから Web サーバーにファイルを送信する方法、およびデータ Web コントロールでこのバイナリ データを表示する方法について説明しました。 ただし、アップロードされたデータをデータ モデルに関連付ける方法については、まだ説明していません。
このチュートリアルでは、新しいカテゴリを追加する Web ページを作成します。 カテゴリの名前と説明の TextBox に加えて、このページには、新しいカテゴリの画像用とパンフレット用の 2 つの FileUpload コントロールを含める必要があります。 アップロードされた画像は新しいレコードのPicture
列に直接保存されますが、パンフレットは新しいレコードの~/Brochures
列に保存されたファイルへのパスを持つBrochurePath
フォルダーに保存されます。
この新しい Web ページを作成する前に、アーキテクチャを更新する必要があります。
CategoriesTableAdapter
のメイン クエリでは、Picture
列は取得されません。 その結果、自動生成された Insert
メソッドには、 CategoryName
、 Description
、および BrochurePath
フィールドの入力のみが含まれます。 そのため、TableAdapter で、4 つの Categories
フィールドすべてに対してプロンプトを表示する追加のメソッドを作成する必要があります。 ビジネス ロジック レイヤーの CategoriesBLL
クラスも更新する必要があります。
手順 1: InsertWithPicture
メソッドを追加するCategoriesTableAdapter
CategoriesTableAdapter
に関するチュートリアルでを作成したときに、メイン クエリに基づいてINSERT
、UPDATE
、およびDELETE
ステートメントを自動的に生成するように構成しました。 さらに、TableAdapter に DB ダイレクト アプローチを採用するよう指示しました。これにより、 Insert
、 Update
、および Delete
メソッドが作成されました。 これらのメソッドは自動生成された INSERT
、 UPDATE
、および 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
という名前のこのメソッドは、CategoryName
、Description
、BrochurePath
、およびPicture
列の値を受け取り、4 つの値をすべて新しいレコードに格納するINSERT
ステートメントを実行します。
型指定されたデータセットを開き、デザイナーから CategoriesTableAdapter
のヘッダーを右クリックし、コンテキスト メニューから [クエリの追加] を選択します。 これにより、TableAdapter クエリ構成ウィザードが起動します。まず、TableAdapter クエリがデータベースにアクセスする方法を確認します。 [SQL ステートメントの使用] を選択し、[次へ] をクリックします。 次の手順では、生成するクエリの種類を確認するプロンプトが表示されます。
Categories
テーブルに新しいレコードを追加するクエリを作成するため、[挿入] を選択して [次へ] をクリックします。
図 1: INSERT オプションを選択します (クリックするとフルサイズの画像が表示されます)
次に、 INSERT
SQL ステートメントを指定する必要があります。 ウィザードは、TableAdapter のメイン クエリに対応する INSERT
ステートメントを自動的に提案します。 この場合は、INSERT
、CategoryName
、およびDescription
の値を挿入するBrochurePath
ステートメントです。 次のように、 Picture
列が @Picture
パラメーターと共に含まれるようにステートメントを更新します。
INSERT INTO [Categories]
([CategoryName], [Description], [BrochurePath], [Picture])
VALUES
(@CategoryName, @Description, @BrochurePath, @Picture)
ウィザードの最後の画面で、新しい TableAdapter メソッドの名前を指定するように求められます。 「 InsertWithPicture
」と入力し、「完了」をクリックします。
図 2: 新しい TableAdapter メソッドに InsertWithPicture
名前を付ける (フルサイズの画像を表示する をクリックします)
手順 2: ビジネス ロジック レイヤーの更新
プレゼンテーション層は、データ アクセス層に直接移動するためにバイパスするのではなく、ビジネス ロジックレイヤーとのみインターフェイスする必要があるため、先ほど作成した DAL メソッドを呼び出す BLL メソッドを作成する必要があります (InsertWithPicture
)。 このチュートリアルでは、CategoriesBLL
という名前のInsertWithPicture
クラスに、入力 3 String
とByte
配列として受け入れるメソッドを作成します。
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.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
メソッドを選択します。 [完了] をクリックして、ウィザードを完了します。
図 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
に設定し、 Height
と Width
プロパティの値をクリアします。 DetailsView のスマート タグから既存の CategoriesDataSource
にバインドし、[挿入を有効にする] チェックボックスをオンにします。
図 6: DetailsView を CategoriesDataSource
にバインドし、挿入を有効にする (フルサイズの画像を表示する をクリックします)
挿入インターフェイスで DetailsView を完全にレンダリングするには、その DefaultMode
プロパティを Insert
に設定します。
DetailsView には 5 つの BoundFields CategoryID
、CategoryName
、Description
、NumberOfProducts
、BrochurePath
がありますが、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 を移動します。
図 7: DetailsView を CategoriesDataSource
にバインドし、挿入を有効にする
[フィールドの編集] ダイアログ ボックスを使用して BrochurePath
BoundField を TemplateField に変換した場合、TemplateField には ItemTemplate
、 EditItemTemplate
、および 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 コントロールの ID
を BrochureUpload
に設定します。 同様に、 Picture
TemplateField の InsertItemTemplate
に FileUpload コントロールを追加します。 この FileUpload コントロールの ID
を PictureUpload
に設定します。
図 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.ContentType
のDisplayCategoryPicture.aspx
経由でクライアントに送信できるようにする必要があります。 このような列がないため、特定のイメージ ファイルの種類のみを提供するようにユーザーを制限することをお勧めします。
Categories
テーブルの既存のイメージはビットマップですが、JPG は Web 経由で提供されるイメージに適したファイル形式です。
ユーザーが正しくないファイルの種類をアップロードした場合は、挿入をキャンセルし、問題を示すメッセージを表示する必要があります。 DetailsView の下にラベル Web コントロールを追加します。
ID
プロパティをUploadWarning
に設定し、Text
プロパティをクリアし、CssClass
プロパティを Warning に設定し、Visible
プロパティとEnableViewState
プロパティをFalse
に設定します。
Warning
CSS クラスはStyles.css
で定義され、大きな赤、斜体、太字のフォントでテキストをレンダリングします。
注
理想的には、 CategoryName
と Description
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.aspx
Page を修正する
最後の数ステップで作成された挿入インターフェイスと 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 に正しくレンダリングされるようになりました。
図 11: 新しいカテゴリの JPG イメージが正しくレンダリングされます (フルサイズの画像を表示する をクリックします)。
手順 9: 例外が発生した場合のパンフレットの削除
Web サーバーのファイル システムにバイナリ データを格納する場合の課題の 1 つは、データ モデルとそのバイナリ データの間に切断を導入することです。 そのため、レコードが削除されるたびに、ファイル システム上の対応するバイナリ データも削除する必要があります。 これは挿入時にも発生する可能性があります。 次のシナリオを考えてみましょう。ユーザーが新しいカテゴリを追加し、有効な画像とパンフレットを指定します。 [挿入] ボタンをクリックするとポストバックが発生し、DetailsView ItemInserting
イベントが発生し、パンフレットが Web サーバーのファイル システムに保存されます。 次に、ObjectDataSource の Insert()
メソッドが呼び出され、 CategoriesBLL
クラスの InsertWithPicture
メソッドが呼び出され、 CategoriesTableAdapter
の InsertWithPicture
メソッドが呼び出されます。
データベースがオフラインの場合や、 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にメッセージを送ってください。