Einmal mehr hat sich für mich heute die 80/20-These bestätigt. Da wollte ich "mal eben" was runterprogrammieren, und bin dann wieder an einer unbedeutenden Kleinigkeit hängengeblieben.
Worum geht's? Ich hatte mir in den Kopf gesetzt, für eine Software, die so flexibel wie möglich eingesetzt werden soll, vollständig auf Code zur Datenbindung usw. im CodeBeside zu verzichten. Hier bietet sich als Möglichkeit z.B. an, einfach eigene Controls zu erstellen - anstatt eines normalen Repeater pflanzt man dann sein eigenes Control in die Seite. Die Vorteile des Repeaters (Templating) bleiben und müssen nicht selbst implementiert werden, und dazu hat derjenige, der die Templates mal bearbeitet, den Luxus, sich nicht um das Databinding kümmern zu müssen, weil dies vom Control selbst erledigt wird.
In der Theorie alles wunderbar, nur in der Praxis, gerade bei Repeater-Controls, etwas komplizierter.
Im konkreten Fall habe ich 2 ineinander verschachtelte Repeater, wobei das innere Daten in Abhängigkeit vom Äußeren darstellt. Hierfür benötigt es einen Parameter, der eigentlich auch zur Verfügung steht:
<cc1:Categories ID="Categories1" runat="server">
<ItemTemplate>
<p><strong><%# Eval("Name") %></strong></p>
<cc1:Articles ID="Articles" runat="server" CategoryID='<%# Eval("ID") %>'>
<HeaderTemplate>
<ol>
</HeaderTemplate>
<ItemTemplate>
<li><%# Eval("Name") %></li>
</ItemTemplate>
<FooterTemplate>
</ol>
</FooterTemplate>
</cc1:Articles>
</ItemTemplate>
</cc1:Categories>
Beim Ausführen führt dieser Code zu diesem Fehler. Da der "DataBinder" in diesem Kontext fehlt, weil das Binding schön gekapselt im Inneren stattfindet, fällt die vorgeschlagene Lösung, die auch funktionieren würde, leider flach:
<%#DataBinder.Eval(Container.DataItem, "SomeProperty") %>
Okay, was bleibt? Zwei Möglichkeiten:
- Das innere Control holt sich den Parameter selbst vom Äußeren.
- Man gestaltet das Binding anders.
Beide Varianten funktionieren, setzen aber voraus, dass das äußere Control die beim Binding gültige CategoryID jeweils global zur Verfügung stellt.
Dafür bekommt es eine neue Eigenschaft:
private int currentCategoryID;
public int CurrentCategoryID
{
get { return currentCategoryID; }
}
Welche auch vom Control selbst befüllt wird (wichtig: OnItemDataBound ist zu spät!):
protected override void OnItemCreated(RepeaterItemEventArgs e)
{
base.OnItemCreated(e);
if (e.Item.DataItem != null && e.Item.DataItem is Category)
currentCategoryID = ((Category)e.Item.DataItem).ID;
}
Um sich die Daten aus dem inneren Control direkt zu holen, muss man sich einfach nur über die ControlCollection das Parent-Control holen und entsprechend casten:
Categories cats = (Categories)Parent.Parent;
Anschließend hat man auf die Property mit dem aktuell gültigen Wert Zugriff. Dumm allerdings, wenn das innere Control einmal separat ohne das Äußere verwendet wird, das sollte man bedenken. Allerdings kann man natürlich immer noch den Wert von außen vorbelegen. Das geht nun übrigens, der Vollständigkeit halber, auch mit der CurrentCategoryID-Property:
<cc1:Articles ID="Articles" runat="server" CategoryID='<%# Categories1.CurrentCategoryID %>'>
Viel Aufwand für einen geringen Effekt, und alles nur wiel ein Eval("") nicht funktioniert ... ein funktionierendes Beispielprojekt ist übrigens angehängt.
Downloads