入れ子になったデータ Web コントロール (C#)

作成者: Scott Mitchell

PDF のダウンロード

このチュートリアルでは、別の Repeater 内に入れ子になったリピータを使用する方法について説明します。 この例では、内部 Repeater に宣言とプログラムの両方を設定する方法を示します。

はじめに

静的 HTML およびデータバインド構文に加えて、テンプレートには Web コントロールとユーザー コントロールを含めることもできます。 これらの Web コントロールには、宣言型、データバインド構文を使用してプロパティを割り当てることも、適切なサーバー側イベント ハンドラーでプログラムからアクセスすることもできます。

テンプレート内にコントロールを埋め込むことで、外観とユーザー エクスペリエンスをカスタマイズして改善できます。 たとえば、GridView コントロールのチュートリアルの TemplateFields の使用に関するチュートリアルでは、TemplateField に Calendar コントロールを追加して従業員の雇用日を表示することで、GridView の表示をカスタマイズする方法について説明しました。「編集および挿入インターフェイスへの検証コントロールの追加」および「データ変更インターフェイスのカスタマイズ」チュートリアルでは、検証コントロール、TextBoxes、DropDownLists、およびその他の Web コントロールを追加して、編集および挿入インターフェイスをカスタマイズする方法について説明しました。

テンプレートには、他のデータ Web コントロールを含めることもできます。 つまり、テンプレート内に別の DataList (Repeater、GridView、DetailsView など) を含む DataList を含めることができます。 このようなインターフェイスの課題は、適切なデータを内部データ Web コントロールにバインドすることです。 ObjectDataSource を使用した宣言型オプションからプログラムによるオプションまで、さまざまな方法があります。

このチュートリアルでは、別の Repeater 内に入れ子になったリピータを使用する方法について説明します。 外側の Repeater には、データベース内の各カテゴリの項目が含まれており、カテゴリの名前と説明が表示されます。 各カテゴリアイテムの内部リピータには、そのカテゴリに属する各製品の情報が箇条書きリストに表示されます (図 1 を参照)。 この例では、内部 Repeater を宣言とプログラムの両方で設定する方法を示します。

各カテゴリとその製品が一覧表示されます

図 1: 各カテゴリとその製品が一覧表示されます (クリックするとフルサイズの画像が表示されます)

手順 1: カテゴリ一覧の作成

入れ子になったデータ Web コントロールを使用するページを作成する場合は、入れ子になった内部コントロールを気にすることなく、最も外側のデータ Web コントロールを最初に設計、作成、テストすると役立ちます。 したがって、まず、各カテゴリの名前と説明を一覧表示するページに Repeater を追加するために必要な手順を説明します。

まず、フォルダー内のページをNestedControls.aspxDataListRepeaterBasics開き、ページに Repeater コントロールを追加し、そのプロパティを IDCategoryList設定します。 Repeater のスマート タグから、 という名前 CategoriesDataSourceの新しい ObjectDataSource を作成することを選択します。

新しい ObjectDataSource CategoriesDataSource に名前を付けます

図 2: 新しい ObjectDataSource に名前を付ける CategoriesDataSource (クリックするとフルサイズの画像が表示されます)

ObjectDataSource を構成して、クラスの GetCategories メソッドからデータをCategoriesBLL取得します。

CategoriesBLL クラスの GetCategories メソッドを使用するように ObjectDataSource を構成する

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

Repeater のテンプレート コンテンツを指定するには、ソース ビューに移動し、宣言型構文を手動で入力する必要があります。 要素に ItemTemplate カテゴリの名前を表示する を <h4> 追加し、段落要素 (<p>) にカテゴリの説明を追加します。 さらに、各カテゴリを水平ルール (<hr>) で区切りましょう。 これらの変更を行った後、ページには、次のような Repeater と ObjectDataSource の宣言構文が含まれている必要があります。

<asp:Repeater ID="CategoryList" DataSourceID="CategoriesDataSource"
    EnableViewState="False" runat="server">
    <ItemTemplate>
        <h4><%# Eval("CategoryName") %></h4>
        <p><%# Eval("Description") %></p>
    </ItemTemplate>
    <SeparatorTemplate>
        <hr />
    </SeparatorTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

図 4 は、ブラウザーで表示したときの進行状況を示しています。

各カテゴリの名前と説明は、水平ルールで区切って一覧表示されます

図 4: 各カテゴリの名前と説明が一覧表示され、横の規則で区切られます (フルサイズの画像を表示する をクリックします)。

手順 2: 入れ子になった製品リピータを追加する

カテゴリ一覧が完了したら、次のタスクは、適切なカテゴリに属する製品にCategoryListItemTemplate関する情報を表示する Repeater を に追加することです。 この内部リピータのデータを取得するには、いくつかの方法があります。そのうちの 2 つについて簡単に説明します。 ここでは、Repeater 内に製品の Repeater ItemTemplateCategoryList作成してみましょう。 具体的には、製品の名前と価格を含む各リスト アイテムを含む箇条書きリストに製品 Repeater に各製品を表示します。

この Repeater を作成するには、内部の Repeater の宣言型構文とテンプレートを に手動で入力するCategoryListItemTemplate必要があります。 Repeater 内ItemTemplateに次のマークアップをCategoryList追加します。

<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
    runat="server">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li><strong><%# Eval("ProductName") %></strong>
            (<%# Eval("UnitPrice", "{0:C}") %>)</li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

手順 3: Category-Specific 製品を ProductsByCategoryList Repeater にバインドする

この時点でブラウザーを介してページにアクセスすると、画面は図 4 と同じように表示されます。これは、データをリピータにバインドしていないためです。 適切な製品レコードを取得して Repeater にバインドするには、いくつかの方法があります。他の方法よりも効率的です。 ここでのメインの課題は、指定されたカテゴリに適した製品を取り戻すことです。

内部の Repeater コントロールにバインドするデータには、Repeater の ObjectDataSource CategoryList を使用して宣言によってアクセスするか、ASP.NET ページの ItemTemplate分離コード ページからプログラムでアクセスできます。 同様に、このデータは、内部 Repeater のプロパティを使用するか、宣言型のデータ バインド構文を使用するか、または Repeater のDataSourceIDイベント ハンドラーでCategoryList内部 Repeater を参照し、プログラムでそのプロパティを設定DataSourceし、そのDataBind()メソッドを呼び出すことによって、内部の Repeater ItemDataBound にバインドできます。 これらの各アプローチを調べてみましょう。

ObjectDataSource コントロールとイベント ハンドラーを使用して宣言的にデータにアクセスするItemDataBound

このチュートリアル シリーズ全体で ObjectDataSource を広範囲に使用したので、この例のデータにアクセスするための最も自然な選択肢は、ObjectDataSource を使用することです。 ProductsBLLクラスには、GetProductsByCategoryID(categoryID)指定した categoryIDに属する製品に関する情報を返す メソッドがあります。 したがって、ObjectDataSource を Repeater に CategoryList 追加し、このクラスの ItemTemplate メソッドからデータにアクセスするように構成できます。

残念ながら、Repeater ではデザイン ビューでテンプレートを編集することは許可されていないため、この ObjectDataSource コントロールの宣言構文を手動で追加する必要があります。 次の構文は、CategoryListこの新しい ObjectDataSource (ProductsByCategoryDataSource) を追加した後の Repeater をItemTemplate示しています。

<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
        DataSourceID="ProductsByCategoryDataSource" runat="server">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li><strong><%# Eval("ProductName") %></strong> -
                sold as <%# Eval("QuantityPerUnit") %> at
                <%# Eval("UnitPrice", "{0:C}") %></li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
           SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
   <SelectParameters>
        <asp:Parameter Name="CategoryID" Type="Int32" />
   </SelectParameters>
</asp:ObjectDataSource>

ObjectDataSource アプローチを使用する場合は、Repeater の プロパティを ProductsByCategoryList ObjectDataSource (ProductsByCategoryDataSource) の の ID に設定する必要DataSourceIDがあります。 また、ObjectDataSource には、 メソッドに<asp:Parameter>GetProductsByCategoryID(categoryID)渡される値をcategoryID指定する 要素があることに注意してください。 しかし、この値を指定するにはどうすればよいですか? 理想的には、次のように、データバインド構文をDefaultValue<asp:Parameter>使用して要素の プロパティを設定できます。

<asp:Parameter Name="CategoryID" Type="Int32"
     DefaultValue='<%# Eval("CategoryID")' />

残念ながら、データバインド構文は、イベントを持つ DataBinding コントロールでのみ有効です。 クラスには Parameter このようなイベントがないため、上記の構文は無効であり、実行時エラーが発生します。

この値を設定するには、Repeater のItemDataBoundイベントのイベント ハンドラーを作成するCategoryList必要があります。 イベントは、 ItemDataBound Repeater にバインドされている各項目に対して 1 回発生することを思い出してください。 したがって、外側の Repeater に対してこのイベントが発生するたびに、ObjectDataSource の CategoryID パラメーターに現在CategoryIDの値をProductsByCategoryDataSource割り当てることができます。

次のコードを使用して、 CategoryList Repeater イベントの ItemDataBound イベント ハンドラーを作成します。

protected void CategoryList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem ||
        e.Item.ItemType == ListItemType.Item)
    {
        // Reference the CategoriesRow object being bound to this RepeaterItem
        Northwind.CategoriesRow category =
            (Northwind.CategoriesRow)((System.Data.DataRowView)e.Item.DataItem).Row;
        // Reference the ProductsByCategoryDataSource ObjectDataSource
        ObjectDataSource ProductsByCategoryDataSource =
            (ObjectDataSource)e.Item.FindControl("ProductsByCategoryDataSource");
        // Set the CategoryID Parameter value
        ProductsByCategoryDataSource.SelectParameters["CategoryID"].DefaultValue =
            category.CategoryID.ToString();
    }
}

このイベント ハンドラーは、まず、ヘッダー、フッター、または区切り記号の項目ではなく、データ項目を確実に処理します。 次に、現在RepeaterItemの にバインドされた実際CategoriesRowのインスタンスを参照します。 最後に、 で ItemTemplate ObjectDataSource を参照し、その CategoryID パラメーター値を CategoryID 現在 RepeaterItemの の に割り当てます。

このイベント ハンドラーを使用すると、 ProductsByCategoryList それぞれの RepeaterItem Repeater は、カテゴリ内の製品に RepeaterItem バインドされます。 図 5 は、結果の出力のスクリーン ショットを示しています。

外側のリピータは各カテゴリLists。内部リピータは、そのカテゴリの製品をListsします

図 5: Outer Repeater Lists Each Category;Inner One Lists、そのカテゴリの製品 (フルサイズの画像を表示する をクリックします)

カテゴリ データによる製品へのプログラムによるアクセス

ObjectDataSource を使用して現在のカテゴリの製品を取得する代わりに、ASP.NET ページの分離コード クラス (またはフォルダー内または App_Code 別のクラス ライブラリ プロジェクト内) にメソッドを作成し、渡されたときに CategoryID適切な製品セットを返すことができます。 ASP.NET ページの分離コード クラスにこのようなメソッドがあり、 という名前 GetProductsInCategory(categoryID)だったとします。 このメソッドを使用すると、次の宣言構文を使用して、現在のカテゴリの製品を内部 Repeater にバインドできます。

<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
      DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
  ...
</asp:Repeater>

Repeater の DataSource プロパティは、データバインド構文を使用して、そのデータが メソッドから GetProductsInCategory(categoryID) 取得されたことを示します。 は 型Objectの値を返すEval("CategoryID")ので、 メソッドに渡す前に オブジェクトを IntegerGetProductsInCategory(categoryID)キャストします。 CategoryIDここでデータバインド構文を使用してアクセスする はCategoryID、外側の Repeater (CategoryList) 内の であり、テーブル内Categoriesのレコードにバインドされているものです。 したがって、データベースNULL値にできないことがわかCategoryIDっているので、 を処理DBNullしているかどうかを確認せずにメソッドをEval盲目的にキャストできます。

この方法では、 メソッドを GetProductsInCategory(categoryID) 作成し、指定された categoryIDを指定して適切な製品セットを取得する必要があります。 これを行うには、クラスの GetProductsByCategoryID(categoryID) メソッドによって返される をProductsDataTableProductsBLL返すだけです。 ページの GetProductsInCategory(categoryID) 分離コード クラスに メソッドを NestedControls.aspx 作成してみましょう。 これを行うには、次のコードを使用します。

protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
    // Create an instance of the ProductsBLL class
    ProductsBLL productAPI = new ProductsBLL();
    // Return the products in the category
    return productAPI.GetProductsByCategoryID(categoryID);
}

このメソッドは、単に メソッドのインスタンスを ProductsBLL 作成し、 メソッドの結果を GetProductsByCategoryID(categoryID) 返します。 メソッドは または ProtectedをマークPublicする必要があることに注意してください。メソッドが マークされているPrivate場合、ASP.NET ページの宣言型マークアップからはアクセスできません。

この新しい手法を使用するためにこれらの変更を行った後、ブラウザーを使用してページを表示します。 ObjectDataSource およびイベント ハンドラーアプローチを使用する場合、出力は出力と ItemDataBound 同じである必要があります (スクリーン ショットを表示するには、図 5 を参照してください)。

注意

ASP.NET ページの分離コード クラスで メソッドを作成 GetProductsInCategory(categoryID) すると、ビジーのように見える場合があります。 結局のところ、このメソッドは単に クラスのインスタンスを ProductsBLL 作成し、その GetProductsByCategoryID(categoryID) メソッドの結果を返します。 内部リピータのデータバインド構文からこのメソッドを直接呼び出すのはなぜですか DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'? この構文はクラスの現在の ProductsBLL 実装では機能しませんが (メソッドはインスタンス メソッドであるため GetProductsByCategoryID(categoryID) )、静的メソッドを含むように変更 ProductsBLL したり、クラスにクラス GetProductsByCategoryID(categoryID) の新しいインスタンスを返す静的 Instance() メソッドを ProductsBLL 含めたりすることができます。

このような変更を行うと、ASP.NET ページの分離コード クラスの メソッドは不要 GetProductsInCategory(categoryID) になりますが、分離コード クラス メソッドを使用すると、取得したデータを柔軟に操作できます。これについては後ほど説明します。

すべての製品情報を一度に取得する

調査した 2 つの精通した手法は、クラスの メソッドを呼び出 ProductsBLL すことによって、現在のカテゴリの製品を取得します (最初の GetProductsByCategoryID(categoryID) アプローチは ObjectDataSource を介して行い、2 つ目は分離コード クラスの メソッドを介して GetProductsInCategory(categoryID) 行いました)。 このメソッドが呼び出されるたびに、ビジネス ロジックレイヤーはデータ アクセス層を呼び出し、指定された入力パラメーターと一致するフィールドを持つCategoryIDテーブルからProducts行を返す SQL ステートメントを使用してデータベースに対してクエリを実行します。

システムに N 個のカテゴリがある場合、このアプローチでは、 N + 1 個の呼び出しでデータベース 1 データベース クエリが呼び出され、すべてのカテゴリが取得され、 N が呼び出されて各カテゴリに固有の製品が取得されます。 ただし、2 つのデータベース呼び出しで必要なすべてのデータを取得し、1 つの呼び出しですべてのカテゴリを取得し、もう 1 つを呼び出してすべての製品を取得できます。 すべての製品を取得したら、それらの製品をフィルター処理して、現在 CategoryID の製品と一致する製品のみがそのカテゴリの内部リピータにバインドされるようにすることができます。

この機能を提供するには、ASP.NET ページの分離コード クラスの メソッドを少し変更 GetProductsInCategory(categoryID) するだけで済みます。 クラスの GetProductsByCategoryID(categoryID) メソッドのProductsBLL結果を盲目的に返すのではなく、まずすべての製品にアクセスし (まだアクセスしていない場合)、渡された CategoryIDに基づいて製品のフィルター処理されたビューのみを返すことができます。

private Northwind.ProductsDataTable allProducts = null;
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
    // First, see if we've yet to have accessed all of the product information
    if (allProducts == null)
    {
        ProductsBLL productAPI = new ProductsBLL();
        allProducts = productAPI.GetProducts();
    }
    // Return the filtered view
    allProducts.DefaultView.RowFilter = "CategoryID = " + categoryID;
    return allProducts;
}

ページ レベル変数 allProductsの追加に注意してください。 これにより、すべての製品に関する情報が保持され、メソッドが初めて呼び出されたときに GetProductsInCategory(categoryID) 設定されます。 オブジェクトがallProducts作成されて設定されたことを確認した後、メソッドは DataTable の結果をフィルター処理して、指定した CategoryID 行と一致する行CategoryIDにのみアクセスできるようにします。 この方法により、データベースへのアクセス回数が N + 1 から 2 に減ります。

この機能強化では、ページのレンダリングされたマークアップに変更は加えられません。また、他の方法よりも少ないレコードが返されることはありません。 データベースへの呼び出しの数を減らすだけです。

注意

データベース アクセスの数を減らすことでパフォーマンスが確実に向上するという理由が直感的に考えられます。 ただし、そうではない可能性があります。 たとえば、 が NULLの製品CategoryIDが多数ある場合、 メソッドのGetProducts呼び出しでは、表示されない製品の数が返されます。 さらに、カテゴリのサブセットのみを表示している場合は、すべての製品を返すのが無駄になる可能性があります。これは、ページングを実装している場合に当てはまります。

いつものように、2 つの手法のパフォーマンスを分析する場合、唯一の surefire メジャーは、アプリケーションの一般的なケース シナリオに合わせて調整された制御されたテストを実行することです。

まとめ

このチュートリアルでは、あるデータ Web コントロールを別のデータ Web コントロール内に入れ子にする方法について説明しました。具体的には、外側の Repeater に各カテゴリのアイテムを表示する方法を調べ、内部リピータには箇条書きの各カテゴリの製品が一覧表示されています。 入れ子になったユーザー インターフェイスを構築する際のメインの課題は、正しいデータにアクセスして内部データ Web コントロールにバインドすることです。 利用できるさまざまな手法があります。そのうちの 2 つは、このチュートリアルで調べました。 最初に調べたアプローチでは、そのプロパティを介して内部データ Web コントロールにバインドされた外部データ Web コントロールで ItemTemplate ObjectDataSource を DataSourceID 使用しました。 2 番目の手法では、ASP.NET ページの分離コード クラスの メソッドを介してデータにアクセスしました。 このメソッドは、データ バインド構文を使用して、内部データ Web コントロールの DataSource プロパティにバインドできます。

このチュートリアルで調べた入れ子になったユーザー インターフェイスでは、Repeater 内に入れ子になった Repeater を使用していますが、これらの手法は他のデータ Web コントロールに拡張できます。 GridView 内に Repeater を入れ子にしたり、DataList 内で GridView を入れ子にしたりできます。

幸せなプログラミング!

著者について

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

特別な感謝

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