Site Meter
 
 

Simplify Iteration in Your MVC Views

This is just a simple little tip, but it may come in handy if you frequently iterate over collections in your ASP.NET MVC views.

How often do your ASP.NET MVC views need to iterate over a collection to render some HTML for each item? For example, if your model is a collection of Person objects, and you want to render a table of them, you might write view code as follows:
image

A few bits of this code make me uncomfortable:

  • In order to render “even” and “odd” and “last” CSS classes, I used a series of inline ternary operators, which obscures the readability of the HTML.
  • Just so I could render the index number of each item, I used a for loop instead of the syntactically tidier foreach loop.
  • To work out which item was the last one (so I could render a special “last” CSS class), I used ToList() to evaluate the whole collection up-front and store it in a temporary variable, then manually compared each item’s index with the index of the final item, remembering to subtract one because the indexes are zero-based. Fiddly manual work.

So, how about the following alternative syntax?

image

Admittedly it’s not radically different, but I think the simplification is worth it. No more kludgy temporary variable, no more manually working out the position of the final item, and no more ugly series of inline ternary operators.

The .ToDescriptiveList() helper

To make the .ToDescriptiveList() helper available in your own application, first download the helper classes and add them to your solution. If you’re putting them into a namespace not normally referenced by your views, you’ll need to import their namespace by adding the following to your web.config file:

<system.web>
    <pages>
        <namespaces>
            <!-- leave rest as-is - just add the following: -->
            <add namespace="DescriptiveListHelper"/>
        </namespaces>
    </pages>
</system.web>

Now, if you add .ToDescriptiveList() on to the end of any IEnumerable in a view, you’ll get an enumerable of objects with the following methods/properties:

Method/Property Type Description
Item Your model type The item in your underlying enumerable
Index int The zero-based index of the item in your underlying enumerable
Description ItemDescription flags Bit flags enum with one or more of the following values: First, Last, Interior, Odd, Even
Is(ItemDescription) bool True if the item matches the specified description; false otherwise. Example:

<% if (item.Is(ItemDescription.Last)) { %> … <% } %>
CssClass() string Generates a CSS class name based on the Description property. This makes it easy to style first, last, or alternate rows differently without manually constructing a CSS class name using <% if %> or ternary operators in your view code. If you wish, you can pass as a parameter to specify a CSS class name prefix.

That’s all there is to it.

A note about NVelocity and its Fancy Loops syntax

I made this helper because I got jealous of NVelocity’s fancy loops syntax which at first glance appears to make iteration in views so neat and simple. So, I spiked a simple way to use similar syntax in ASPX views, but having done it, realised that there are many scenarios where this syntax is inadequate or forces you to repeat yourself (e.g., in tables, when the row markup varies by odd/even, but a certain column varies by first/last or index). That’s why, in the end, I settled on the more basic but more flexible option of just annotating a collection with first/last/odd/even/index information.

A note about jQuery and/or CSS 3

With jQuery or with CSS 3, you can use pseudoclasses such as :first, :last, :even, and :o dd to style items by their position in a collection of DOM nodes. If you have the luxury of being able to rely on these, great, otherwise you might need a server-side solution such as the one I’ve presented here.

27 Responses to Simplify Iteration in Your MVC Views

  1. Great post. This rings a bell for anyone doing web devleopment. I don’t know how many times I’ve used the ternary operator approach for applying CSS styles.

    We have moved on to a more elegant solution via jQuery, which does not require creating layout methods in your model:

    $(‘ul > li:nth-child(even)’).addClass(‘alt’);
    $(‘ul > li:first’).addClass(‘first’);
    $(‘ul > li:last’).addClass(‘last’);

    We typically do this site-wide, for tables, lists and a bunch of other common “list” containers. Should tide us over until CSS3 comes along :)

  2. @Derek: I would not call using script for styling elegant ;) I’ve seen people use jQuery instead of perfectly ubiquitous CSS on many occasions. But yeah, until CSS3 is everywhere, I guess we’ll have to resort to that sort of thing for the non-universally-supported selectors. It does make the markup lighter than doing it server-side…

  3. Todd

    Or you can use the Spark View Engine (http://http://www.sparkviewengine.com) which builds in support for this

  4. FWIW, I tend to use a logical variable odd/even tracking. Declared up front:

    class=”<%= (OddEven = !OddEven) ? “evenClass” : “oddClass” %>”

    You still end up with a variable but at least the whole thing is wrapped up in a single expression + plus the var declaration.

  5. Crap = comments ate my tags, WTF? No code posting on this blog :-}

  6. Steve

    @Rick – sorry about that! I repaired your original comment based on the square-bracket version.

    @Todd – sorry, your markup was wiped out too. If you want to try re-posting, please replace angle brackets with &lt; and &gt;. WordPress’s approach to HTML encoding (obliterate markup at source) really sucks, so much that I’m probably going to switch to a different blog engine altogether.

  7. Pingback: Reflective Perspective - Chris Alcock » The Morning Brew #423

  8. Ira

    I think it is funny that all these other view engines (Spark, NVelocity + NHAML) have this built in, and with the WebForms view engine we are stuck coding it up every time. It may be about time for me to look really hard at different view engines. Thanks for the great write up Steve!

  9. Todd

    We’ll try again. No preview, so if this fails, I’m done trying :-P Here’s your example in Spark:

    <table>
    <thead>
    <th>Rank</th>
    <th>Name</th>
    <th>Age</th>
    </thead>
    <var styles=”new[] { ‘myitem-odd’, ‘myitem-even’}”>
    <tr each=”var person in ViewData.Model”
    class=”myitem-last?{personIsLast} ${styles[personIndex % 2]}?{!personIsLast}”>
    <td>${personIndex + 1}</td>
    <td>${person.Name}</td>
    <td>${person.Age}</td>
    </tr>
    </var>
    </table>

  10. Craig

    “We have moved on to a more elegant solution via jQuery, which does not require creating layout methods in your model”

    Your more elegant jQuery based solution doesn’t degrade gracefully for users with javascript turned off though, which makes it less elegant.

  11. Craig, I’m sorry but your argument REALLY annoys me. The amount of people who have JavaScript turned off is so tiny it’s not worth worrying about.

    5 years ago I would have worried about JavaScript been enabled/disabled, but these days it’s really just not worth worrying about.

  12. I have to side with Craig. For the government work I do we must be very strict making sure everything works for everyone, might means we can’t go a JavaScript required route.

    I’d love to rely on pure jQuery, it’s quicker, slicker and more fun to develop in than tradtional server-side methods. But it ain’t that simple…

    However saying that for pure presentational layer work, it doesn’t matter if the lack of JavaScript causes the page to not look as pretty – from a Disability Discrimination Act (DDA) point of view :)

  13. Richard

    You should really be disposing of the IEnumerator instance in your ToDescriptiveList method – a “using” block will work.

    Also, if you want to add argument checking, the iterator method should be separate:

    public static IEnumerable<DescriptiveListItem> ToDescriptiveList(this IEnumerable items)
    {
    if (null == items) throw new ArgumentNullException(“items”);
    return ToDescriptiveListIterator(items);
    }

    private static IEnumerable<DescriptiveListItem> ToDescriptiveListIterator(IEnumerable items)
    {

    }

  14. Steve

    @Richard – agreed about disposing the IEnumerator – that was an oversight. Not so sure why you think the guard clause needs to go into a separate method though.

  15. Richard

    @Steve – If you put the guard clause in the iterator method, the exception will be thrown on the first call to MoveNext; if you put it in a separate method, it will be thrown when you call that method.

    It doesn’t make a huge difference in your example, because the method call is within the foreach loop, but in a more complex example, it could cause confusion.

    IEnumerable[Product] model = null;

    var list = model.ToDescriptiveList();
    // With a separate method, the exception is thrown here.

    foreach (var item in list)
    // Without, the exception is thrown here.
    {
    }

  16. Joe Chung

    Avoid null reference errors by using Enumerable.Empty instead:

    IEnumerable[Product] model = Enumerable.Empty[Product]();

  17. Richard

    @Joe – That’s fine *if* you’re in complete control of the calling code.

    If you’re writing a method which could be called by other developers, it’s always best to assert your requirements via argument exceptions, Debug.Assert, or code contracts.

    And “by other developers” also includes you if it’s more that five minutes since you wrote the method!

  18. Steve Gibbings

    @Phillip Haydon It might annoy you but we write systems based on our, our client’s or user audience’s needs not the whether you like it or not. If that were true I’d be dropping IE6 support but the thing just won’t die.

    If you side with the whole progressive enhancement and accessibility thang then even if it isn’t necessary to support disabled javascript it’s still not wholly following the pattern. Many screen reader users switch off javascript to avoid Ajax style content updates which are not obvious via a screen reader. That said as Peter said you may be able to be pragmatic and say does it matter if they don’t get the zebra stripes? That’s the way I’d go but I’d still do it via CSS in this case.

    At the end of they day it’s about dev choices and customer needs but it really shouldn’t cause you to be “REALLY annoyed”. Code-rage?

  19. Pingback: Servefault.com

  20. Very nice API design, thanks!

  21. BTW – what license are you releasing the helper classes under? Without knowing, using them as-is in any project is a potential problem.

    The MIT license seems appropriate.

  22. Steve

    @orip – Fine – consider it MIT-licensed!

  23. ms440

    Thanks, Steve! While I do it for non-profit, the licensing is still pretty strict here. MIT license is fine.

    I was using jQuery solution, but they want no-script scenario (i.e. server side solution) also :(

  24. Awesome Steve, thanks!

  25. Have you ever thought about creating an ebook or guest authoring on other websites? I have a blog based upon on the same ideas you discuss and would love to have you share some stories/information. I know my audience would enjoy your work. If you’re even remotely interested, feel free to shoot me an e-mail.

  26. I was recommended this website by my cousin. I am not sure whether this post is written by him as no one else know such detailed about my trouble. You’re amazing! Thanks! I’ll surely come back and look for new stories! See you