データ Web コントロールにバイナリ データを表示する (C#)
このチュートリアルでは、画像ファイルの表示や PDF ファイルの [ダウンロード] リンクのプロビジョニングなど、バイナリ データを Web ページに表示するオプションについて説明します。
はじめに
前のチュートリアルでは、バイナリ データをアプリケーションの基になるデータ モデルに関連付ける 2 つの手法を確認し、FileUpload コントロールを使用してブラウザーから Web サーバーのファイル システムにファイルをアップロードしました。 アップロードされたバイナリ データをデータ モデルに関連付ける方法については、まだ確認していません。 すなわち、ファイルをアップロードしてファイル システムに保存した後、ファイルへのパスを適切なデータベース レコードに格納する必要があります。 データがデータベースに直接保存されている場合は、アップロードされたバイナリ データをファイル システムに保存する必要はありませんが、データベースに挿入する必要があります。
ただし、データをデータ モデルに関連付けることに目を向ける前に、まずバイナリ データをエンド ユーザーに提供する方法を確認しましょう。 テキスト データを表示することは簡単ですが、バイナリ データはどのように表示すべきでしょうか? もちろん、バイナリ データの種類によって異なります。 画像の場合はその画像を表示する必要がありますが、PDF、Microsoft Word ドキュメント、ZIP ファイル、その他の種類のバイナリ データの場合は、おそらくダウンロード リンクを提供するほうが適しているでしょう。
このチュートリアルでは、GridView や DetailsView などのデータ Web コントロールを使用して、バイナリ データを関連するテキスト データと共に表示する方法について説明します。 次のチュートリアルでは、アップロードしたファイルをデータベースに関連付けることに目を向けます。
手順 1: BrochurePath
の値を指定する
Categories
テーブル内の Picture
列には、さまざまなカテゴリの画像のバイナリ データが既に入っています。 具体的には、各レコードの Picture
列には、不鮮明で画質の低い 16 色のビットマップ画像のバイナリ コンテンツが保持されます。 各カテゴリの画像は幅 172 ピクセル、高さが 120 ピクセルで、約 11 KB 消費します。 さらに、Picture
列のバイナリ コンテンツには、画像を表示する前に削除する必要がある 78 バイトの OLE ヘッダーが含まれています。 このヘッダー情報が存在するのは、Northwind データベースのルーツが Microsoft Access にあるためです。 Access では、バイナリ データが OLE Object データ型を使用して保存され、そこにこのヘッダーが付加されます。 ここでは、画像を表示するために、これらの低画質の画像からヘッダーを削除する方法について説明します。 後のチュートリアルでは、カテゴリの Picture
列を更新するためのインターフェイスを構築し、OLE ヘッダーを使用するこれらのビットマップ画像を、不要な OLE ヘッダーのない同等の JPG 画像に置き換えます。
前のチュートリアルでは、FileUpload コントロールの使用方法について確認しました。 そのため、Web サーバーのファイル システムにパンフレット ファイルを追加できます。 ただし、そのようにしても、Categories
テーブル内の BrochurePath
列は更新されません。 次のチュートリアルではこれを行う方法について確認しますが、ここではこの列の値を手動で指定する必要があります。
このチュートリアルのダウンロードでは、Seafood を除くカテゴリごとに 1 つ、合計 7 つの PDF パンフレット ファイルが ~/Brochures
フォルダーにあります。 すべてのレコードにバイナリ データが関連付けられていないシナリオに対応する方法を示すために、Seafood のパンフレットを追加することを意図的に省きました。 これらの値で Categories
テーブルを更新するには、サーバー エクスプローラーで Categories
ノードを右クリックし、[テーブル データの表示] を選択します。 次に、図 1 に示すように、1 つのパンフレットが格納された各カテゴリのパンフレット ファイルへの仮想パスを入力します。 Seafood カテゴリのパンフレットはないため、BrochurePath
列の値は NULL
のままにします。
図 1: Categories
テーブルの BrochurePath
列の値を手動で入力します (クリックするとフルサイズの画像が表示されます)
手順 2: GridView にパンフレットのダウンロード リンクを指定する
Categories
テーブルに BrochurePath
値が指定されたので、各カテゴリを一覧表示する GridView と、カテゴリのパンフレットをダウンロードするためのリンクを作成する準備ができました。 手順 4 では、この GridView を拡張して、カテゴリの画像も表示します。
まず、ツールボックスから BinaryData
フォルダー内の DisplayOrDownloadData.aspx
ページのデザイナーに GridView をドラッグします。 GridView の ID
を Categories
に設定し、GridView のスマート タグを使用して、それを新しいデータ ソースにバインドすることを選択します。 具体的には、CategoriesBLL
オブジェクトの GetCategories()
メソッドを使用してデータを取得する、CategoriesDataSource
という名前の ObjectDataSource にバインドします。
図 2: CategoriesDataSource
という名前の新しい ObjectDataSource の作成 (クリックするとフルサイズの画像が表示されます)
図 3: CategoriesBLL
クラスを使用するように ObjectDataSource を構成します (クリックするとフルサイズの画像が表示されます)
図 4: GetCategories()
メソッドを使用してカテゴリの一覧を取得します (クリックするとフルサイズの画像が表示されます)
データ ソースの構成ウィザードが完了すると、Visual Studio によって、CategoryID
、CategoryName
、Description
、NumberOfProducts
、BrochurePath
DataColumn
の Categories
GridView に BoundField が自動的に追加されます。 GetCategories()
メソッドのクエリではこの情報が取得されないため、NumberOfProducts
BoundField を削除してください。 また、CategoryID
BoundField を削除し、BoundField の HeaderText
プロパティ CategoryName
と BrochurePath
の名前をそれぞれ Category と Brochure に変更します。 これらの変更を行った後、GridView と ObjectDataSource の宣言型マークアップは次のようになります。
<asp:GridView ID="Categories" runat="server"
AutoGenerateColumns="False" DataKeyNames="CategoryID"
DataSourceID="CategoriesDataSource" EnableViewState="False">
<Columns>
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
<asp:BoundField DataField="BrochurePath" HeaderText="Brochure"
SortExpression="BrochurePath" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
ブラウザーを使用してこのページを表示します (図 5 を参照)。 8 つの各カテゴリが一覧表示されます。 BrochurePath
値を持つ 7 つのカテゴリには、それぞれの BoundField に BrochurePath
値が表示されます。 BrochurePath
の値が NULL
の Seafood には空のセルが表示されます。
図 5: 各カテゴリの名前、説明、および BrochurePath
値が一覧表示されます (クリックするとフルサイズの画像が表示されます)
BrochurePath
列のテキストを表示するのではなく、パンフレットへのリンクを作成します。 これを行うには、BrochurePath
BoundField を削除し、HyperLinkField に置き換えます。 新しい HyperLinkField の HeaderText
プロパティを Brochure に、その Text
プロパティを View Brochure に、その DataNavigateUrlFields
プロパティを BrochurePath
に設定します。
図 6: BrochurePath
の HyperLinkField を追加します
これにより、図 7 に示すように、GridView へのリンクの列が追加されます。 [View Brochure] リンクをクリックすると、PDF リーダーがインストールされているかどうかとブラウザーの設定に応じて、PDF がブラウザーに直接表示されるか、ユーザーにファイルのダウンロードが求められます。
図 7: [View Brochure] リンクをクリックすると、カテゴリのパンフレットを表示できます (クリックするとフルサイズの画像が表示されます)
図 8: そのカテゴリのパンフレット PDF が表示されます (クリックするとフルサイズの画像が表示されます)
カタログのないカテゴリの「View Brochure」のテキストを非表示にする
図 7 に示すように、BrochurePath
HyperLinkField によって、BrochurePath
に NULL
以外の値が指定されているかどうかに関係なく、すべてのレコードに Text
プロパティ値 (View Brochure) が表示されます。 もちろん、BrochurePath
が NULL
である場合、Seafood カテゴリの場合と同様に、リンクはテキストとしてのみ表示されます (図 7 を参照)。 BrochurePath
値のないカテゴリには、「View Brochure」というテキストではなく、「No Brochure Available」などの代替テキストを表示するほうがよいかもしれません。
この動作にするには、BrochurePath
値に基づいて適切な出力を発するページ メソッドへの呼び出しを介してコンテンツが生成される、TemplateField を使用する必要があります。 このフォーマット手法については、前の「GridView コントロールでの TemplateField の使用」のチュートリアルで初めて確認しました。
BrochurePath
HyperLinkField を選択し、[列の編集] ダイアログ ボックスで [このフィールドを TemplateField に変換します] リンクをクリックして、HyperLinkField を TemplateField に変換します。
図 9: HyperLinkField を TemplateField に変換します
これにより、NavigateUrl
プロパティが BrochurePath
値にバインドされている HyperLink Web コントロールを含む ItemTemplate
とともに、TemplateField が作成されます。 このマークアップをメソッド GenerateBrochureLink
への呼び出しに置き換え、BrochurePath
の値を渡します。
<asp:TemplateField HeaderText="Brochure">
<ItemTemplate>
<%# GenerateBrochureLink(Eval("BrochurePath")) %>
</ItemTemplate>
</asp:TemplateField>
次に、string
を返し、入力パラメーターとして object
を受け入れる GenerateBrochureLink
という名前の ASP.NET ページの分離コード クラスに、protected
メソッドを作成します。
protected string GenerateBrochureLink(object BrochurePath)
{
if (Convert.IsDBNull(BrochurePath))
return "No Brochure Available";
else
return string.Format(@"<a href="{0}">View Brochure</a>",
ResolveUrl(BrochurePath.ToString()));
}
このメソッドは、渡された object
値がデータベース NULL
であるかどうかを判定し、そうである場合は、カテゴリにパンフレットがないことを示すメッセージを返します。 そうではなく、BrochurePath
値がある場合は、それがハイパーリンクで表示されます。 BrochurePath
値が存在する場合は、それが ResolveUrl(url)
メソッドに渡されることに注意してください。 このメソッドにより、渡された URL が解決され、~
の文字が適切な仮想パスに置き換えられます。 たとえば、アプリケーションが /Tutorial55
でルート化されている場合、ResolveUrl("~/Brochures/Meats.pdf")
によって /Tutorial55/Brochures/Meat.pdf
が返されます。
図 10 は、これらの変更が適用された後のページを示しています。 Seafood カテゴリの BrochurePath
フィールドに「No Brochure Available」というテキストが表示されるようになったことに注目してください。
図 10: パンフレットのないカテゴリに「No Brochure Available」というテキストが表示されます (クリックするとフルサイズの画像が表示されます)
手順 3: カテゴリの画像を表示する Web ページを追加する
ユーザーが ASP.NET ページにアクセスすると、ASP.NET ページの HTML を受け取ります。 受け取った HTML はテキストに過ぎず、バイナリ データは含まれません。 画像、サウンド ファイル、Macromedia Flash アプリケーション、埋め込みの Windows メディア プレーヤーのビデオなどの追加のバイナリ データは、Web サーバー上に別個のリソースとして存在します。 HTML にはこれらのファイルへの参照は含まれていますが、ファイルの実際の内容は含まれません。
たとえば、HTML では、画像を参照するのに <img>
要素を使用し、src
属性が次のように画像ファイルをポイントします。
<img src="MyPicture.jpg" ... />
ブラウザーがこの HTML を受け取ると、Web サーバーに対して画像ファイルのバイナリ コンテンツを取得するように別の要求を行うことで、それがブラウザーに表示されます。 バイナリ データにも同じ概念が適用されます。 手順 2 では、パンフレットがページの HTML マークアップの一部としてブラウザーに送信されませんでした。 代わりに、クリックによってブラウザーが PDF ドキュメントを直接要求するハイパーリンクが、レンダリングされた HTML から提供されていました。
ユーザーがデータベース内に存在するバイナリ データを表示またはダウンロードできるようにするには、そのデータを返す別の Web ページを作成する必要があります。 このアプリケーションに関しては、データベースに直接保存されるバイナリ データ フィールドは、そのカテゴリの画像 1 つのみです。 したがって、呼び出されるとその特定のカテゴリの画像データを返すページが必要です。
DisplayCategoryPicture.aspx
という名前の BinaryData
フォルダーに新しい ASP.NET ページを追加します。 その際、[マスター ページを選択する] チェックボックスはオフのままにしておきます。 このページでは、querystring に CategoryID
値を要求し、そのカテゴリの Picture
列のバイナリ データを返します。 このページはバイナリ データを返し、それ以外は何も返さないため、HTML セクションにマークアップは必要ありません。 そのため、左下隅にある [ソース] タブをクリックし、<%@ Page %>
ディレクティブを除くページのマークアップをすべて削除します。 つまり、DisplayCategoryPicture.aspx
の宣言型マークアップは、次のように 1 行で構成する必要があります。
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="DisplayCategoryPicture.aspx.cs"
Inherits="BinaryData_DisplayCategoryPicture" %>
<%@ Page %>
ディレクティブに MasterPageFile
属性がある場合は、それを削除します。
ページの分離コード クラスで、次のコードを Page_Load
イベント ハンドラーに追加します。
protected void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
// 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);
}
このコードは、最初に CategoryID
querystring 値を categoryID
という名前の変数に読み込みます。 次に、画像データは CategoriesBLL
クラスの GetCategoryWithBinaryDataByCategoryID(categoryID)
メソッドへの呼び出しを介して取得されます。 このデータは Response.BinaryWrite(data)
メソッドを使用してクライアントに返されますが、このメソッドが呼び出される前に、Picture
列値の OLE ヘッダーを削除する必要があります。 これは、Picture
列にある文字数よりもちょうど 78 文字少ない文字数を保持する、strippedImageData
という名前の byte
配列を作成することで達成されます。 Array.Copy
メソッドは、category.Picture
の 78 の位置から strippedImageData
にデータをコピーするのに使用されます。
Response.ContentType
プロパティは、コンテンツのレンダリング方法をブラウザーで認識できるように、返されるコンテンツの MIME の種類を指定しします。 Categories
テーブルの Picture
列はビットマップ画像であるため、ここで使用される MIME の種類はビットマップ (image/bmp) になります。 MIME の種類を省略しても、ほとんどのブラウザーでは、画像ファイルのバイナリ データの内容に基づいて種類を推測できるため、画像を正しく表示できます。 ただし、可能であれば MIME の種類を指定するのが賢明です。 MIME メディアの種類の全一覧については、Internet Assigned Numbers Authority の Web サイトを参照してください。
このページを作成して DisplayCategoryPicture.aspx?CategoryID=categoryID
にアクセスすると、特定のカテゴリの画像を表示できます。 図 11 は、Beverages カテゴリの画像を示しています。DisplayCategoryPicture.aspx?CategoryID=1
から表示できます。
図 11: Beverages カテゴリの画像が表示されます (クリックするとフルサイズの画像が表示されます)
DisplayCategoryPicture.aspx?CategoryID=categoryID
にアクセスすると「'System.DBNull' 型のオブジェクトを 'System.Byte[]' 型にキャストすることはできません」という例外が発生する場合、その原因としては 2 つのこと考えられます。 第一に、Categories
テーブルの Picture
列では NULL
値が許可されます。 ただし、DisplayCategoryPicture.aspx
ページでは NULL
値以外の値が存在すると想定しています。 NULL
値がある場合、CategoriesDataTable
の Picture
プロパティに直接アクセスすることはできません。 Picture
列に NULL
値を許可する必要がある場合は、次の条件を含めます。
if (category.IsPictureNull())
{
// Display some "No Image Available" picture
Response.Redirect("~/Images/NoPictureAvailable.gif");
}
else
{
// Send back the binary contents of the Picture column
// ... Set ContentType property and write out ...
// ... data via Response.BinaryWrite ...
}
上記のコードでは、画像のないカテゴリに対して表示する、NoPictureAvailable.gif
という名前の画像ファイルが Images
フォルダーにあることを前提としています。
この例外は、CategoriesTableAdapter
の GetCategoryWithBinaryDataByCategoryID
メソッドの SELECT
ステートメントがメイン クエリの列リストに戻った場合にも発生する可能性があります。これは、アドホック SQL ステートメントを使用しており、TableAdapter のメイン クエリに対してそのウィザードを再実行したことがある場合に発生することがあります。 GetCategoryWithBinaryDataByCategoryID
メソッドの SELECT
ステートメントに Picture
列がまだ含まれていることを確認します。
Note
DisplayCategoryPicture.aspx
にアクセスするたびに、データベースへのアクセスが発生し、指定されたカテゴリの画像データが返されます。 ただし、ユーザーが最後に表示してからカテゴリの画像が変更されていない場合、これは無駄な処理になります。 幸いなことに、HTTP では "条件付き GET" が許可されます。 条件付き GET を使用すると、HTTP 要求を行うクライアントは、クライアントが Web サーバーからこのリソースを最後に取得した日時を指定する If-Modified-Since
HTTP ヘッダーに沿って送信するようになります。 この指定された日付以降にコンテンツが変更されていない場合、Web サーバーは状態コード Not Modified (304) で応答し、要求されたリソースのコンテンツの送り返すのを見送ることがあります。 つまり、この手法により、クライアントが最後にアクセスしてからリソースが変更されていない場合は、Web サーバーがリソースのコンテンツを送り返さなくて済むようになります。
ただし、この動作を実装するには、Picture
列が最後に更新されたタイミングをキャプチャする PictureLastModified
列を Categories
テーブルに追加するほか、If-Modified-Since
ヘッダーをチェックするコードが必要です。 If-Modified-Since
ヘッダーと条件付き GET のワークフローの詳細については、「RSS ハッカー向けの HTTP 条件付き GET」と、ASP.NET ページでの HTTP 要求の実行に関する詳細を参照してください。
手順 4: GridView にカテゴリの画像を表示する
これで特定のカテゴリの画像を表示する Web ページが作成されたので、Image Web コントロールまたは DisplayCategoryPicture.aspx?CategoryID=categoryID
を指している HTML <img>
要素を使用してそれを表示できます。 データベースのデータによって URL が決まる画像は、ImageField を使用して GridView または DetailsView に表示できます。 ImageField には、HyperLinkField の DataNavigateUrlFields
プロパティや DataNavigateUrlFormatString
プロパティと同じように機能する DataImageUrlField
プロパティや DataImageUrlFormatString
プロパティがあります。
各カテゴリの画像を表示するために、ImageField を追加して DisplayOrDownloadData.aspx
の Categories
GridView を拡張してみましょう。 ImageField を追加し、その DataImageUrlField
プロパティと DataImageUrlFormatString
プロパティをそれぞれ CategoryID
と DisplayCategoryPicture.aspx?CategoryID={0}
に設定するだけです。 これにより、src
属性が DisplayCategoryPicture.aspx?CategoryID={0}
を参照する <img>
要素をレンダリングする GridView 列が作成されます。ここで、{0} は GridView 行の CategoryID
値に置き換えられます。
図 12: GridView に ImageField を追加します
ImageField を追加した後の GridView の宣言型構文は次のようになります。
<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
EnableViewState="False">
<Columns>
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
<asp:TemplateField HeaderText="Brochure">
<ItemTemplate>
<%# GenerateBrochureLink(Eval("BrochurePath")) %>
</ItemTemplate>
</asp:TemplateField>
<asp:ImageField DataImageUrlField="CategoryID"
DataImageUrlFormatString="DisplayCategoryPicture.aspx?CategoryID={0}">
</asp:ImageField>
</Columns>
</asp:GridView>
少し時間を取り、ブラウザーでこのページを表示してみてください。 各レコードにカテゴリの画像が含まれるようになったことに注目してください。
図 13: 各行にカテゴリの画像が表示されます (クリックするとフルサイズの画像が表示されます)
まとめ
このチュートリアルでは、バイナリ データを表示する方法を確認しました。 データの表示方法は、データの種類によって異なります。 パンフレットの PDF ファイルについては、クリックするとユーザーを PDF ファイルに直接移動する [View Brochure] リンクを設定しました。 カテゴリの画像については、まずデータベースからバイナリ データを取得して返すページを作成し、そのページを使用して各カテゴリの画像を GridView に表示しました。
ここまででバイナリ データを表示する方法を確認したので、バイナリ データの入ったデータベースに対して挿入、更新、削除を実行する方法を確認する準備ができました。 次のチュートリアルでは、アップロードされたファイルを対応するデータベース レコードに関連付ける方法について説明します。 その後のチュートリアルでは、既存のバイナリ データを更新する方法と、関連するレコードが削除されたときにそのバイナリ データを削除する方法について説明します。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジに取り組んでいます。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Teresa Murphy と Dave Gardner でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。