(0) exportieren Drucken
Alle erweitern
Erweitern Minimieren

Hierarchische Datenbindung in ASP.NET

Veröffentlicht: 24. Sep 2003 | Aktualisiert: 27. Jun 2004
Von Fritz Onion

Hier erfahren Sie mehr über das Durchführen von ASP.NET-Datenbindung an Datenquellen, die mehr als zweidimensional und zudem hierarchisch sind. Dieser Artikel enthält auch Links zu englischsprachigen Seiten.

Auf dieser Seite

Einführung Einführung
Datenbindung Datenbindung
Hierarchische Daten Hierarchische Daten
Bindung an hierarchische Datenbankdaten Bindung an hierarchische Datenbankdaten
Bindung an XML-Daten Bindung an XML-Daten
Zugreifen auf verschachtelte Steuerelemente Zugreifen auf verschachtelte Steuerelemente
Hierarchische "DataGrid"- und "DataList"-Bindung Hierarchische "DataGrid"- und "DataList"-Bindung
Einschränkungen und Effizienz Einschränkungen und Effizienz
Schlussfolgerung Schlussfolgerung

Einführung

ASP.NET stellt eine praktische Architektur für die Bindung von Daten an serverseitige Steuerelemente bereit. Diese Daten werden von den Steuerelementen dann auf dem Client in einem Format gerendert, das vom Steuerelement angezeigt werden kann. In den meisten Beispielen für Datenbindung in ASP.NET erfolgt eine Bindung an flache Datenquellen, die das Ergebnis von Datenbankabfragen sind. Diese Art von Datenbindung ist zwar in vielen Anwendungen die gängigste, es gibt jedoch auch Fälle, bei denen die Daten nicht in einen einfachen zweidimensionalen Raum passen und die herkömmlichen Datenbindungstechniken nicht ausreichen.

In diesem Artikel erfahren Sie mehr über die Datenbindung an Datenquellen, die mehr als zweidimensional und zudem hierarchisch sind.

Datenbindung

Bei der Datenbindung in ASP.NET werden Daten auf dem Server an ein serverseitiges Steuerelement gebunden, das diese Daten dann in einem beliebigen Format auf dem Client rendert. Die einzigen Einschränkungen für die Datenbindung bestehen darin, dass das serverseitige Steuerelement eine Eigenschaft mit dem Namen DataSource und eine Methode mit dem Namen DataBind()unterstützen muss und dass die Datenquelle, an die das Steuerelement gebunden wird, die IEnumerable-Schnittstelle implementiert.

Anmerkung Für diese Anforderung gelten zwei Ausnahmen: DataSet und DataTable können direkt gebunden werden, d.h., es erfolgt eine Bindung an die standardmäßige DataView der Standardtabelle (DataView implementiert IEnumerable). Dies wird aus praktischen Gründen so gehalten, denn DataSets und DataTables werden bei der Datenbindung häufig als Datenquelle verwendet.

Für das Binden von Daten an ein Steuerelement weisen Sie die Datenquelle der DataSource-Eigenschaft des Steuerelements zu und rufen dessen DataBind()-Methode auf.

Nehmen wir beispielsweise die folgende Datenquelle, die eine ArrayList voller Instanzen der Item-Klasse zurückgibt:

  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; 
 } 
  }

Da die IEnumerable von der ArrayList implementiert wird, ist das Ergebnis unserer GetData()-Methode in der TestDataSource-Klasse eine gültige Datenquelle für die Datenbindung. Wir verwenden Repeater als das serverseitige Steuerelement, an das wir die Daten binden. Dies setzt voraus, dass wir eine ItemTemplate bereitstellen, in der beschrieben wird, wie die einzelnen Elemente in der auflistbaren Datenquelle gerendert werden sollen. Unsere Beispielanwendung rendert ein CheckBox-Steuerelement, dessen Text auf die Name-Eigenschaft der Item-Klasseninstanz gesetzt wird, an die es gebunden ist:

<asp:Repeater Runat="server" ID="_itemsRepeater"  
  EnableViewState="false"> 
  <ItemTemplate> 
 <asp:CheckBox Runat="server"  
 Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>' 
  /> 
 <br/> 
  </ItemTemplate> 
</asp:Repeater>

Nun müssen wir lediglich noch Daten an den Repeater binden. Dies erfolgt für gewöhnlich im Load-Handler der Page-abgeleiteten Klasse, wie nachfolgend veranschaulicht:

 private void Page_Load(object sender, EventArgs e) 
   { 
   _itemsRepeater.DataSource = TestDataSource.GetData(); 
   _itemsRepeater.DataBind(); 
 }

Im Anschluss finden Sie ein Beispiel für eine Wiedergabe dieser Seite:

HierarchischeDatenbindungASP_01.gif

Abbildung 1. Datenbindungsseite

Hierarchische Daten

Unser erstes Datenquellenbeispiel war flach und enthielt nur eine Datenebene. Nehmen wir an, wir fügen wie folgt jedem Element in unserer Datenquelle eine Auflistung von Unterelementen hinzu:

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; } } 
}

In unserer künstlichen Datenquellen-Füllmethode, GetData, können wir jetzt wie folgt jedem Element 5 Unterelemente hinzufügen:

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; 
  } 
}

Unsere Datenstruktur ist nun hierarchisch, mit einer Datenebene unterhalb der Elementeauflistung oberster Ebene. Die zuvor vorgenommene Datenbindung funktioniert immer noch fehlerfrei, zeigt jedoch nur die erste Datenebene an. Die Unterelemente werden beim Rendering ignoriert. Damit jetzt alle Daten korrekt angezeigt werden, müssen wir eine geschachtelte Datenbindung für die Unterelemente der einzelnen Elemente durchführen. Logisch bedeutet dies, dass wir ein weiteres datengebundenes Steuerelement in die ItemTemplate des bereits vorhandenen Repeater einfügen und dieses an die SubItems-Auflistung jedes Item binden müssen, das vom Repeater der obersten Ebene enumeriert wird. Dies können wir in unserer ASPX-Datei deklarativ durch Hinzufügen eines verschachtelten Repeater erzielen. Die einzige Schwierigkeit besteht darin, die SubItems-Auflistung des derzeit gebundenen Item korrekt der DataSource-Eigenschaft des verschachtelten Repeater zuzuordnen. Zu diesem Zweck setzen wir die DataSource-Eigenschaft des verschachtelten Repeater deklarativ auf einen Datenbindungsausdruck. Dies resultiert in der folgenden SubItems-Auflistung:

<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/>&nbsp;&nbsp;&nbsp; 
  <asp:CheckBox Runat="server" 
 Text= 
   '<%# DataBinder.Eval(Container.DataItem, "Name") %>'  
  /> 
   </ItemTemplate> 
 </asp:Repeater> 
 <br/> 
  </ItemTemplate> 
</asp:Repeater>

In unserer CodeBehind-Klasse müssen keine Änderungen vorgenommen werden, da wir unsere Datenquelle bereits an den Repeater der obersten Ebene binden. Diese verschachtelte Datenbindung erfolgt einmal pro Item in der Auflistung oberster Ebene. Beim Lesen von derartigen verschachtelten datengebundenen Steuerelementpaaren sollten Sie beachten, dass die Datenbindungsausdrücke (<%# %>) auf ihr nächstes Steuerelement ausgelegt sind. In unserem Beispiel sind die ersten beiden Datenbindungsausdrücke auf die äußere Datenbindung des Repeater der obersten Ebene ausgelegt und werden in das aktuelle Element in der Auflistung auf oberster Ebene aufgelöst. Der dritte Datenbindungsausdruck ist auf den inneren Repeater ausgelegt und wird in ein Element in der SubItems-Auflistung des Item aufgelöst, das gerade gebunden wird. Nachfolgend finden Sie ein Beispiel für eine Wiedergabe dieser Seite:

HierarchischeDatenbindungASP_02.gif

Abbildung 2. Seitenwiedergabe mit Datenbindung an ein "Repeater"-Beispiel

Beachten Sie, dass diese verschachtelte Datenbindung nicht auf nur eine Ebene beschränkt ist, sondern beliebig weit in der Tiefe ausgedehnt werden kann. So lange die Verschachtelung der datengebundenen Steuerelemente mit der Verschachtelung der Steuerelemente in der Datenquelle übereinstimmt und die Datenquelle eine reguläre Form hat, funktioniert die Bindung. Beispielsweise können wir unsere Datenquelle um eine zusätzliche Datenebene erweitern und jedem Item in den vorhandenen SubItems-Auflistungen seine eigene SubItems-Auflistung hinzufügen.

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; 
  } 
}

Die einzige Änderung, die zum Anzeigen dieser neuen verschachtelten Daten auf unserer Seite erforderlich ist, besteht darin, einen weiteren verschachtelten Repeater hinzuzufügen, dessen DataSource-Eigenschaft an die SubItems-Eigenschaft des Elements gebunden ist, das derzeit vom Repeater der zweiten Ebene enumeriert wird.

<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/>&nbsp;&nbsp;&nbsp; 
  <asp:CheckBox Runat="server" 
 Text= 
 '<%# DataBinder.Eval(Container.DataItem, "Name")  
   %>'/> 
  <asp:Repeater Runat="server" EnableViewState="false" 
 DataSource= 
 '<%# DataBinder.Eval(Container.DataItem, "SubItems")  
   %>'> 
 <ItemTemplate> 
   <br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
   <asp:CheckBox Runat="server" 
  Text= 
   '<%# DataBinder.Eval(Container.DataItem, "Name") %>'  
  /> 
 </ItemTemplate> 
  </asp:Repeater> 
   </ItemTemplate> 
 </asp:Repeater> 
 <br /> 
  </ItemTemplate> 
</asp:Repeater>

Nachfolgend finden Sie ein Beispiel für eine teilweise Wiedergabe dieser Seite:

HierarchischeDatenbindungASP_03.gif

Abbildung 3. Seite mit einem weiteren verschachtelten "Repeater"

Bindung an hierarchische Datenbankdaten

Nachdem wir nun die Grundlagen der hierarchischen Datenbindung geklärt haben, können wir uns einer praktischeren Anwendung zuwenden. Da es bei Datenbindung meist um eine Bindung an die Ergebnisse einer Datenbankabfrage geht, verwenden wir im Anschluss hierarchische Daten, die aus einer Datenbank abgerufen wurden. Hierarchische Daten werden in der Regel in relationalen Datenbanken unter Verwendung von 1:n-Beziehungen zwischen Tabellen gespeichert. So besteht z.B. in der Beispieldatenbank "Northwind", die standardmäßig mit SQL Server und Microsoft Access installiert wird, eine 1:n-Beziehung zwischen der Tabelle Customers und der Tabelle Orders. Entsprechend gibt es eine 1:n-Beziehung zwischen der Tabelle Orders und der Tabelle Order Details. Diese Beziehungen sind in der nachfolgenden Abbildung dargestellt:

HierarchischeDatenbindungASP_04.gif

Abbildung 4. Tabellenbeziehungen

Es gibt verschiedene Wege, diese Daten abzufragen. Doch die einfachste Lösung, bei der auch die Anzahl der Roundtrips zur Datenbank auf 1 verringert wird, besteht in jedem Fall darin, den Inhalt aller drei Tabellen in ein DataSet zu ziehen und die Möglichkeit zur Definition von Beziehungen in einem DataSet zu nutzen, um die Daten hierarchisch zu extrahieren. Dies wird vom folgenden Load-Handler durchgeführt, und das so erstellte DataSet wird anschließend an einen Repeater mit der ID _customerRepeater gebunden.

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(); 
}

Sobald die Daten in das DataSet geladen sind, können wir darin über die eingerichteten Beziehungen hierarchisch navigieren. So können wir beispielsweise GetChildRows() für jede beliebige Zeile in der Tabelle Customers in unserem DataSet mit der Zeichenfolge "Customer_Order" aufrufen, um eine Zeilenauflistung aus der mit diesem Kunden verknüpften Tabelle Orders abzurufen. Entsprechend finden wir alle Order Detail-Einträge, die mit einer bestimmten Bestellung verknüpft sind, indem wir GetChildRows mit der Zeichenfolge "Order_OrderDetail" in einer Zeile der Tabelle Orders aufrufen, um alle mit dieser Bestellung verknüpften Order Detail-Einträge abzurufen. Noch nützlicher für unsere Zwecke ist die CreateChildView-Methode der DataRowView-Klassse. Diese gibt eine DataView zurück, die alle Zeilen für eine bestimmte Beziehung anzeigt.

Nachdem die Daten nun für die Bindung vorbereitet sind, müssen wir datengebundene Steuerelemente mit entsprechender Verschachtelung hinzufügen, um die Daten zu rendern. Ebenso wie bei unserem vorherigen Beispiel unter Einsatz einer benutzerdefinierten Datenstruktur haben auch hier die Daten, an die gebunden werden soll, zwei Ebenen. Wir brauchen also zwei verschachtelte Steuerelemente, um alle Datenunterebenen zu rendern. Genauer gesagt brauchen wir einen Repeater der obersten Ebene, der an die Tabelle Customers in unserem DataSet gebunden wird, einen verschachtelten Repeater für die Bindung an alle mit den einzelnen Kunden verknüpften Bestellungen (Orders) und einen weiteren verschachtelten Repeater für die Bindung an alle mit den einzelnen Bestellungen verknüpften Order Detail-Einträge. Die DataSource für die beiden verschachtelten Repeater ist das Ergebnis des Aufrufs von CreateChildView in der übergeordneten Zeile mit dem entsprechenden Beziehungsnamen. Anstatt zu versuchen, die DataView in einem einzelnen Ausdruck in der Repeater-Deklaration zu erstellen, bietet es sich an, eine Funktion in unserer CodeBehind-Klasse zu definieren, die die übergeordnete Zeile und den Namen der Beziehung verarbeitet und die DataView zurückgibt.

protected DataView GetChildRelation(object dataItem,  
 string relation) 
{ 
  DataRowView drv = dataItem as DataRowView; 
  if (drv != null) 
 return drv.CreateChildView(relation); 
  else 
 return null; 
}

Nachdem diese Funktion und unsere Datenquelle nun bereitstehen, können wir jetzt die Repeater-Steuerelementdeklarationen in unserer ASPX-Datei schreiben, wobei wir ein recht einfaches Layout mit Umbrüchen und Leerzeichen verwenden:

<asp:Repeater Runat="server" ID="_customerRepeater"  
  EnableViewState="false"> 
  <ItemTemplate> 
 Customer: 
 <%# DataBinder.Eval(Container.DataItem, "CustomerID") %> 
   &nbsp; &nbsp; 
 <%# DataBinder.Eval(Container.DataItem,"CompanyName") %> 
 <br /> 
 <asp:Repeater runat="server" EnableViewState="false" 
   DataSource= 
   '<%# GetChildRelation(Container.DataItem,  
 "Customer_Order")%>' 
 > 
   <itemTemplate> 
 &nbsp;&nbsp;&nbsp;&nbsp; 
 Orderid:<b> 
 <%#DataBinder.Eval(Container.DataItem, "OrderID")%>  
 </b><br/> 
 <asp:Repeater runat="server" EnableViewState="false" 
   DataSource= 
 '<%# GetChildRelation(Container.DataItem,  
 "Order_OrderDetail")%>' 
 > 
   <itemTemplate> 
  &nbsp;&nbsp;&nbsp;&nbsp; 
  &nbsp;&nbsp;&nbsp;&nbsp; 
  <b><%# DataBinder.Eval(Container.DataItem,  
 "ProductName") %></b> 
  $<%# DataBinder.Eval(Container.DataItem,  
  "UnitPrice") %> <br/> 
   </itemTemplate> 
 </asp:Repeater> 
  </itemTemplate> 
   </asp:Repeater> 
 </ItemTemplate> 
</asp:Repeater>

Bindung an XML-Daten

Eine Erörterung hierarchischer Daten ohne Berücksichtigung von XML wäre unvollständig, da es sich dabei in den meisten Systemen von heute um das vorherrschende Format für hierarchische Daten handelt. Es gibt einige Optionen für das Binden von Serversteuerelementen an XML-Daten in ASP.NET. Eine Option besteht darin, die XML-Daten in ein DataSet zu lesen und dann die im vorherigen Abschnitt erläuterten Verfahren anzuwenden. Eine weitere Option besteht darin, die XML-API in .NET zu verwenden, um die Daten direkt zu laden und an auflistbare Klassen in den geladenen Daten zu binden. Die letzte und wohl attraktivste Option besteht darin, das spezialisierte Xml-Websteuerelement zu verwenden, das sich selbst rendert, indem es eine XSL-Transformation (XSLT) auf ein XML-Dokument anwendet.

Die XmlDocument-Klasse stellt eine Implementierung des XML-DOM in .NET bereit und kann direkt für die Bindung an Steuerelemente verwendet werden, die Datenbindung unterstützen. Als primäre Klasse zum Navigieren durch das DOM in XmlDocument wird XmlNode verwendet. Diese Klasse stellt ein Element im Dokument dar. Zu unserem Glück implementiert die XmlNode-Klasse IEnumerable so, dass ein Enumerator über den entsprechenden untergeordneten Elementen zurückgegeben wird, d.h., wir können jeden beliebigen XmlNode als Datenquelle bei der Datenbindung verwenden. Es hat sich herausgestellt, dass XmlDocument ebenfalls von XmlNode abgeleitet wird, da ein Dokument letztendlich nur ein einzelner Knoten mit untergeordneten Elementen ist, so dass die Navigation relativ einfach ist. Nehmen wir beispielsweise das folgende XML-Dokument, das in der Datei publishers.xml gespeichert ist:

<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>

Wir können diese Datei in eine XmlDocument-Klasse im Load-Handler unserer Seite laden und das publishers-Element der obersten Ebene wie folgt an einen Repeater binden:

   private void Page_Load(object sender, EventArgs e) 
   { 
   XmlDocument doc = new XmlDocument(); 
   doc.Load(Server.MapPath("~/Publishers.xml")); 
   _rep1.DataSource = doc.FirstChild; 
   _rep1.DataBind(); 
 }

Als Nächstes müssen wir uns überlegen, wie wir die notwendigen verschachtelten Repeaters schreiben wollen, um die Daten aus dem XML-Dokument zu extrahieren und an den Client zu rendern. Ausgehend von den beiden vorherigen Beispielen können wir diese Daten auf ähnliche Weise modellieren. Da unser Dokument drei Datenebenen hat - Herausgeber (publisher), Autoren (author) und Titel (title) - definieren wir drei Repeater-Steuerelemente, wobei der Autoren-Repeater im Herausgeber-Repeater verschachtelt ist und der Titel-Repeater im Autoren-Repeater. Diese Anordnung ist nachfolgend dargestellt:

<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> 
  &nbsp;&nbsp;Author: <%#  
 ((XmlNode)Container.DataItem) 
 .Attributes["name"].Value  
   %><br/> 
  <asp:Repeater runat="server" EnableViewState="false" 
 DataSource='<%# Container.DataItem %>' > 
  <itemTemplate> 
  &nbsp;&nbsp;&nbsp;&nbsp;<i> 
  <%# ((XmlNode)Container.DataItem). 
 Attributes["name"].Value %> 
  </i><br /> 
  </itemTemplate> 
   </asp:Repeater> 
   </itemTemplate> 
 </asp:Repeater> 
 <hr /> 
  </itemTemplate> 
 </asp:Repeater>

Dies wird wie folgt gerendert:

HierarchischeDatenbindungASP_05.gif

Abbildung 5. Datenbindungstest

Das Binden an XML-Daten unterscheidet sich grundlegend von den beiden vorhergehenden Beispielen. Beachten Sie zunächst, dass unsere deklarativen DataSource-Ausdrücke recht einfach waren: Container.DataItem. Dies ist darauf zurückzuführen, dass die Datenquelle auf jeder Ebene der Datenbindung einfach ein XmlNode ist, der einen Enumerator über seinen untergeordneten Elementen implementiert. Beachten Sie darüber hinaus, dass wir zum Extrahieren von Daten aus dem aktuellen Datenelement Container.DataItem in XmlNode umwandeln und (in diesem Fall) seine Elemente extrahieren mussten. Die für gewöhnlich recht praktische DataBinder.Eval()-Methode ist in diesem Fall nutzlos, da sie für die Arbeit mit Datenbankquellen konzipiert wurde, nicht für XML-Quellen.

Im Allgemeinen gestaltet sich das Binden von willkürlichen XML-Daten unter Verwendung von Datenbindungs-Steuerelementen recht mühsam. Im vorangehenden Beispiel wurden Daten verwendet, die aus einer Reihe von Datenbanktabellen extrahiert wurden. Somit war dieses Beispiel recht regulär und gut strukturiert und ermöglichte die Definition von verschachtelten Steuerelementen, die der Datenstruktur entsprachen. Dies wird schwieriger, wenn die Daten nicht regulär oder nicht hierarchisch sind. Nehmen wir beispielsweise folgendes XML-Dokument:

<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>

Wenn wir dasselbe Verfahren wie bei dem vorhergehenden Beispiel anwenden, können wir versuchen, einen Repeater oberster Ebene zu definieren, der jedes animal-Element mit einem anderen verschachtelten Repeater enumeriert, um jedes Unterelement von animal anzuzeigen:

<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>

Dies ist jedoch nicht sehr überzeugend, da einfach der Inhalt aller untergeordneten Knoten gerendert wird, ohne die Elementnamen zu verwenden. Es gibt keine einfache Lösung, um den Repeater anzuweisen, sich selbst auf eine Art zu rendern, wenn das Element name lautet, und auf eine andere, wenn es sound lautet. Stattdessen müssten wir letztendlich eine Vielzahl von bedingten Ausdrücken schreiben, damit die XML so gerendert wird, wie wir es möchten.

An diesem Punkt dürfte deutlich geworden sein, dass die Datenbindungs-Steuerelemente in ASP.NET nicht für die Bindung an beliebige XML-Dokumente entworfen wurden. Stattdessen bietet es sich an, die vorhandene XML-Transformationssprache XSL für das Rendern von XML zu nutzen. ASP.NET bietet eine praktische Lösung hierfür, selbst für bloße Teile einer Seite: Das Xml-Steuerelement. Dieses verwendet als Eingabe ein XML-Dokument und eine XSL-Transformation (XSLT) und rendert durch Anwendung der Umwandlung auf das Dokument. Für unser animal-XML-Dokument könnten wir die Datei animal.xsl schreiben, die wie folgt aussieht:

<xsl:transform  
   version="1.0"  
   xmlns:xsl="<A href="http://www.w3.org/1999/XSL/Transform">
http://www.w3.org/1999/XSL/Transform</A>"> 
  <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>

Dann könnten wir dies als Eingabe in ein Xml-Steuerelement auf unserer Seite verwenden:

<asp:Xml Runat="server"  
   DocumentSource="animals.xml"  
   TransformSource="animals.xsl" />

Und die Datei würde wie folgt gerendert:

HierarchischeDatenbindungASP_06.gif

Abbildung 6. Wiedergabe von "animals.xsl"

Zugreifen auf verschachtelte Steuerelemente

In unseren bisherigen Beispielen haben wir uns auf die bloße Darstellung der Daten konzentriert, ohne Datenerfassung durch den Benutzer. Das Abrufen von Daten aus den Tiefen eines hierarchisch gebundenen Steuerelements kann recht mühsam sein, da Sie durch die Hierarchie dynamisch erstellter Steuerelemente navigieren und deren Status abrufen müssen. Eine weitere, bequemere Option besteht darin, Steuerelementen, die in dem datengebundenen Steuerelement eingebettet sind, Handler für Änderungsbenachrichtigungen hinzuzufügen. Wenn der Benachrichtigungshandler ausgelöst wird, können Sie die mit dem Steuerelement verbundenen Daten extrahieren.

Zur Veranschaulichung dieser Technik können wir unser erstes Beispiel für das Binden eines Repeater auf benutzerdefinierte Daten und das Rendern von Kontrollkästchen für jedes Element und Unterelement anwenden. Wenn der Benutzer eines der Kontrollkästchen aktiviert und die Seite sendet, drucken wir einfach die Tatsache, dass dieses Kontrollkästchen unten auf der Seite aktiviert wurde, in ein Label. Die ASPX-Datei hierfür sieht wie folgt aus:

<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/>&nbsp;&nbsp;&nbsp; 
  <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/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
   <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" />

Wenn der Client nun die Seite bereitstellt, wird unser OnCheckedItem-Handler einmal für jedes Element aufgerufen, dessen Status sich geändert hat (von "aktiviert" zu "nicht aktiviert" oder umgekehrt). Wir können identifizieren, welches Steuerelement vom Client geändert wurde, indem wir uns den sender-Parameter für unseren Ereignishandler ansehen. Nachfolgend finden Sie eine Beispielimplementierung eines solchen Handlers, der eine Meldung auf die Seite schreibt, aus der hervorgeht, dass sich der Status eines Kontrollkästchens geändert hat:

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); 
}

Hierarchische "DataGrid"- und "DataList"-Bindung

Alle bisherigen Beispiele haben sich aus Gründen der Einfachheit auf das Repeater-Steuerelement beschränkt. Hierarchische Datenbindungen können jedoch auch mit den Steuerelementen DataList und DataGrid durchgeführt werden. Die Details der Datenbindung sind stets dieselben, unabhängig vom verwendeten Steuerelement. Sie können sogar gemischt und beliebig kombiniert werden, indem Sie z.B. einen Repeater mit einer verschachtelten DataList verwenden. Die Codebeispiele für diesen Artikel sind identisch mit denen unter Verwendung des Repeater für die Klassen DataList und DataGrid.

Die DataList-Klasse rendert eine Tabelle, in der jede Tabellenzelle eine Wiedergabe einer Zeile im Ergebnissatz ist. Bei Verwendung der DataList für das hierarchische Binden entstehen verschachtelte Tabellenwiedergaben, in denen eine Zelle im Steuerelement der obersten Ebene eine ganze Tabelle enthält, die vom verschachtelten Steuerelement gerendert wird. Nachfolgend sehen Sie eine Beispielwiedergabe unseres DataSet, das mittels einer hierarchischen Datenbindung mit drei Ebenen an eine DataList mit den "Northwind"-Daten gefüllt wird (nur eine Zelle oberster Ebene).

HierarchischeDatenbindungASP_07.gif

Abbildung 7. "DataSet", gefüllt mit "Northwind"-Daten

Die DataGrid-Klasse rendert eine Tabelle, in der jede Tabellenzeile eine Wiedergabe einer Zeile im Ergebnissatz ist. Bei Verwendung von DataGrid für die hierarchische Bindung entstehen ebenfalls verschachtelte Tabellenwiedergaben, doch anders als bei der DataList können Sie selbst entscheiden, welche Zelle die verschachtelten Tabellen enthält, indem Sie diese Spalte als Vorlagenspalte definieren und ein verschachteltes DataGrid als Teil der Definition der Vorlagenspalte hinzufügen. Nachfolgend sehen Sie eine Beispielwiedergabe unseres DataSet, das mittels einer hierarchischen Datenbindung mit drei Ebenen an eine DataGrid mit den "Northwind"-Daten gefüllt wird (nur eine Zeile oberster Ebene).

HierarchischeDatenbindungASP_08.gif

Abbildung 8. "DataSet", gefüllt mit "Northwind"-Daten

Einschränkungen und Effizienz

Sie sollten stets bedenken, dass der Datenbindungsmechanismus in ASP.NET für die Bindung an flache Datenquellen konzipiert wurde. Er kann zwar auch für hierarchische Bindungen verwendet werden, ist jedoch nicht unbedingt erste Wahl für das Rendern von Daten, die von ihrer Art her wirklich hierarchisch sind. Die Datenquelle muss eine reguläre Form haben. Es ist nicht möglich, an eine Datenquelle zu binden, die in einigen Teilen 2 Ebenen und in anderen 4 oder 5 Ebenen hat. XSL ist für das Rendern von Daten mit willkürlicher hierarchischer Form wesentlich besser geeignet. Daher bietet es sich an, Ihre Daten in XML zu konvertieren und über das ASP-Xml-Steuerelement die XSL-Transformation (XSLT) zu verwenden.

Ihnen ist vielleicht schon aufgefallen, dass in allen Beispielen in diesem Artikel das EnableViewState-Flag in jedem datengebundenen Steuerelement auf FALSE gesetzt wurde. ViewState wird verwendet, um den Status für Steuerelemente zwischen POST-Anforderungen an dieselbe Seite zu speichern. Standardmäßig behalten alle serverseitigen Steuerelemente in ASP.NET ihren gesamten Status zwischen POST-Anforderungen bei. Dies ist äußerst praktisch, da Sie sich für alle Ihre Steuerelemente auf diese Statusbeibehaltung verlassen können. Dabei kann die Wiedergabegröße Ihrer Seiten jedoch ganz erheblich anwachsen. Wenn Sie die Statusbeibehaltung nicht nutzen, sollten Sie somit Schritte ergreifen, um ViewState für diese Steuerelemente zu deaktivieren. Dieses Verfahren ist besonders anfällig für ein übermäßiges Anwachsen von ViewState, da der gesamte Inhalt aller datengebundenen Steuerelemente und aller untergeordneten Steuerelemente standardmäßig in ViewState gespeichert wird. In den meisten Fällen wird der Inhalt von ViewState überhaupt nicht verwendet, da die datengebundenen Steuerelemente bei jeder Anforderung neu an ihre Datenquellen gebunden werden - unabhängig davon, ob es sich um die erste GET-Anforderung an eine Seite oder eine nachfolgende POST-Anforderung für dieselbe Seite handelt. Sofern nicht gute Gründe dagegen sprechen, sollten Sie EnableViewState somit in allen Ihren datengebundenen Steuerelementen auf FALSE setzen, insbesondere wenn Sie die in diesem Artikel erläuterten hierarchischen Datenbindungsverfahren verwenden. Es spricht nichts dagegen, ViewState für verschachtelte serverseitige Steuerelemente in einer ItemTemplate eines datengebundenen Steuerelements aktiviert zu lassen, wie wir dies bei den verschachtelten CheckBox-Steuerelementen in unserem Repeater-Beispiel getan haben, sofern Sie daran denken, dies für das eigentliche datengebundene Steuerelement zu deaktivieren. Ein kleines Beispiel für die drastischen Auswirkungen, die dies haben kann: Wenn Sie ViewState für alle Repeater-Steuerelemente in unserem Beispiel aktivieren, die an ein DataSet gebunden sind, wächst das ViewState-Feld auf 250.000 Zeichen an. Dies steht im Widerspruch zu der Anzahl von sichtbaren Zeichen, die auf der Seite wiedergegeben werden, nämlich ca. 100.000.

Schlussfolgerung

In diesem Artikel wurde ein Verfahren für das Binden an hierarchische Daten durch Verschachtelung von datengebundenen Steuerelementen mit Vorlagen und das dynamische Zuordnen der entsprechenden Datenquellen vorgestellt. Dieses Verfahren kann äußerst nützlich sein für das Rendern von regulären, strukturierten hierarchischen Daten, wie sie sich in Tabellenbeziehungen in einer Datenbank finden. Es ist auch möglich, andere hierarchische Datenquellen mit diesem Verfahren zu rendern, dies kann jedoch mühsam sein, wenn die Daten nicht in allen Dimensionen regulär sind. Alternative Verfahren unter Verwendung von XSL mit XML-Datenquellen sind für gewöhnlich präziser und ermöglichen Ihnen eine stärkere Kontrolle über den Renderingprozess.


Anzeigen:
© 2014 Microsoft