新しいレコードを追加するとき、ファイル アップロード オプションを含める (C#)
このチュートリアルでは、テキスト データの入力とバイナリ ファイルのアップロードの両方をユーザーが行なうための、Web インターフェイスの作成方法について説明します。 バイナリ データを格納するために使用できるオプションを示すために、1 つのファイルをデータベースに保存し、もう 1 つのファイルをファイル システムに格納します。
はじめに
前の 2 つのチュートリアルでは、アプリケーションのデータ モデルに関連付けられているバイナリ データを格納する方法、FileUpload コントロールを使用してクライアントから Web サーバーにファイルを送信する方法、およびデータ Web コントロール内にバイナリ データを表示する方法について説明しました。 ただし、アップロードされたデータをデータ モデルに関連付ける方法については、まだ説明していません。
このチュートリアルでは、新しいカテゴリを追加する Web ページを作成していきます。 カテゴリの名前と説明のための TextBox に加えて、このページには、新しいカテゴリの画像用とパンフレット用に、2 つの FileUpload コントロールを含める必要があります。 アップロードされた画像は新しいレコードの Picture
列に直接保存されますが、パンフレットは、新しいレコードの BrochurePath
列に保存されたファイルへのパスとともに ~/Brochures
フォルダーに保存されます。
この新しい Web ページを作成する前に、アーキテクチャを更新する必要があります。 CategoriesTableAdapter
のメイン クエリでは、Picture
列が取得されません。 その結果、自動生成された Insert
メソッドは、フィールド CategoryName
、Description
、 および BrochurePath
に対する 入力のみを含みます。 そのため、TableAdapter で、4 つの Categories
フィールドすべてに対して入力を要求する追加のメソッドを作成する必要があります。 また、ビジネス ロジック レイヤーの CategoriesBLL
クラスも更新する必要があります。
手順 1:CategoriesTableAdapter
に InsertWithPicture
メソッドを追加する
前出の 「データ アクセス層の作成」チュートリアルで CategoriesTableAdapter
を作成した際、メイン クエリに基づき INSERT
、UPDATE
、DELETE
ステートメントを自動的に生成するような構成を行ないました。 さらに、Insert
、Update
、Delete
メソッドを作成した DB ダイレクト アプローチを採用するように、TableAdapter に指示しました。 これらのメソッドは自動生成された INSERT
、 UPDATE
、および 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
という名前のメソッドは、CategoryName
、Description
、BrochurePath
、および Picture
列の値を受け取り、さらに INSERT
ステートメントを実行して、新しいレコードに 4 つの値をすべて格納します。
型指定されたデータセットを開き、デザイナーから CategoriesTableAdapter
ヘッダーを右クリックし、コンテキスト メニューから [クエリの追加] を選択します。 これにより、TableAdapter クエリ構成ウィザードが起動し、TableAdapter クエリがデータベースにアクセスする方法を確認してきます。 [SQL ステートメントを使用する] を選択して、[次へ] をクリックします。 次の手順では、生成するクエリの型を確認するプロンプトが表示されます。 ここでは、Categories
テーブルに新しいレコードを追加するクエリを作成しているので、[INSERT] を選択して [次へ] をクリックします。
図 1: INSERT オプションを選択する (クリックするとフルサイズの画像が表示されます)
次に、INSERT
SQL ステートメントを指定する必要があります。 ウィザードが、TableAdapter のメイン クエリに対応する INSERT
ステートメントを自動的に提案します。 この場合は、CategoryName
、Description
、および BrochurePath
値を挿入する INSERT
ステートメントが提案されます。 次のように、@Picture
パラメーターと共に Picture
列が含まれるようにステートメントを更新します。
INSERT INTO [Categories]
([CategoryName], [Description], [BrochurePath], [Picture])
VALUES
(@CategoryName, @Description, @BrochurePath, @Picture)
ウィザードの最後の画面では、新しい TableAdapter メソッドの名前を付けるように求められます。 「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
にコピーすることを忘れないでください.
図 3: DisplayOrDownload.aspx
から UploadInDetailsView.aspx
に宣言構文コピーおよび貼り付ける (クリックするとフルサイズの画像をを表示します)
宣言型の構文と GenerateBrochureLink
メソッドを UploadInDetailsView.aspx
ページに コピーした後、ブラウザーでページを表示して、すべてが正しくコピーされたことを確認します。 GridView により、パンフレットをダウンロードするためのリンクとカテゴリの画像を含む 8 つのカテゴリが、一覧表示されていることを確認してください。
図 4: 各カテゴリとそのバイナリ データが表示されることを確認する (クリックするとフルサイズの画像を表示します)
手順 4: 挿入をサポートするように CategoriesDataSource
を構成する
この時点で、Categories
GridView で使用される CategoriesDataSource
ObjectDataSource には、データを挿入する機能がありません。 このデータ ソース コントロール経由での挿入をサポートするには、その Insert
メソッドを、基盤オブジェクト CategoriesBLL
のメソッドにマップする必要があります。 具体的には、前の手順 2 InsertWithPicture
で追加した CategoriesBLL
メソッドに、これをマップします。
まず、ObjectDataSource のスマート タグから [データ ソースの構成] リンクをクリックします。 最初の画面には、データ ソースと連携するように構成されているオブジェクト CategoriesBLL
が表示されます。 この設定はそのままにし、[次へ] をクリックして [データメソッドの定義] 画面に進みます。 [INSERT] タブに移動し、ドロップダウン リストから InsertWithPicture
メソッドを選択します。 [完了] をクリックして、ウィザードを完了します。
図 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
タグにバインドし、[挿入を有効にする] チェック ボックスをオンにします。
図 6: DetailsView を CategoriesDataSource
にバインドし、挿入を有効化する (クリックするとフルサイズの画像を表示します)
挿入インターフェイスで DetailsView を恒久的にレンダリングするには、その DefaultMode
プロパティを Insert
に設定します。
DetailsView にはCategoryID
、CategoryName
、Description
、NumberOfProducts
、および 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 を移動します。
図 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>
Brochure と Picture のフィールドに FileUpload コントロールを追加する
現在、 BrochurePath
TemplateField の InsertItemTemplate
には TextBox が含まれていますが、Picture
TemplateField には、含まれているテンプレートはありません。 これら 2 つの TemplateField の InsertItemTemplate
を、FileUpload コントロールを使用するように更新する必要があります。
DetailsView のスマート タグから、[テンプレートの編集] オプションを選択し、ドロップダウン リストから BrochurePath
TemplateField の InsertItemTemplate
を選択 します。 TextBox を削除し、ツールボックスからテンプレートに FileUpload コントロールをドラッグします。 FileUpload コントロールの ID
を BrochureUpload
に設定します。. 同様に、FileUpload コントロールを Picture
TemplateField の InsertItemTemplate
に追加します。 この 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
スキーマを拡張して、ファイルの種類をキャプチャする列を含める必要があります。これにより、この種類を DisplayCategoryPicture.aspx
の Response.ContentType
経由でクライアントに送信できます。 ただ既定では、このような列はないため、特定のイメージ ファイルの種類のみを指定するように、ユーザーを制限することをお勧めします。 Categories
テーブルの既存の画像はビットマップですが、Web 経由での画像提供には、JPG がより適したファイル形式です。
ユーザーが適切ではないファイルの種類をアップロードした場合は、その挿入をキャンセルし、問題を示すメッセージを表示する必要があります。 DetailsView の下に Label Web コントロールを追加します。 その ID
プロパティを UploadWarning
に設定し、その Text
プロパティをクリアし、CssClass
プロパティを Warning に設定して、Visible
および EnableViewState
プロパティを false
に設定します。 Warning
CSS クラスは Styles.css
の中で定義されており、テキストを大きく、赤く、斜体かつ太字のフォントでレンダリングします。
Note
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 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 以外のパンフレットを指定します。 いずれの場合も、エラー メッセージが表示され、挿入ワークフローが取り消されます。
図 9: 無効なファイルの種類がアップロードされた場合は警告メッセージが表示される (クリックするとフルサイズの画像を表示します)
ページに画像をアップロードする必要があり、また PDF と JPG 以外のファイルは受け付けられないことを確かめたら、[パンフレット] フィールドは空のままにして、有効な JPG 画像を含む新しいカテゴリを追加します。 [挿入] ボタンをクリックすると、ページがポストバックし、データベースに直接格納されたアップロード済み画像のバイナリ コンテンツとともに、新しいレコードが Categories
テーブルに追加されます。 GridView が更新され、新しく追加されたカテゴリの行が表示されますが、図 10 に示すように、新しいカテゴリの画像は正しくレンダリングされません。
図 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 内で正しくレンダリングされるようになりました。
図 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 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行をドロップしてください。