Thomas Bandt

Über mich | Kontakt | Archiv

ASP.NET MVC - Repeater (Listendarstellungen)

Ich hatte bereits Ende Oktober kurz etwas zum Thema Datenübergabe an Html.RenderPartial() innerhalb eines ASP.NET-Repeater-Controls geschrieben. Ich habe heute keine Ahnung mehr, wie ich damals zu dem Schluss kommen konnte, dass das so funktioniert - denn, Asche über mein Haupt, es funktioniert überhaupt nicht.

Der Grund hierfür liegt im Lebenszyklus einer ASP.NET-WebForm bzw. der Art und Weise der Datenbindung begründet. Ich habe eine ganze Weile recherchiert und bin dann zu folgender Bastel-Zwischenlösung gelangt:

   1:  <asp:Repeater ID="RMessages" runat="server" 
   2:      OnItemDataBound="RMessages_ItemDataBound">
   3:      <ItemTemplate>
   4:          <% Html.RenderPartial("Username", new UsernameViewData() 
   5:              { UserID = Authors[GetItemIndex(true)] }); %>
   6:      ItemTemplate>
   7:  </asp:Repeater>

 

   1:  public partial class Inbox : ViewPage
   2:  {
   3:   
   4:      private int ItemIndex = -1;
   5:      public Dictionary<int, Guid> Authors { get; set; }
   6:   
   7:      protected void Page_Load(object sender, EventArgs e)
   8:      {
   9:          if (ViewData.Model.Messages.Count > 0)
  10:          {
  11:              Authors = new Dictionary<int, Guid>();
  12:              RMessages.DataSource = ViewData.Model.Messages;
  13:              RMessages.DataBind();
  14:          }
  15:      }
  16:   
  17:      protected void RMessages_ItemDataBound(object sender, 
  18:          RepeaterItemEventArgs e)
  19:      {
  20:          if (e.Item.ItemType == ListItemType.Item || 
  21:              e.Item.ItemType == ListItemType.AlternatingItem)
  22:          {
  23:              Message msg = (Message)e.Item.DataItem;
  24:              Authors[e.Item.ItemIndex] = msg.AuthorID;
  25:          }
  26:      }
  27:   
  28:      protected int GetItemIndex(bool count)
  29:      {
  30:          if (count)
  31:              ItemIndex++;
  32:          return ItemIndex;
  33:      }
  34:   
  35:  }

Der Punkt ist: man kann erst auf die Werte zugreifen, wenn das DataBinding schon passiert ist. Über den Umweg des Dictionaries kann man das Ganze nun speichern und per Index aufrufen.

Ziemlich hässlich, oder?

Habe ich mir auch gedacht, bin über meinen Schatten gesprungen und habe mir mal die Lösung von Phil Haack angeschaut: Code Based Repeater for ASP.NET MVC. Beachtenswert ist hier nicht sein erster Ansatz, sondern die am Ende gepostete Lösung.

Nun kann man sich auf den Standpunkt stellen, C#-Code bzw. "Scripts" haben in - sozusagen - der View der View, also dem Markup-Teil der Controls, nichts zu suchen. Je länger und intensiver ich allerdings mit ASP.NET MVC gearbeitet habe, desto lockerer habe ich das gesehen. Inzwischen sehe ich es ganz pragmatisch - es hat alles seine Vor- und Nachteile.

ASP.NET-Controls mit spitzen Klammern sind intuitiver zu verwenden, insbesondere durch Templating und IntelliSense. "Native C#-Controls", so nenne ich sie mal, haben dafür den Vorteil der Typsicherheit - etwas, was man mit Ausdrücken wie Eval() nicht bekommt.

Dort wo es möglich ist, verwende ich weiterhin klassische Controls, dort wo es aber Arbeit spart oder wie in diesem Fall schlicht praktikabler ist, gehe ich den anderen Weg.

Inzwischen verwende ich eine abgeänderte Version von Phils Repeater:

   1:  public class RepeaterItem
   2:  {
   3:      public string CssClass { get; set; }
   4:      public int Index { get; set; }
   5:  }
   6:   
   7:  public static void Repeater(this HtmlHelper html, 
   8:      IEnumerable items, string className, string classNameAlt, 
   9:      Action render)
  10:  {
  11:      if (items == null)
  12:          return;
  13:   
  14:      int i = 0;
  15:   
  16:      foreach (var item in items)
  17:      {
  18:          render(item, new RepeaterItem() { Index = i++, 
  19:              CssClass = (i % 2 == 0) ? className : classNameAlt });
  20:      }
  21:   
  22:  }

 

   1:  <table border="0" cellpadding="0" cellspacing="0" class="CssTableList">
   2:      <% Html.Repeater(ViewData.Model.Messages, "CssTableListItems", 
   3:      "CssTableListItemsAlternate", (message, item) => { %>
   4:      <tr class="<%= item.CssClass %>">
   5:          <td><% Html.RenderPartial("Username", new UsernameViewData() 
   6:          { UserID = message.AuthorID }); %>td>
   7:      tr>
   8:      <% }); %>
   9:  </table>

Ich habe den String, der vormals von Phil für die CSS-Klasse vorgesehen gewesen ist, durch ein eigenes Objekt namens RepaterItem ersetzt, was sich beliebig erweitern lässt. Der Index ist z.B. nützlich, wenn man einen Separator rendern will, aber bei einer Spaltendarstellung z.B. alle 4 Elemente nicht (weil dort ein Zeilenumbruch erfolgt).

Nebenbei spart das natürlich auch noch eine Unmenge an Code, der mit einem klassischen Repeater für das AlternatingItem-Template notwendig gewesen ist - im Normalfall tauscht man aber ja tatsächlich nur die CSS-Klasse für die TableRow aus, der Inhalt ist in 99% der Fälle identisch.



« Zurück  |  Weiter »