Code Based Repeater for ASP.NET MVC

Not long ago, my compadre Scott Hanselman related the following story...

In a recent MVC design meeting someone said something like "we’ll need a Repeater control" and a powerful and very technical boss-type said:

"We’ve got a repeater control, it’s called a foreach loop."

I beg to differ. I think we can do better than a foreach loop. A foreach loop doesn’t help you handle alternating items, for example. My response to this story is, “The foreach loop is not our repeater control. Our repeater control is an iterating extension method with lambdas!”. Because who doesn’t love lambdas?

Not many people realize that within an ASPX template, it’s possible to pass sections of the template into a lambda. Here, let me show you the end result of using my Repeater<T> helper method. It’s an extension method of the HtmlHelper class in ASP.NET MVC.

<table>
  <% Html.Repeater<Hobby>("Hobbies", hobby => { %>
  <tr class="row">
    <td><%= hobby.Title %></td>
  </tr>
  <% }, hobbyAlt => { %>
  <tr class="alt-row">
    <td><%= hobbyAlt.Title %></td>
    </tr>
  <% }); %>
</table>

This renders a table with alternating rows. The Repeater method takes in two lambdas, one which represents the item template, and another that represents the alternating item template.

This particular overload of the Repeater method takes in a key to the ViewData dictionary and casts that to an IEnumerable<T>. In this case, it tries to cast ViewData["Hobbies"] to IEnumerable<Hobby>. I’ve included overloads that allow you to explicitly specify the items to repeat over.

This isn't very remarkable when you think a bout it. What the above template code translates to is the following (roughly speaking)...

Response.Write("<table>");

Html.Repeater<Hobby>("Hobbies", hobby => {
    Response.Write("  <tr class=\"row\">");
    Response.Write("    <td>");
    Response.Write(hobby.Title);
    Response.Write("    </td>");
    Response.Write("  </tr>");
  }, hobbyAlt => { 
    Response.Write("  <tr class=\"alt-row\">");
    Response.Write("    <td>");
    Response.Write(hobbyAlt.Title);
    Response.Write("    </td>");
    Response.Write("  </tr>");
  });

Response.Write("</table>");

The code for the Repeater method is simple, short, and sweet.

public static void Repeater<T>(this HtmlHelper html
  , IEnumerable<T> items
  , Action<T> render
  , Action<T> renderAlt)
{
  if (items == null)
    return;

  int i = 0;
  items.ForEach(item => {
    if(i++ % 2 == 0 ) 
      render(item);
    else
      renderAlt(item); 
  });
}

public static void Repeater<T>(this HtmlHelper html
  , Action<T> render
  , Action<T> renderAlt)
{
  var items = html.ViewContext.ViewData as IEnumerable<T>;
  html.Repeater(items, render, renderAlt);
}

public static void Repeater<T>(this HtmlHelper html
  , string viewDataKey
  , Action<T> render
  , Action<T> renderAlt)
{
  var items = html.ViewContext.ViewData as IEnumerable<T>;
  var viewData = html.ViewContext.ViewData as IDictionary<string,object>;
  if (viewData != null)
  {
    items = viewData[viewDataKey] as IEnumerable<T>;
  }
  else
  {
    items = new ViewData(viewData)[viewDataKey] as IEnumerable<T>;
  }
  html.Repeater(items, render, renderAlt);
}

Some of the ViewData machinations you see here is due to the fact that ViewData might be a dictionary, or it might be an unknown type, in which case we perform the equivalent of a DataBinder.Eval call on it using the supplied view data key.

It turns out that the regular <asp:Repeater /> control works just fine with ASP.NET MVC, so there’s no need for such an ugly method call. I just thought it was fun to try out and provides an alternative approach that doesn’t require databinding.

UPDATE: I wanted to end this post here, but my compadre and others took exception to my implementation. Read on to see my improvement...

As astute readers of my blog noted, the example I used forces me to repeat a lot of template code in the alternative item case. The point of this post was on how to mimic the repeater, not in building a better one. Maybe you want to have a completely different layout in the alternate item case. I was going to build a another one that relied only on one template, but I figured I would leave that to the reader. But noooo, you had to complain. ;)

So the following is an example of a repeater method that follows the most common pattern in an alternating repeater. In this common case, you generally want to simply change the CSS class and nothing else. So with these overloads, you specify two CSS classes - one for items and one for alternating items. Here’s an example of usage.

<table>
  <% Html.Repeater<Hobby>("Hobbies", "row", "row-alt", (hobby, css) => { %>
  <tr class="<%= css %>">
    <td><%= hobby.Title%></td>
  </tr>
  <% }); %>
</table>

And here’s the source for the extra overloads. Note that I refactored the code for getting the enumerable from the ViewData into its own method.

public static void Repeater<T>(this HtmlHelper html
  , IEnumerable<T> items
  , string className
  , string classNameAlt
  , Action<T, string> render)
{
  if (items == null)
    return;

  int i = 0;
  items.ForEach(item =>
  {
    render(item, (i++ % 2 == 0) ? className: classNameAlt
  });
}

public static void Repeater<T>(this HtmlHelper html
  , string viewDataKey
  , string cssClass
  , string altCssClass
  , Action<T, string> render)
{
  var items = GetViewDataAsEnumerable<T>(html, viewDataKey);

  int i = 0;
  items.ForEach(item =>
  {
    render(item, (i++ % 2 == 0) ? cssClass : altCssClass);
  });
}

static IEnumerable<T> GetViewDataAsEnumerable<T>(HtmlHelper html, string viewDataKey)
{
  var items = html.ViewContext.ViewData as IEnumerable<T>;
  var viewData = html.ViewContext.ViewData as IDictionary<string, object>;
  if (viewData != null)
  {
    items = viewData[viewDataKey] as IEnumerable<T>;
  }
  else
  {
    items = new ViewData(viewData)[viewDataKey] 
      as IEnumerable<T>;
  }
  return items;
}

Hopefully that gets some people off my back now. ;)

Technorati Tags: ,
[ad] Free Bug Tracking & Project Management Software Axosoft’s OnTime 2007 allows software development teams to collaborate on software projects by tracking everything from defects to enhancements to helpdesk incidents in one easy-to-use database driven by an intuitive Windows, Web or VS.NET Integrated UI. Get a Free Single-User License ($200 Value!)

What others have said

Requesting Gravatar... Scott Watermasysk May 03, 2008 10:04 PM
# re: Code Based Repeater for ASP.NET MVC
Or just use NVelocity which has really nice support for alternating rows (and much more) built in.

http://www.castleproject.org/others/nvelocity/improvements.html (see fancy foreach loops).

I have not used much NVelocity yet for MVC, but we use it through out Graffiti and it is a joy to use (especially if you hate the constant context jumping required for aspx's.
Requesting Gravatar... Scott Hanselman May 03, 2008 10:06 PM
# re: Code Based Repeater for ASP.NET MVC
I respectfully disagree. ;) IMHO, it's widely agreed that using server-generaed alternate CSS classes for alternating rows is a sub-optimial thing. In my opinion, the View is the view, sure, but the *skinning* of the view should be as much on the client/css as possible. Alternating rows are a client-side presentation detail that the server shouldn't be sweating.

I'd prefer doing it like this in jquery, without any trouble on the server-side:

$(document).ready(function() {
$('table.striped tbody tr:not([th]):odd').addClass('odd');
$('table.striped tbody tr:not([th]):even').addClass('even');
});

Additionally, that server-side repeater doesn't handle triple striping, grouped striping, alternating triplets, etc.

Just my 2 bytes.

See this page for more details.

Requesting Gravatar... VIjay Santhanam May 03, 2008 10:08 PM
# re: Code Based Repeater for ASP.NET MVC
Wikid! That's cool!
Requesting Gravatar... Peli May 03, 2008 10:10 PM
# re: Code Based Repeater for ASP.NET MVC
2 more bytes: instead of putting logic to alternate the css class, you force the user to duplicate the entire row generation. that's a lot of duplication.
Requesting Gravatar... Haacked May 03, 2008 10:14 PM
# re: Code Based Repeater for ASP.NET MVC
@peli, @hanselman well this is a simple example. Suppose I wanted a totally different layout for alternating items.

I was going to write yet another overload that makes doing the alternate CSS class without repeating the template simpler too, but figured I'd leave that as an exercise to the reader.
Requesting Gravatar... Jonathan Carter May 03, 2008 10:27 PM
# re: Code Based Repeater for ASP.NET MVC
The thinking behind this is pretty cool. While that dual-lambda is a little cryptic (subjectively), it does illustrate how creative you could get with MVC.
Requesting Gravatar... Haacked May 03, 2008 10:53 PM
# re: Code Based Repeater for ASP.NET MVC
Ok, I updated my post to show yet another approach.
Requesting Gravatar... SAprelov May 04, 2008 6:22 AM
# re: Code Based Repeater for ASP.NET MVC
I guess "view" must be in "view":)
jQuery rules:

CSS:
table tr:nth-child(odd)
{
background-color: magenta;
}

Code:
$("tr:nth-child(odd)").addClass("odd");
Requesting Gravatar... Steve May 04, 2008 9:15 AM
# re: Code Based Repeater for ASP.NET MVC
We have a repeater...it's called a repeater :)

I use the repeater in my ms mvc apps with jQuery sorting and paging.

Requesting Gravatar... Tuna Toksoz May 04, 2008 9:24 AM
# re: Code Based Repeater for ASP.NET MVC
Why don't we put this into a ComponentController? Isn't it better?
Requesting Gravatar... Steve May 04, 2008 9:27 AM
# re: Code Based Repeater for ASP.NET MVC
Here is sample of what I am talking about:

http://blogger.forgottenskies.com/?p=89

Granted, to have a helper is a great idea, I'm just showing that we 'have a repeater' and it support templating of course - I just show the itemtemplate.

Requesting Gravatar... Steve May 04, 2008 9:35 AM
# re: Code Based Repeater for ASP.NET MVC
oh, you did come back and address what I was talking about :)

sorry about that - on the bright side, maybe that quick example I cooked up will help someone else - lol

All I can say is this ASP.NET MVC has been incredible to work with, especially using jQuery. I am using the ajax form, validation, other aspects and the best part: full control over everything :)

I've bounced back and forth over using a foreach or a repeater. The repeater handles null bindings better, I don't like needing to do < % if(... == null) { % > etc... that is ugly stuff.
Requesting Gravatar... Scott May 04, 2008 3:13 PM
# re: Code Based Repeater for ASP.NET MVC
For the second example I would probably just wrap it in a for loop and output my rows like so.
<tr class="<%=(rowNum%2==0)?"evenCssRow":"oddCssRow";%>">

I think if you keep refactoring the repeater helper to account for all cases, you'll end up writing the repeater control. ;)
Requesting Gravatar... Mischa Kroon May 04, 2008 3:38 PM
# re: Code Based Repeater for ASP.NET MVC
I don't really like this way of doing things.

Scott's example seems both cleaner, takes less code and is less complex.

ForEach + Jquery / other framework CSS insertions for the win :)
Requesting Gravatar... Michael Teper May 04, 2008 4:36 PM
# re: Code Based Repeater for ASP.NET MVC
I like the jQuery examples simply because they don't involve the hideous spaghetti code that is quickly becoming the distinguishing feature of all the MVC examples I am seeing, and is touted as a good thing to boot. Its not. Whatever its shortcomings, ASP.NET gave us a way to code cleanly (compared to classic ASP, for example), so lets not throw the baby out with the bathwater.
Requesting Gravatar... Scott Hanselman May 04, 2008 5:47 PM
# re: Code Based Repeater for ASP.NET MVC
Ya, if a repeater works maybe use that for nulls and JQuery for the rest.
Requesting Gravatar... Chris Chandler May 04, 2008 6:15 PM
# re: Code Based Repeater for ASP.NET MVC
Thanks for the update, reading your post, I was hoping you would refine it to use a css switch, kudos.

Must say, since mvc is all about separation of concerns, alternating rows is a style issue. Since I'm the ultimate jQuery fanboy anyway, jQuery FTW.

Lambdas are nice here. But why all the ceremony about a loop? Seems like an awful lot of moving pieces just to iterate some tr's?
Requesting Gravatar... Lou D May 04, 2008 8:51 PM
# re: Code Based Repeater for ASP.NET MVC
Cool trick! Quite a picky audience. Sounds like it could be an interesting way to get stuff other than html-encoded text in a link.

public static void ActionLink<T>(
this HtmlHelper html,
Expression<Action<T>> action,
Action linkContents) where T:Controller
{
string url = LinkBuilder.BuildUrlFromExpression(
html.ViewContext, action);

html.ViewContext.HttpContext.Response.Write(
string.Format("<a href=\"{0}\">", url));

linkContents();

html.ViewContext.HttpContext.Response.Write("");
}

in aspx

<% var x = "foo"; %>

<% Html.ActionLink<HomeController>(
home => home.About(),
() => {%><span class="<%=x%>">hello</span><%});
%>



Requesting Gravatar... Rob Conery May 04, 2008 10:03 PM
# re: Code Based Repeater for ASP.NET MVC
"We’ve got a repeater control, it’s called a foreach loop."

That was me pinhead :).

Dig the lambda/template bits. You've got my mad scientist juices flowing...
Requesting Gravatar... James Gregory May 05, 2008 3:33 AM
# re: Code Based Repeater for ASP.NET MVC
Overkill? Obtuse certainly.

I agree with Scott H that it's probably all unnecessary, leave it to javascript; however, if you really want to do it, why is the mvc implementation better than simply using an if statement?

Why is this better than:

<% foreach ... {
if IsAlt { %>
...
<% } else { %>
...
<% }
} %>

MVC seems to be forgetting to KISS...
Requesting Gravatar... Joe Brinkman May 05, 2008 4:32 AM
# re: Code Based Repeater for ASP.NET MVC
I love it. Something as simple as a reapeater and we have 30 different methods to achieve the same thing. Definitely what I want my devs spending their time working on. I guess spending time on solving actual business problems is so yesterday, now devs will get to spend time learning how to rebuild basic server controls.
Requesting Gravatar... Chris Chandler May 05, 2008 6:10 AM
# re: Code Based Repeater for ASP.NET MVC
@joe - Spending your time monkeying with someone else's contrived Html, is so yesterday. Taking control and making it look exact with your designer, so now.
Requesting Gravatar... Haacked May 05, 2008 8:17 AM
# re: Code Based Repeater for ASP.NET MVC
@Joe because every problem in computer science and software engineering has been solved, so no point in trying to experiment with different ways of doing anything, right? ;)

It's a matter of choice and preference. It's why you guys (DNN) stuck with VB, while many devs stuck with C#. We have 30 different languages to solve things, but that's a good thing, right?
Requesting Gravatar... Sameer May 05, 2008 9:03 AM
# re: Code Based Repeater for ASP.NET MVC
Do people you work with say, "watch what you say, it might end up on Phil's blog"?

:D

guys... watch your back (just kidding)
Requesting Gravatar... Jason Simone May 05, 2008 12:13 PM
# re: Code Based Repeater for ASP.NET MVC
Scott,

Why would you want your view to be dependent on client-side script - especially when it doesn't need to be? You say it's widely agreed that this is best practice so I'm really curious to see why. To me that defeats the whole idea of keeping your View nice and straightforward.

Working for government, our paranoid techs distribute a lot of PCs with JavaScript turned off so it is of particular concern to me to choose where I use it wisely. I would never mix the initial presentation state of my page with client-script if I can easily avoid it. But perhaps I also just have a fear of mixing too much into client-script because I still find it much more difficult to organize than views in a nice MVC site.
Requesting Gravatar... Stephen May 05, 2008 12:32 PM
# re: Code Based Repeater for ASP.NET MVC
I can't quite believe it was recommended to use jquery to style the page instead..

Thats just.. really scary
Requesting Gravatar... Stephen May 05, 2008 12:45 PM
# re: Code Based Repeater for ASP.NET MVC
I'm wondering, while I've started to get more love for the helper method way of generating html..

I wonder if theres any thought to upgrade the control system for asp.net, something more like wpf binding.. I know webforms provides a control system, and that can even be used within the mvc, but personally i don't think they reflect the mvc well, and I fear the helper methods are really going backwards.. in terms of:

Monolithic helpers for various different amounts of overloads you require..

Compared to the controls system of webforms, where you compose an instance that will eventually render.. not the mension that data the forms forms controls actually look like what you are doing vs tons of <% %> <%= %> yadda yaddas..

As someone who spent a lot of time with ASP, it seems all too familiar, I'm wondering if you guys who aim to retire controls in favour of tons of methods have ever used asp? :P
Requesting Gravatar... Haacked May 05, 2008 2:01 PM
# re: Code Based Repeater for ASP.NET MVC
@Stephen we do have intentions to look into MVC specific declarative controls, but not necessarily for MVC 1.0. I think we want to get the core framework done first and make sure code based approaches work first.
Requesting Gravatar... Scott Hanselman May 05, 2008 3:45 PM
# re: Code Based Repeater for ASP.NET MVC
Seems to me that the current web would be crippled with Javascript disabled. I thought that the great Javascript/Cookies scare of 1998 was over. ;)
Requesting Gravatar... mario May 05, 2008 5:30 PM
# re: Code Based Repeater for ASP.NET MVC
we're back to spaghetti ASP except now in MVC. the trivial asp.net mvc examples are barely tolerable going in and out of <% %>. have you ever considered adding first-tier support for templates?

<mvc:repeater-template id="MyTable">
<layout>
<table>
<tr><th>Name</th></tr>
<mvc:render-item />
<table>
</layout>

<item>
<mvc:render-template id="even" when="row => row.IsEven" />
<mvc:render-template id="odd" when="row => row.IsOdd" />
</item>

<empty>
</empty>
</mvc:repeater-template>

<mvc:template id="odd">
<tr class="odd"><td><%# model.Name %></td></tr>
</mvc:template>

<mvc:template id="even">
<tr class="odd"><td><%# model.Name %></td></tr>
</mvc:template>


<html>
<body>
<%= Mvc.InvokeTemplate("myTable", ViewData["model"]) %>
</body>
</html>



Requesting Gravatar... Jimmy Bogard May 05, 2008 5:41 PM
# re: Code Based Repeater for ASP.NET MVC
I hate to say it, but all the C# noise is making me want VBScript back. All the <% %> <%= %> () => {} stuff is pretty noisy. Clever, but noisy.
Requesting Gravatar... Steven Harman May 05, 2008 5:48 PM
# re: Code Based Repeater for ASP.NET MVC
@mario, Don't forget that much of the spaghetti from the old ASP days was about mixing many concerns together into a big, messy, opaque ball of... well of spaghetti. The idea of adapting a pattern like MVC for the web is to help developers fall into the pit of success by making doing the right thing, keeping those concerns separated and in their own space, is the easy thing to do.

So while I'd agree with several other commenters that this might not be the best use of lambdas and is probably too much ceremony for a simple looping construct, its still all presentation logic. And as such, keeping it in the View is perfectly valid.

Btw... I'm on board with the idea of having a strong templating engine. Have you looked at how rhtml, NVelocity, or Brail do things?
Requesting Gravatar... Joe Brinkman May 05, 2008 9:29 PM
# re: Code Based Repeater for ASP.NET MVC
Phil - My biggest pain point is that in many ways the MVC framework seems to be a huge step backward. One of the greatest advances that I have seen in software development over the last 20 years has been the realization of some of the benefits of code re-use. More specifically, software components and the binary packaging which allows for them to be easily distributed and re-used has resulted in a huge productivity boosts for the development community. This is definitely the case in the desktop world, and to a large degree, it has been that way in the Asp.Net world as well.

In many ways this feels like the old debate between the VB and C++ crowds... C++ Guy - Look how much control we have over all aspects of our software. We can build anything we want to. VB Guy - Yeah but look at how many real world business problems we have solved while you are still trying to build one simple dialog box.
Requesting Gravatar... mario May 06, 2008 1:04 AM
# re: Code Based Repeater for ASP.NET MVC
@steven if a DESIGNER cannot figure out the presentation logic, even if it is acceptable to have it there, then there's too much logic in the UI. nvelocity and brail have for loops, thus i would rather stick with C#.

a neat approach would be templates + CSS3 selectors on the enumerator. from my previous example, the mvc:repeater would use the odd row template for the item like this:

<mvc:render-template id="odd" when="nth-child(odd)" />
Requesting Gravatar... Janko May 06, 2008 3:11 AM
# re: Code Based Repeater for ASP.NET MVC
I disagree. You can create a server code and strip html markup using jQuery in a line of code.
Requesting Gravatar... Richard Bushnell May 06, 2008 3:40 AM
# re: Code Based Repeater for ASP.NET MVC
While I do like the control afforded by the demo, I have to agree with Joe. The comments to this post demonstrate the point that we are spending too much time worrying about more than one way of doing things.

In Python, the community really tries to just have one way to do something. In Rails, DHH tells everyone to make their own framework if they want to do it more than one way. We seem to have 20 ways just to do rendering. Ever heard of the anti-pattern, "Design by committee?"

At the same time, I'm struggling to get ASP.Net to do what I want because there is so much stuff missing or badly designed. I fail to see why we don't learn by experience and have great hackers like you guys spend time getting that right.

How can we expect anyone to take this up anyway? You guys might be hacks, but I am still trying to train normal developers how to use generics. Lambdas are way beyond them at the moment. And now we're asking them to start learning JQuery, just to do something which they could previously do using a DataGrid?

In any case, the Rails way is way simpler. They have a class just for cycling through CSS names on each iteration. What about trying something similar to that?
Requesting Gravatar... Stephen May 06, 2008 12:17 PM
# re: Code Based Repeater for ASP.NET MVC
Hi Scott,
While js is much more common these days, that shouldn't mean you change menial design choices into using js.. the jquery example just really looked like it was there for the sake of it..

I'm sure what you are saying is you love the expressive context something like jquery adds.. but I think the inspiration should be to try and make the server side as expressive as css.. the functional side I think we're getting there with c# anyway.. lambdas fill a big gap in, in that respect.
Requesting Gravatar... Louis DeJardin May 06, 2008 3:57 PM
# re: Code Based Repeater for ASP.NET MVC
@mario, @bogard: agreed a hundred percent that the road to hell is paved with <%x { %><d t="<%=h.e<y>(y=>y.n)%>" /> <% } %>. Especially with the default editor constantly attempting to realign any order you try to bring to the file.

But how can you avoid that if you're in the aspx view engine? Myself, I've a fan of nvelocity - I'd love to see a form of '#' and '$' escaping in aspx in addition to <% %> and <%= %>.

http://www.castleproject.org/others/nvelocity/improvements.html#fancyloops


#foreach($hobby in $Hobbies)
#beforeall
<table>
#odd
<tr class="row">
#even
<tr class="row-alt">
#each
<td>$hobby.Title</td>
#after
</tr>
#afterall
</table>
#nodata
<div>You need some hobbies</div>
#end


@bushnell agreed about the value of simplicity and providing a clear best practice. Hopefully the net result of this conversation is less about listing 20 different techniques as it is about which is the simplest effective approach.

Requesting Gravatar... Peter May 06, 2008 7:26 PM
# re: Code Based Repeater for ASP.NET MVC
Arrr, all these <%..%> are sooo ugly... It's noise, they get in the way. That NVelocity example is not much prettier either. We need to think about it backwards. Forget what the current design view engine can or cannot do and let's start from scratch. What would we like?
Requesting Gravatar... Lou D May 06, 2008 8:25 PM
# re: Code Based Repeater for ASP.NET MVC
What if the few things you needed in a view language fit into the html seamlessly?

(crossing fingers about formatting)

<var i="0"/>
<var css="new string[] {'row', 'row-alt'}"/>
<table>
<for each="var hobby in Hobbies" i="i+1">
<tr class="$css[i%2];">
<td>$i;</td>
<td>$hobby.Title;</td>
<td>$Html.Function("arg","arg");</td>
</tr>
</for>
</table>
Requesting Gravatar... Joey May 07, 2008 4:05 AM
# re: Code Based Repeater for ASP.NET MVC
@ScottHa

Alternating rows are a client-side presentation detail that the server shouldn't be sweating.


I love JavaScript and use jQuery as my toolkit of choice ... but think 100+ rows your code has to parse and add a simple CSS.

Compiled FTW.

Not a knock on jquery ... but I've seen it abused and people stuff their "document.ready" with too much logic that hangs the browser. Be pragmatic.
Requesting Gravatar... Stephen May 07, 2008 5:23 AM
# re: Code Based Repeater for ASP.NET MVC
I'm not sure what any of these alternatives provide other than a different way to make spaghetti..

I don't see whats so wrong with server side controls.. wpf and xaml work great.

asp.net just needs some cleaner binding.. generics aware if possible.

Not that I like the current server side controls, they are far too tied to the webforms paradigm for me to feel comfortable with..

What do you have to say?

(will show your gravatar)
Please add 7 and 5 and type the answer here: