ASP.NET の階層的なデータ バインディング
Fritz Onion
DevelopMentor
適用対象:
Microsoft® ASP.NET
要約: ASP.NET のデータ バインディングを実行して、2 次元を超える、本質的に階層的な性質を持ったデータ ソースにバインドする技法について説明します。
HierarchicalDataBindingSample.msi のダウンロード
目次
はじめに
データ バインディング
階層データ
階層データベースのデータにバインドする
XML データにバインドする
ネストされたコントロールにアクセスする
DataGrid および DataList の階層バインディング
制限と効率
まとめ
はじめに
ASP.NET に備わっている便利なアーキテクチャを使用してサーバー側のコントロールにデータをバインドすると、コントロールは予定された表示形式でそのデータをクライアントに表示できます。ASP.NET のデータ バインディングの例は、データベースへのクエリ結果であるフラットなデータ ソースにバインドする例がほとんどです。このようなデータ バインディングは多くのアプリケーションでよく使われていますが、その一方で、単純な 2 次元空間にデータが適合せず、標準のデータ バインディング技法では不十分な場合もあります。
この記事では、2 次元を超える本質的に階層的な性質を持ったデータ ソースにデータ バインディングを実行する技法について説明します。
データ バインディング
ASP.NET のデータ バインディングは、サーバー上でサーバー側のコントロールにデータをバインドして、そのデータをある形式でクライアントに表示するプロセスです。データ バインディングの制約としては、サーバー側のコントロールが DataSource
というプロパティおよび DataBind()
というメソッドをサポートし、コントロールにバインドされるデータ ソースが IEnumerable
インターフェイスを実装している必要があるということだけです。
注: この要件には 2 つの例外があり、注意する必要があります。DataSet
と DataTable
は、どちらも直接バインド可能であり、結果として、既定のテーブルの既定の DataView
にバインドされます (DataView
は IEnumerable
を実装します)。DataSets
と DataTable
は、データ バインディングのデータ ソースとして頻繁に使用されるため、このような便宜が図られています。
コントロールにデータをバインドするには、コントロールの DataSource
プロパティにデータ ソースを割り当て、DataBind()
メソッドを呼び出します。
たとえば、次のデータ ソースについて考えた場合、この例では、Item
クラスのインスタンスでいっぱいになった ArrayList
が返されます。
public class Item
{
private string _name;
public Item(string name) { _name = name; }
public string Name { get { return _name; } }
}
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
items.Add(item);
}
return items;
}
}
ArrayList
は IEnumerable
を実装しているため、TestDataSource
クラスの GetData()
メソッドの結果は、バインディングに有効なデータ ソースです。ここでは、データをバインドするサーバー側のコントロールとして、Repeater
を使用します。そのためには、ItemTemplate
を準備して、列挙可能なデータ ソースの各アイテムをどのように表示するかを指定する必要があります。このサンプルでは、バインドしている Item
クラス インスタンスの Name
プロパティがテキストに設定された CheckBox
コントロールが表示されます。
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
<br/>
</ItemTemplate>
</asp:Repeater>
あとは Repeater
にデータを実際にバインドするだけです。この操作は、次に示すように、Page
派生クラスの Load
ハンドラで行うのが一般的です。
private void Page_Load(object sender, EventArgs e)
{
_itemsRepeater.DataSource = TestDataSource.GetData();
_itemsRepeater.DataBind();
}
次の図は、このページの表示例を示します。
図 1. データ バインディング ページ
階層データ
最初のサンプルでは、データ レベルが 1 つだけのフラットなデータ ソースを使用しました。次の例では、データ ソースの各項目に、サブ項目のコレクションを追加します。
public class Item
{
string _name;
ArrayList _subItems = new ArrayList();
public Item(string name) { _name = name; }
public string Name { get { return _name; } }
public ArrayList SubItems { get { return _subItems; } }
}
作成したデータ ソース入力メソッド GetData
で、各項目に 5 つのサブ項目を追加します。
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
for (int j=0; j<5; j++)
{
Item subItem = new Item("subitem" + j.ToString());
item.SubItems.Add(subItem);
}
items.Add(item);
}
return items;
}
}
これで、トップレベルの項目のコレクションの下に、データ レベルを 1 つ持った階層構造のデータができました。前に実行したデータ バインディングは、そのまま適切に機能します。ただし、第 1 レベルのデータだけが表示され、サブ項目は表示されません。この時点で、すべてのデータを正しく表示するには、各項目のサブ項目に対してネストされたデータ バインドを実行する必要があります。論理的に言うと、既にある Repeater
の ItemTemplate
内にもう 1 つデータ連結コントロールを追加し、トップレベルの Repeater
によって列挙される各 Item
の SubItems
コレクションにそれをバインドする必要があることを意味します。ネストされた Repeater
を .aspx ファイルに追加すれば、宣言による方法でコントロールを追加できます。ただし、ネストされた Repeater
の DataSource
プロパティに現在バインドされている Item
の SubItems
コレクションを正しく割り当てるには、いくらか注意が必要です。これを正しく行うには、宣言を使用して、ネストされた Repeater
の DataSource
プロパティにデータ バインディング式を設定します。SubItems
コレクションは、次のようになります。
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name")
%>'
/>
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems") %>'>
<ItemTemplate>
<br/>
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
</ItemTemplate>
</asp:Repeater>
<br/>
</ItemTemplate>
</asp:Repeater>
データ ソースをトップレベルの Repeater
に既にバインドしているため、分離コード クラスでは何も変更する必要はありません。ネストされたデータ バインドは、トップレベル コレクションの各 Item
に対して 1 度ずつ発生します。このようにネストされた 2 つのデータ連結コントロールを読み取るときは、最も近くにあるコントロールがデータ バインディング式 (<%# %>
) の適用範囲となることに注意してください。この例では、最初の 2 つのデータ バインディング式は、外側のトップレベルの Repeater
のデータ バインディングに適用され、トップレベル コレクションの現在の Item に解決されます。3 番目のデータ バインディング式は、内側の Repeater
に適用され、現在バインドされている Item
の SubItems
コレクションの要素に解決されます。次の図は、このページの表示例を示します。
図 2. Repeater へのデータ バインディングを使用しているページの表示例
ネストされたデータ バインディングは、1 レベルだけに制限されず、任意の深さに拡張可能です。データ連結コントロールのネストと、データ ソースのコレクションのネストが一致し、データ ソースが規則的に形成されている限り、バインディングは機能します。例として、既存の SubItems
コレクションの各 Item
に SubItems
コレクションを追加して、データ レベルがさらに追加されたデータ ソースに拡張してみます。
public class TestDataSource
{
public static ArrayList GetData()
{
ArrayList items = new ArrayList();
for (int i=0; i<10; i++)
{
Item item = new Item("item" + i.ToString());
for (int j=0; j<5; j++)
{
Item subItem = new Item("subitem" + j.ToString());
item.SubItems.Add(subItem);
for (int k=0; k<4; k++)
{
Item subsubItem =
new Item("subsubitem" + k.ToString());
subItem.SubItems.Add(subsubItem);
}
}
items.Add(item);
}
return items;
}
}
この場合も先と同様に、ネストされた新しいデータを表示するには、ネストされた Repeater
をもう 1 つ追加するだけで十分です。この Repeater
の DataSource
プロパティは、第 2 レベルの Repeater
によって現在列挙されている Item の SubItems
プロパティにバインドします。
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="false">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems") %>'
>
<ItemTemplate>
<br/>
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name")
%>'/>
<asp:Repeater Runat="server" EnableViewState="false"
DataSource=
'<%# DataBinder.Eval(Container.DataItem, "SubItems")
%>'>
<ItemTemplate>
<br/>
<asp:CheckBox Runat="server"
Text=
'<%# DataBinder.Eval(Container.DataItem, "Name") %>'
/>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
<br />
</ItemTemplate>
</asp:Repeater>
次の図は、このページの一部分の表示例です。
図 3. ネストされた追加の Repeater が表示されたページ
階層データベースのデータにバインドする
階層的なデータ バインディングの実行方法について基本事項を理解したところで、今度はより実用的なアプリケーションを検討してみましょう。ほとんどのデータ バインディングでは、データベース クエリの結果にバインドするため、ここでは、データベースから取得した階層データを使用します。階層データは通常、テーブル間の一対多のリレーションシップを使用して、リレーショナル データベースに格納されます。たとえば、SQL Server および Microsoft Access の既定のインストールで使用可能な Northwind サンプル データベースでは、Customers
テーブルと Orders
テーブルの間に一対多のリレーションシップが存在します。同様に、Orders
テーブルと Order Details
テーブルの間にも、一対多のリレーションシップが存在します。次の図は、これらのリレーションシップを示しています。
図 4. テーブルのリレーションシップ
さまざまな方法でこのデータのクエリを実行できますが、ここでは、データベースのラウンド トリップを 1 回だけに減らす効果もある一番簡単な方法を採用します。つまり、3 つのすべてのテーブルの内容を引き出して DataSet
に入れ、DataSet
内でリレーションを定義する機能を利用して、データを階層的に取り出します。次に示すように、Load
ハンドラがこれを実行し、結果の DataSet
を _customerRepeater
という ID を持つ Repeater にバインドします。
private void Page_Load(object sender, EventArgs e)
{
string strConn =
"server=.;trusted_connection=yes;database=northwind";
string strSql = "SELECT CustomerID, CompanyName FROM " +
" Customers; " +
"SELECT OrderID, CustomerID, " +
" EmployeeID FROM Orders;" +
"SELECT OrderID, Products.ProductID," +
"ProductName, Products.UnitPrice FROM" +
" [Order Details], Products WHERE " +
" [Order Details].ProductID = " +
"Products.ProductID";
SqlConnection conn = new SqlConnection(strConn);
SqlDataAdapter da = new SqlDataAdapter(strSql, conn);
da.TableMappings.Add("Customers1", "Orders");
da.TableMappings.Add("Customers2", "OrderDetails");
_ds = new DataSet();
da.Fill(_ds, "Customers");
_ds.Relations.Add("Customer_Order",
_ds.Tables["Customers"].Columns["CustomerID"],
_ds.Tables["Orders"].Columns["CustomerID"]);
_ds.Relations[0].Nested = true;
_ds.Relations.Add("Order_OrderDetail",
_ds.Tables["Orders"].Columns["OrderID"],
_ds.Tables["OrderDetails"].Columns["OrderID"]);
_ds.Relations[1].Nested = true;
_customerRepeater.DataSource = _ds.Tables["Customers"];
_customerRepeater.DataBind();
}
DataSet
へのデータの読み込みが完了したら、設定したリレーションを使用して階層的にナビゲートできます。たとえば、DataSet
内の Customers
テーブルの任意の行で、文字列 "Customer_Order
" を指定して GetChildRows()
を呼び出すと、Orders
テーブルからその顧客に関連付けられた行のコレクションを取得できます。同様に、任意の注文に関連した Order Detail
のエントリをすべて検索することもできます。それには、Orders
テーブルの任意の行で、文字列 "Order_OrderDetail
" を指定して GetChildRows
を呼び出し、その注文に関連付けられたすべての Order Detail
エントリを取得します。ここでの目的でさらに便利なのは、DataRowView
クラスの CreateChildView
メソッドです。このメソッドは、特定のリレーションのすべての行が表示された DataView
を返します。
バインディング用のデータの準備が完了したら、データを表示するために、適切なネスト レベルのデータ連結コントロールを追加する必要があります。カスタム データ構造を使用した前の例と同様に、ここでバインドするデータにも深さのレベルが 2 つあります。したがって、各下位レベルのデータを表示するには、ネストされたコントロールが 2 つ必要です。具体的に言うと、DataSet
の Customers
テーブルにバインドするトップレベルの Repeater
が 1 つ必要となるほか、各顧客に関連付けられたすべての Orders
と、各注文に関連付けられたすべての Order Detail
エントリにバインドするために、ネストされた Repeater
が 1 つずつ必要になります。2 つのネストされた Repeater
の DataSource
は、適切なリレーション名を指定して、親の行で CreateChildView
を呼び出した結果です。Repeater
宣言で DataView
を単一の式として作成する代わりに、親の行とリレーション名を受け取り、DataView
を返す関数を分離コード クラスで定義すると便利です。
protected DataView GetChildRelation(object dataItem,
string relation)
{
DataRowView drv = dataItem as DataRowView;
if (drv != null)
return drv.CreateChildView(relation);
else
return null;
}
関数とデータ ソースの準備ができたら、.aspx ファイルに Repeater
コントロールの宣言を記述できます。改行や空白を使用して極めて単純なデータ レイアウトを示します。
<asp:Repeater Runat="server" ID="_customerRepeater"
EnableViewState="false">
<ItemTemplate>
Customer:
<%# DataBinder.Eval(Container.DataItem, "CustomerID") %>
<%# DataBinder.Eval(Container.DataItem,"CompanyName") %>
<br />
<asp:Repeater runat="server" EnableViewState="false"
DataSource=
'<%# GetChildRelation(Container.DataItem,
"Customer_Order")%>'
>
<itemTemplate>
Orderid:<b>
<%#DataBinder.Eval(Container.DataItem, "OrderID")%>
</b><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource=
'<%# GetChildRelation(Container.DataItem,
"Order_OrderDetail")%>'
>
<itemTemplate>
<b><%# DataBinder.Eval(Container.DataItem,
"ProductName") %></b>
$<%# DataBinder.Eval(Container.DataItem,
"UnitPrice") %> <br/>
</itemTemplate>
</asp:Repeater>
</itemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
XML データにバインドする
今日のほとんどのシステムでは XML が階層データの主要形式であるため、XML を考慮しなければ階層データの説明としては完全とはいえません。ASP.NET でサーバー コントロールを XML データにバインドするには、いくつかの方法があります。その 1 つとして、XML データを DataSet
に読み込んで、前に説明した技法を使用する方法があります。または、.NET の XML API を使用してデータを直接読み込んで、読み込まれたデータの列挙可能なクラスに対してバインドを行う方法もあります。最後になりましたが、XML ドキュメントに XSL 変換を適用して XML を表示する特殊な XML
Web コントロールを使用する方法もあり、これが最も魅力的な方法であると言えます。
XmlDocument
クラスは、.NET の XML DOM を実装し、データ バインディングをサポートするコントロールに対するバインディングに直接使用できます。XmlDocument
の DOM のナビゲーションに使用される主なクラスは、ドキュメントの要素を表す XmlNode
です。幸いなことに、XmlNode
クラスは、子の列挙子を返す IEnumerable
を実装します。これは、任意の XmlNode
をデータ バインディングのデータ ソースとして使用できることを意味します。ドキュメントは実際には子を持つ単一のノードにすぎないため、XmlDocument
も XmlNode
から派生することは明らかであり、ナビゲーションがたいへん簡単になります。例として、publishers.xml ファイルに格納された次の XML ドキュメントについて考えてみましょう。
<publishers>
<publisher name="New Moon Books" city="Boston"
state="MA" country="USA">
<author name="Albert Ringer ">
<title name="Is Anger the Enemy?" />
<title name="Life Without Fear" />
</author>
<author name="John White ">
<title name="Prolonged Data Deprivation " />
</author>
<author name="Charlene Locksley ">
<title name="Emotional Security: A New Algorithm" />
</author>
<author name="Marjorie Green ">
<title name="You Can Combat Computer Stress!" />
</author>
</publisher>
<publisher name="Binnet and Hardley" city="Washington"
state="DC" country="USA">
<author name="Sylvia Panteley ">
<title name="Onions, Leeks, and Garlic" />
</author>
<author name="Burt Gringlesby ">
<title name="Sushi, Anyone?" />
</author>
<author name="Innes del Castillo ">
<title name="Silicon Valley Gastronomic Treats" />
</author>
<author name="Michel DeFrance ">
<title name="The Gourmet Microwave" />
</author>
<author name="Livia Karsen ">
<title name="Computer Phobic AND Non-Phobic" />
</author>
</publisher>
<!-- ... -->
</publishers>
ページの Load
ハンドラで、このファイルを XmlDocument
クラスに読み込み、次に示すように、トップレベルの publishers
要素を Repeater にバインドできます。
private void Page_Load(object sender, EventArgs e)
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/Publishers.xml"));
_rep1.DataSource = doc.FirstChild;
_rep1.DataBind();
}
今度は、XML ドキュメントのデータを取り出してクライアントに表示するために、必要となるネストされた Repeater
を記述する方法を考える必要があります。前の 2 つの例を利用して、ほとんど同じ方法でこのデータを作成できます。このドキュメントには 3 レベルのデータ (publishers、authors、titles) があるため、ここでは 3 つの Repeater
コントロールを定義します。authors の Repeater
は publishers の Repeater
の内部にネストし、titles の Repeater
は authors の Repeater
の内部にネストします。この配置は、次のようになります。
<asp:Repeater id="_rep1" runat="server"
EnableViewState="false">
<itemTemplate>
Publisher: <%# ((XmlNode)Container.DataItem).
Attributes["name"].Value %><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<itemTemplate>
Author: <%#
((XmlNode)Container.DataItem)
.Attributes["name"].Value
%><br/>
<asp:Repeater runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<itemTemplate>
<i>
<%# ((XmlNode)Container.DataItem).
Attributes["name"].Value %>
</i><br />
</itemTemplate>
</asp:Repeater>
</itemTemplate>
</asp:Repeater>
<hr />
</itemTemplate>
</asp:Repeater>
表示は次のようになります。
図 5. データ バインディングのテスト
XML データにバインドするプロセスは、前の 2 つの例とはかなり異なります。第 1 に注目すべき点は、宣言の DataSource
の式が、Container.DataItem
のように非常にシンプルなことです。それは、データ バインドの各レベルのデータ ソースが、子の列挙子を実装する単なる XmlNode
であるためです。また、現在のデータ項目からデータを取り出すには、Container.DataItem
を XmlNode
にキャストし、この場合には属性を取り出す必要があったという点にも注目してください。一般に便利な DataBinder.Eval()
メソッドは、XML ソースではなく、データベース ソースで機能するようにデザインされているため、この場合には役立ちません。
一般に、データ連結コントロールを使用して任意の XML データをバインドするのはわずらわしい作業です。前の例ではデータベース テーブルのセットから取り出したデータを使用したので、データは規則的であり適切に構造化されていました。そのため、データの構造に合致した、ネストされたコントロールのセットを定義できました。データが不規則であったり、階層データでなかったりすると、バインドは非常に困難になります。たとえば、次の XML ドキュメントを見てみましょう。
<animals>
<animal>
<name>Dog</name>
<sound>woof</sound>
<hasHair>true</hasHair>
</animal>
<animal>
<name>Cat</name>
<sound>meow</sound>
<hasHair>true</hasHair>
</animal>
<animal>
<name>Pig</name>
<sound>oink</sound>
<hasHair>false</hasHair>
</animal>
</animals>
前の例と同じ技法を使用する場合は、1 つのトップレベル Repeater を定義して各 animal 要素を列挙し、もう 1 つのネストされた Repeater を使用して animal の各サブ要素の表示を試みます。
<asp:Repeater ID="_animalRep" Runat="server"
EnableViewState="false">
<ItemTemplate>
<asp:Repeater Runat="server" EnableViewState="false"
DataSource='<%# Container.DataItem %>' >
<ItemTemplate>
<%# ((XmlNode)Container.DataItem).InnerText %><br
/>
</ItemTemplate>
</asp:Repeater>
<hr />
</ItemTemplate>
</asp:Repeater>
しかし、要素名をまったく使用せずに、単に子ノードの内容を表示するのは、あまり有力な方法ではありません。要素が name のときはある表示方法を使用し、sound のときは別の表示方法を使用するように Repeater に指定するのは、容易なことではありません。それどころか、意図するとおりに XML を表示するには、多数の条件式を記述することになるでしょう。
この時点で、ASP.NET のデータ連結コントロールは、任意の XML ドキュメントにバインドするようにデザインされていないことが明らかになります。代わりに、既存の XML 変換言語の XSL を利用して XML を表示したほうが、はるかに便利です。ASP.NET には、XML コントロールを使用して、ページの一部分の場合でも、XML を表示する便利な方法が準備されています。XML コントロールは、XML ドキュメントと XSL 変換を入力として受け取り、ドキュメントに変換を適用して、XML を表示します。この animal XML ドキュメントでは、次のような animal.xsl を記述できます。
<xsl:transform
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="animal">
<hr />
<xsl:apply-templates />
</xsl:template>
<xsl:template match="name">
<i><xsl:value-of select="." /></i><br/>
</xsl:template>
<xsl:template match="sound">
<b><xsl:value-of select="." />!!!</b><br/>
</xsl:template>
<xsl:template match="hasHair">
Has hair? <xsl:value-of select="." /><br/>
</xsl:template>
</xsl:transform>
次に、この XSL 変換を、ページの XML コントロールへの入力として指定します。
<asp:Xml Runat="server"
DocumentSource="animals.xml"
TransformSource="animals.xsl" />
次のように表示されます。
図 6. animals.xsl の表示
ネストされたコントロールにアクセスする
これまでの例では、ユーザーからデータを収集することなしに、ただデータを表示することだけに焦点を当ててきました。さまざまな階層の深さでバインドされたコントロールからデータを取得するには、動的に作成されたコントロールの階層をナビゲートしてそれらの状態を取得する必要があるので、非常に骨の折れる作業です。より便利な方法としては、データ連結コントロールに埋め込まれたコントロールに、変更通知ハンドラを追加する方法があります。通知ハンドラが起動したときに、コントロールに関連付けられたデータを取り出せます。
この技法を説明するために、最初の例を使用して、Repeater
をカスタム データにバインドし、各項目とサブ項目のチェック ボックスを表示します。ここでは、ユーザーがいずれかのチェック ボックスをオンにしてページを送信した場合は、チェック ボックスがオンになっていることをページの下部にある Label
に印刷します。この .aspx ファイルは、次のようになります。
<asp:Repeater Runat="server" ID="_itemsRepeater"
EnableViewState="False">
<ItemTemplate>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem" />
<asp:Repeater Runat="server" ID="_subitemsRepeater"
EnableViewState="False"
DataSource='<%# DataBinder.Eval(Container.DataItem,
"SubItems") %>'>
<ItemTemplate>
<br/>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem" />
<asp:Repeater Runat="server" EnableViewState="False"
DataSource='<%#
DataBinder.Eval(Container.DataItem,
"SubItems") %>'>
<ItemTemplate>
<br/>
<asp:CheckBox Runat="server"
Text='<%# DataBinder.Eval(Container.DataItem,
"Name") %>'
OnCheckedChanged="OnCheckedItem"/>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
<br />
</ItemTemplate>
</asp:Repeater>
<asp:Button Runat="server" Text="Submit" />
<asp:Label EnableViewState="False" Runat="server"
ID="_message" />
クライアントがページをポストすると、オンからオフ、またはオフからオンに状態が変更された各項目に対して、OnCheckedItem
ハンドラが 1 度ずつ呼び出されます。イベント ハンドラへの sender パラメータを調べると、どのコントロールがクライアントによって変更されたかを識別できます。チェック ボックスの状態が変更されたことを示すメッセージをページに書き込むハンドラの実装サンプルは次のとおりです。
protected void OnCheckedItem(object sender, EventArgs e)
{
CheckBox cb = sender as CheckBox;
if (cb.Checked)
_message.Text += string.Format("{0} was checked<br
/>",
cb.Text);
else
_message.Text += string.Format("{0} was
unchecked<br/>",
cb.Text);
}
DataGrid および DataList の階層バインディング
これまでのすべての例では、簡潔にするために Repeater
コントロールに焦点を当てていましたが、 DataList
コントロールと DataGrid
コントロールを使用して、階層的なデータ バインドを実行することも可能です。実際には、使用するコントロールにかかわらず、データ バインディングの詳細は同じです。Repeater
とネストされた DataList
を使用するなど、コントロールを組み合わせることも可能です。この記事のコード サンプルには、DataList
クラスと DataGrid
クラスについて、Repeater
を使用したサンプルとまったく同じものが含まれています。
DataList
クラスによって表示されるテーブルでは、テーブルの各セルに結果セットの行が表示されます。DataList
を階層的なバインドに使用すると、ネストされたテーブルが表示されます。トップレベル コントロールのセルには、ネストされたコントロールによって表示されるテーブル全体が含まれます。次の表示例では、3 つのレベルの階層を使用して DataList
にデータをバインドすることで、Dataset
に Northwind データが挿入されています (1 つのトップレベルのセルのみ)。
図 7. Northwind データが挿入された DataSet
DataGrid
クラスによって表示されるテーブルでは、テーブルの各行に結果セットの行が表示されます。DataGrid
を階層的なバインドに使用した場合も、ネストされたテーブルが表示されます。ただし、DataList
の場合とは異なり、ユーザーの判断しだいでどのセルにネストされたテーブルを含めるかを決定できます。それには、列をテンプレート列として作成し、ネストされた DataGrid
をテンプレート列の定義の一部として追加します。次の表示例では、3 つのレベルの階層を使用して DataGrid
にデータをバインドすることで、Dataset
に Northwind データが挿入されています (1 つのトップレベルの行のみ)。
図 8. Northwind データが挿入された DataSet
制限と効率
ASP.NET のデータ バインディング メカニズムは、フラットなデータ ソースとのバインド用にデザインされている点に注意してください。階層のバインドにも使用できますが、本質的に階層データの性質を持つデータの表示には最適な選択ではない場合もあります。データ ソースは規則的に形成されている必要があります。つまり、ある場所では深さのレベルが 2 で他の場所では 4 か 5 の場合、そのデータ ソースはバインドに適しません。任意の階層で形成されたデータの表示には、XSL が最適です。したがって、XML へのデータの変換および ASP XML コントロールでの XSL 変換の使用が、通常では最適な方法になります。
この記事のすべてのサンプルでは、各データ連結コントロールの EnableViewState
フラグが、注意深く false に設定されています。 ViewState
は、同一ページへの POST 要求の合間に、コントロールに代わって状態を格納するために使用されます。既定では、ASP.NET のすべてのサーバー側のコントロールでは、POST 要求の合間のすべての状態が保存されます。これによって、すべてのコントロールの状態が保存されるため、たいへん便利です。ただし、ページのサイズが著しく増加する可能性があるため、状態の保存を利用しない場合は、それらのコントロールの ViewState
をオフにする方法を取ってください。この技法を使用すると、各データ連結コントロールとそのすべての子コントロールの全内容が ViewState
に既定で格納されるため、特に ViewState
が膨張しやすくなります。ほとんどの場合、この ViewState
の内容は、まったく使用されません。というのは、ページへの最初の GET 要求であれ、それ以降の同じページへの POST 要求であれ、データ連結コントロールは各要求ごとにデータ ソースに再びバインドされるからです。したがって、特にこの記事で説明している階層的なデータ バインディング技法を使用する場合は、他に特別な理由がない限り、すべてのデータ連結コントロールの EnableViewState
を false に設定することをお勧めします。Repeater
の例では、内部にネストされた CheckBox
コントロールの ViewState
を無効にしませんでした。これと同様に、データ連結コントロールの ItemTemplate
の内部にネストされたサーバー側のコントロールについては、ViewState
が有効なままであっても構いません。ただし、実際のデータ連結コントロールでは、無効にすることを忘れないでください。どれだけ深刻な問題になり得る可能性があるかを理解するための例として、サンプルで DataSet
にバインドされるすべての Repeater
コントロールの ViewState を有効にした場合、ViewState
フィールドは 250,000 文字に膨張しました。これに対して、ページに表示可能な文字数は、およそ 100,000 文字です。
まとめ
この記事では、テンプレート化されたデータ連結コントロールをネストし、動的にデータ ソースを割り当てて、階層データにバインドするための技法について説明しました。この技法は、データベース内のテーブル リレーションで見られるような、規則的で構造化された階層データの表示に役立ちます。この技法を使用して他の階層データ ソースを表示することも可能ですが、一部の次元でデータが不規則な場合は、扱いづらい技法です。代替の技法として、XML データ ソースで XSL を使用すると、通常ではより簡潔に表示できるうえ、表示を詳細に制御できます。