Share via


Repeater コントロールと DataList を使って 2 つのページでマスター/詳細をフィルター処理する (C#)

作成者: Scott Mitchell

PDF のダウンロード

このチュートリアルでは、マスター/詳細レポートを 2 つのページに分ける方法について説明します。 "マスター" ページでは、Repeater コントロールを使ってカテゴリの一覧をレンダリングします。これをクリックすると、ユーザーは "詳細" ページに移動し、選択したカテゴリに属する製品が 2 列の DataList に表示されます。

はじめに

前のチュートリアルでは、1 つの Web ページでマスター/詳細レポートを表示する方法について説明しました。その際、DropDownList を使って "マスター" レコードを表示し、DataList を使って "詳細" を表示しました。マスター/詳細レポートによく使われるもう 1 つのパターンは、マスター レコードを 1 つの Web ページに配置し、詳細を別のページに配置する方法です。 以前の 2 つのページでマスター/詳細をフィルター処理するチュートリアルでは、GridView を使ってシステム内のすべてのサプライヤーを表示することで、このパターンを確認しました。 この GridView には、HyperLinkField が含まれていました。これは 2 番目のページへのリンクとしてレンダリングされ、クエリ文字列で SupplierID を渡します。 2 番目のページでは、GridView を使って、選択したサプライヤーが提供する製品を一覧表示しました。

このような 2 ページのマスター/詳細レポートは、DataList コントロールと Repeater コントロールを使って実現することもできます。 唯一の違いは、DataList も Repeater も HyperLinkField コントロールをサポートしていないということです。 代わりに、HyperLink Web コントロールか HTML のアンカー要素 (<a>) をコントロールの ItemTemplate 内に追加する必要があります。 そして、HyperLink の NavigateUrl プロパティまたはアンカーの href 属性を、宣言による方法かプログラムによる方法でカスタマイズできます。

このチュートリアルでは、各カテゴリを Repeater コントロールを使って 1 つのページに箇条書きで一覧表示する例について説明します。 一覧の各項目はそのカテゴリの名前と説明を含み、カテゴリ名は 2 番目のページへのリンクとして表示されます。 このリンクをクリックすると、ユーザーは 2 番目のページに移動し、選択したカテゴリに属する製品が DataList に表示されます。

手順 1: カテゴリを箇条書きで表示する

マスター/詳細レポートを作成する最初の手順は、まず "マスター" レコードを表示することです。 そのため、最初のタスクは "マスター" ページにカテゴリを表示することです。 DataListRepeaterFiltering フォルダー内の CategoryListMaster.aspx ページを開き、Repeater コントロールを追加して、スマート タグから新しい ObjectDataSource の追加を選択します。 CategoriesBLL クラスの GetCategories メソッドからデータにアクセスするように新しい ObjectDataSource を構成します (図 1 を参照)。

Configure the ObjectDataSource to Use the CategoriesBLL Class's GetCategories Method

図 1: CategoriesBLL クラスの GetCategories メソッドを使用するように ObjectDataSource を構成する (フルサイズの画像を表示するにはクリック)

次に、各カテゴリ名と説明を箇条書きの項目として表示するように Repeater のテンプレートを定義します。 各カテゴリを詳細ページにリンクする方法については、まだ心配しなくて構いません。 Repeater と ObjectDataSource の宣言型マークアップを次に示します。

<asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource1"
    EnableViewState="False">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
 
    <ItemTemplate>
        <li><%# Eval("CategoryName") %> - <%# Eval("Description") %></li>
    </ItemTemplate>
 
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>
 
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

このマークアップが完成したら、ブラウザーで進行状況を確認しましょう。 図 2 に示すように、Repeater は箇条書きでレンダリングされ、各カテゴリの名前と説明が表示されます。

Each Category is Displayed as a Bulleted List Item

図 2: 各カテゴリが箇条書きの項目として表示されている (フルサイズの画像を表示するにはクリック)

ユーザーが特定のカテゴリの "詳細" 情報を表示できるようにするために、各箇条書き項目にリンクを追加する必要があります。このリンクをクリックすると、ユーザーは 2 番目のページ (ProductsForCategoryDetails.aspx) に移動します。 そして、この 2 番目のページで、DataList を使って選択したカテゴリの製品を表示します。 クリックされたリンクのカテゴリを特定するには、何らかのメカニズムを使ってクリックされたカテゴリの CategoryID を 2 番目のページに渡す必要があります。 あるページから別のページにスカラー データを転送する最もシンプルでわかりやすい方法は、クエリ文字列を使うことです。このチュートリアルではこのオプションを使います。 具体的には、ProductsForCategoryDetails.aspx ページで、選択した categoryID 値が CategoryID という名前のクエリ文字列フィールドを介して渡されることを想定します。 たとえば、CategoryID が 1 の Beverages カテゴリの製品を表示する場合、ユーザーは ProductsForCategoryDetails.aspx?CategoryID=1 にアクセスします。

Repeater の各箇条書き項目にハイパーリンクを作成するには、HyperLink Web コントロールか HTML のアンカー要素 (<a>) を ItemTemplate に追加する必要があります。 各行で同じようにハイパーリンクを表示するシナリオでは、どちらの方法でも構いません。 Repeater の場合、筆者はアンカー要素を使う方が好きです。 アンカー要素を使う場合、Repeater の ItemTemplate を次のように更新します。

<li>
    <a href='ProductsForCategoryDetails.aspx?CategoryID=<%# Eval("CategoryID") %>'>
        <%# Eval("CategoryName") %>
    </a> - <%# Eval("Description") %>
</li>

アンカー要素の href 属性内に CategoryID を直接挿入できることに注意してください。ただしそのためには、href 属性の値を必ずアポストロフィで区切る必要があります (引用符ではありません)。href 属性内の Eval メソッドで、その文字列 ("CategoryID") を引用符で区切っているためです。 代わりに、HyperLink Web コントロールを使うこともできます。

<li>
    <asp:HyperLink runat="server" Text='<%# Eval("CategoryName") %>'
        NavigateUrl='<%# "ProductsForCategoryDetails.aspx?CategoryID=" &
            Eval("CategoryID") %>'>
    </asp:HyperLink>
    - <%# Eval("Description") %>
</li>

データ バインド構文内で文字列連結を使って、URL の静的部分 (ProductsForCategoryDetails.aspx?CategoryID) を Eval("CategoryID") の結果に直接追加していることに注意してください。

HyperLink コントロールを使う利点の 1 つは、必要に応じて、Repeater の ItemDataBound イベント ハンドラーからプログラムでアクセスできる点です。 たとえば、関連付けられている製品がないカテゴリについては、カテゴリ名をリンクではなくテキストとして表示したい場合があります。 このようなチェックは、ItemDataBound イベント ハンドラー内でプログラムによって実行できます。関連付けられている製品がないカテゴリは、HyperLink の NavigateUrl プロパティを空の文字列に設定することで、その特定のカテゴリ名を (リンクではなく) プレーンテキストとしてレンダリングすることができます。 ItemDataBound イベント ハンドラーを使ってプログラムのロジックに基づいて DataList と Repeater の内容を書式設定する方法について詳しくは、「データに基づいて DataList と Repeater を書式設定する」チュートリアルをもう一度参照してください。

先に進むにあたっては、ページでアンカー要素と HyperLink コントロールのどちらの方法を使っても構いません。 どちらの方法でも、ブラウザーを介してページを表示すると、各カテゴリ名が ProductsForCategoryDetails.aspx へのリンクとしてレンダリングされ、適切な CategoryID 値が渡されるはずです (図 3 を参照)。

The Category Names Now Link to ProductsForCategoryDetails.aspx

図 3: ProductsForCategoryDetails.aspx にリンクされるようになったカテゴリ名 (フルサイズの画像を表示するにはクリック)

手順 3: 選択したカテゴリに属する製品を一覧表示する

CategoryListMaster.aspx ページが完成したら、次は "詳細" ページ (ProductsForCategoryDetails.aspx) の実装に注意を向けましょう。 このページを開き、ツールボックスからデザイナーに DataList をドラッグして、その ID プロパティを ProductsInCategory に設定します。 次に、DataList のスマート タグから、新しい ObjectDataSource をページに追加することを選択し、ProductsInCategoryDataSource という名前を付けます。 ProductsBLL クラスの GetProductsByCategoryID(categoryID) メソッドを呼び出すようにそれを構成します。[INSERT]、[UPDATE]、[DELETE] タブのドロップダウン リストは [(なし)] に設定します。

Configure the ObjectDataSource to Use the ProductsBLL Class's GetProductsByCategoryID(categoryID) Method

図 4: ProductsBLL クラスの GetProductsByCategoryID(categoryID) メソッドを使用するように ObjectDataSource を構成する (フルサイズの画像を表示するにはクリック)

GetProductsByCategoryID(categoryID) メソッドは入力パラメーター (categoryID) を受け取るので、[データ ソースの選択] ウィザードではパラメーターのソースを指定できます。 パラメーターのソースを QueryString に設定し、QueryStringField CategoryID を使います。

Use the Querystring Field CategoryID as the Parameter's Source

図 5: パラメーターのソースとして Querystring フィールド CategoryID を使用する (フルサイズの画像を表示するにはクリック)

前のチュートリアルで説明したように、[データ ソースの選択] ウィザードを完了すると、Visual Studio によって自動的に、各データ フィールドの名前と値を一覧表示する DataList の ItemTemplate が作成されます。 このテンプレートを、製品の名前、サプライヤー、価格のみを一覧表示するテンプレートに置き換えます。 また、DataList の RepeatColumns プロパティを 2 に設定します。 これらの変更後、DataList と ObjectDataSource の宣言型マークアップは次のようになります。

<asp:DataList ID="ProductsInCategory" runat="server" DataKeyField="ProductID"
    RepeatColumns="2" DataSourceID="ProductsInCategoryDataSource"
    EnableViewState="False">
    <ItemTemplate>
        <h5><%# Eval("ProductName") %></h5>
        <p>
            Supplied by <%# Eval("SupplierName") %><br />
            <%# Eval("UnitPrice", "{0:C}") %>
        </p>
    </ItemTemplate>
</asp:DataList>
 
<asp:ObjectDataSource ID="ProductsInCategoryDataSource"
    OldValuesParameterFormatString="original_{0}" runat="server"
    SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:QueryStringParameter Name="categoryID" QueryStringField="CategoryID"
            Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

このページの動作を確認するには、CategoryListMaster.aspx ページから開始して、カテゴリの箇条書きのリンクをクリックします。 そうすると、ProductsForCategoryDetails.aspx に移動し、クエリ文字列によって CategoryID が渡されます。 次に、ProductsForCategoryDetails.aspxProductsInCategoryDataSource ObjectDataSource が指定したカテゴリの製品のみを取得し、DataList で表示します。それは行ごとに 2 つの製品をレンダリングします。 図 6 は、Beverages を表示したときの ProductsForCategoryDetails.aspx のスクリーンショットを示しています。

The Beverages are Displayed, Two per Row

図 6: Beverages の表示 (行ごとに 2 つ) (フルサイズの画像を表示するにはクリック)

手順 4: ProductsForCategoryDetails.aspx でカテゴリ情報を表示する

ユーザーが CategoryListMaster.aspx でカテゴリをクリックすると、ProductsForCategoryDetails.aspx に移動して、選択したカテゴリに属する製品が表示されます。 しかし ProductsForCategoryDetails.aspx には、どのカテゴリが選択されたかについての視覚的な手掛かりがありません。 ユーザーが Beverages をクリックするつもりで間違って Condiments をクリックしてしまった場合、ProductsForCategoryDetails.aspx に移動した後にその間違いに気付く方法はありません。 この潜在的な問題を軽減するために、選択したカテゴリに関する情報 (その名前と説明) を ProductsForCategoryDetails.aspx ページの上部に表示できます。

これを実現するには、ProductsForCategoryDetails.aspx の Repeater コントロールの上に FormView を追加します。 次に、FormView のスマート タグから CategoryDataSource という名前の新しい ObjectDataSource をページに追加し、CategoriesBLL クラスの GetCategoryByCategoryID(categoryID) メソッドを使用するように構成します。

Access Information about the Category through the CategoriesBLL Class's GetCategoryByCategoryID(categoryID) Method

図 7: CategoriesBLL クラスの GetCategoryByCategoryID(categoryID) メソッドを使ってカテゴリに関する情報にアクセスする (フルサイズの画像を表示するにはクリック)

手順 3 で追加した ProductsInCategoryDataSource ObjectDataSource と同様に、CategoryDataSource の [データ ソースの構成] ウィザードでは GetCategoryByCategoryID(categoryID) メソッドの入力パラメーターのソースを求められます。 以前とまったく同じ設定を使って、パラメーターのソースを QueryString に設定し、QueryStringField の値を CategoryID に設定します (前の図 5 を参照)。

ウィザードが完了すると、Visual Studio によって自動的に FormView の ItemTemplateEditItemTemplateInsertItemTemplate が作成されます。 読み取り専用のインターフェイスを表示するので、EditItemTemplateInsertItemTemplate は削除して構いません。 また、FormView の ItemTemplate も自由にカスタマイズしてください。 余分なテンプレートを削除して ItemTemplate をカスタマイズした後、FormView と ObjectDataSource の宣言型マークアップは次のようになります。

<asp:FormView ID="FormView1" runat="server" DataKeyNames="CategoryID"
    DataSourceID="CategoryDataSource" EnableViewState="False" Width="100%">
    <ItemTemplate>
        <h3>
            <asp:Label ID="CategoryNameLabel" runat="server"
                Text='<%# Bind("CategoryName") %>' />
        </h3>
        <p>
            <asp:Label ID="DescriptionLabel" runat="server"
                Text='<%# Bind("Description") %>' />
        </p>
    </ItemTemplate>
</asp:FormView>
 
<asp:ObjectDataSource ID="CategoryDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategoryByCategoryID" TypeName="CategoriesBLL">
    <SelectParameters>
        <asp:QueryStringParameter Name="categoryID" Type="Int32"
            QueryStringField="CategoryID" />
    </SelectParameters>
</asp:ObjectDataSource>

図 8 は、ブラウザーでこのページを表示したときのスクリーンショットを示しています。

Note

筆者は FormView に加えて、ユーザーをカテゴリの一覧 (CategoryListMaster.aspx) に戻す HyperLink コントロールを FormView の上に追加しました。 このリンクは他の場所に配置しても、または完全に省略しても構いません。

Category Information is Now Displayed at the Top of the Page

図 8: ページ上部にカテゴリ情報が表示されるようになった (フルサイズの画像を表示するにはクリック)

手順 5: 選択したカテゴリに属する製品がない場合にメッセージを表示する

CategoryListMaster.aspx ページでは、関連付けられている製品があるかどうかに関係なく、システム内のすべてのカテゴリが一覧表示されます。 ユーザーが関連付けられた製品のないカテゴリをクリックした場合、ProductsForCategoryDetails.aspx の DataList はレンダリングされません。そのデータ ソースには項目がないためです。 前のチュートリアルで説明したように、GridView には EmptyDataText プロパティが用意されており、これを使って、データ ソースにレコードがない場合に表示するテキスト メッセージを指定できます。 残念ながら、DataList と Repeater のどちらにもこのようなプロパティはありません。

選択したカテゴリに一致する製品がないことをユーザーに伝えるメッセージを表示するには、ページに Label コントロールを追加し、一致する製品がないときに表示するメッセージをその Text プロパティに割り当てる必要があります。 その後、DataList に項目が含まれているかどうかに基づいて、その Visible プロパティをプログラムで設定する必要があります。

これを実現するには、まず DataList の下に Label を追加します。 その ID プロパティを NoProductsMessage に、Text プロパティを "There are no products for the selected category…" (選択したカテゴリの製品がありません…) に設定します。次に、ProductsInCategory DataList にデータがバインドされたかどうかに基づいて、この Label の Visible プロパティをプログラムで設定する必要があります。 この割り当ては、データが DataList にバインドされた後に行う必要があります。 GridView、DetailsView、FormView の場合は、データ バインドが完了した後に発生するコントロールの DataBound イベントに対して、イベント ハンドラーを作成することができました。 しかし、DataList と Repeater では、どちらも DataBound イベントを使用できません。

この特定の例では、Page_Load イベント ハンドラーで Label の Visible プロパティを割り当てることができます。ページの Load イベントより前にデータが DataList に割り当てられるためです。 ただし、ObjectDataSource のデータはページのライフサイクルにおいて後ほど DataList にバインドされる可能性があるため、この方法は一般的なケースでは機能しません。 たとえば、表示されるデータが別のコントロールの値に基づいている場合 ("マスター" レコードを保持するために DropDownList を使ってマスター/詳細レポートを表示する場合など)、データはページのライフ サイクルの PreRender ステージまで、データの Web コントロールに再バインドされない可能性があります。

すべてのケースで機能する 1 つの解決策は、Item または AlternatingItem のアイテムの種類をバインドするときに、DataList の ItemDataBound (または ItemCreated) イベント ハンドラーで Visible プロパティを False に割り当てることです。 このような場合は、データ ソースに少なくとも 1 つのデータ項目があることがわかるため、NoProductsMessage ラベルを非表示にすることができます。 このイベント ハンドラーに加えて、DataList の DataBinding イベントに対するイベント ハンドラーも必要です。そこで Label の Visible プロパティを True に初期化します。 DataBinding イベントは ItemDataBound イベントの前に発生するため、Label の Visible プロパティは最初は True に設定されますが、データ項目がある場合は False に設定されます。 このロジックを実装するコードを次に示します。

protected void ProductsInCategory_DataBinding(object sender, EventArgs e)
{
    // Show the Label
    NoProductsMessage.Visible = true;
}
 
protected void ProductsInCategory_ItemDataBound(object sender, DataListItemEventArgs e)
{
    // If we have a data item, hide the Label
    if (e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
        NoProductsMessage.Visible = false;
}

Northwind データベース内のすべてのカテゴリは、1 つ以上の製品に関連付けられています。 この機能をテストするために、筆者はこのチュートリアルの Northwind データベースを手動で調整し、Produce カテゴリ (CategoryID = 7) に関連付けられているすべての製品を Seafood カテゴリ (CategoryID = 8) に再割り当てしました。 これを実行するには、サーバー エクスプローラーから [新しいクエリ] を選択して、次の UPDATE ステートメントを使用します。

UPDATE Products SET
    CategoryID = 8
WHERE CategoryID = 7

データベースを適切に更新した後、CategoryListMaster.aspx ページに戻り、[Produce] リンクをクリックします。 Produce カテゴリに属する製品はもうないため、図 9 のように "There are no products for the selected category…" というメッセージが表示されるはずです。

A Message is Displayed if there are No Products Belonging to the Selected Category

図 9: 選択したカテゴリに属する製品がない場合にメッセージが表示される (フルサイズの画像を表示するにはクリック)

まとめ

マスター/詳細レポートでは、マスター レコードと詳細レコードの両方を 1 つのページに表示できますが、多くの Web サイトでは 2 つの Web ページに分かれています。 このチュートリアルでは、"マスター" Web ページで Repeater を使ってカテゴリを箇条書きで一覧表示し、"詳細" ページで関連付けられた製品を表示することで、このようなマスター/詳細レポートを実装する方法について説明しました。 マスター Web ページの一覧の各項目には詳細ページへのリンクが含まれており、その行の CategoryID 値が渡されました。

指定されたサプライヤーの製品を取得する詳細ページは、ProductsBLL クラスの GetProductsByCategoryID(categoryID) メソッドを使って実現しました。 categoryID パラメーターの値は、CategoryID クエリ文字列値をパラメーター ソースとして使って宣言によって指定しました。 また、FormView を使って詳細ページにカテゴリの詳細を表示する方法と、選択したカテゴリに属する製品がない場合にメッセージを表示する方法についても説明しました。

プログラミングに満足!

著者について

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

特別な感謝

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