An Arbitrary Cycle Method For ASP.NET MVC

In his Practical Review of ASP.NET MVC, Josh Charles provides a helpful review of ASP.NET MVC from a Rails developer’s perspective. It seemed fair and balanced, and the end result is that there’s room for improvement, which we’re taking to heart.

However, that’s not the part that caught my attention. He mentioned that he wrote a cycle method but couldn’t write it as an extension method to HtmlHelper.

this was an instance method that would take two strings and return the one that it didn’t return the last time it was called. In my templates, I used this to change the classes for each row of data, to give them different background colors. I considered writing an extension method to the Html object used for other Html operations in the view page, but this method specifically required the use of an additional private variable, so that would not work.

If you don’t mind cheating a bit, there is a way to write this as an extension method. And while we’re doing that, why stop at only two strings? Why not take an indefinite number? :)

public static string Cycle(this HtmlHelper html, params string[] strings) {
    var context = html.ViewContext.HttpContext;
    int index = Convert.ToInt32(context.Items["cycle_index"]);

    string returnValue = strings[index % strings.Length];

    html.ViewContext.HttpContext.Items["cycle_index"] = ++index;
    return returnValue;
}

Perhaps allowing an indefinite number of strings is overkill (who ever heard of a table with tri-color highlighting?) but I thought it was fun to do regardless. Here’s an example of usage with three different CSS styles:

<style>
    .first {background-color: #ddd;}
    .second {background-color: khaki;}
    .third {background-color: #fdd;}
</style>

<table>
<% for (int i = 0; i < 5; i++) { %>
    <tr class="<%= Html.Cycle("first", "second", "third") %>">
        <td>Stuff</td>
    </tr>
<% } %>
</table>

And the output...

Stuff
Stuff
Stuff
Stuff
Stuff

With this, go forth and spread tri-color highlighted tables all over the web. Or if you’re really crazy player, go with four color highlighting!

Technorati Tags: ,,

What others have said

Requesting Gravatar... Javier Lozano Aug 07, 2008 1:52 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
Why are you considering your approach as cheating?
Requesting Gravatar... Francois Ward Aug 07, 2008 3:26 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
The "cheat" i think is the usage of a Context variable to keep track of the "state" of the loop operation.

My first reaction, too, was "Where's the cheat", since usually when people refer to cheating, they're talking about Reflection =P
Requesting Gravatar... Timothy Khouri Aug 07, 2008 3:26 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
That actually looks pretty cool. I think it's going to take a while for anyone outside of the ASP.NET community to realize that C# is a programming language.

I don't mean that as a slam against the guys out there (I used to be one)... but when you live in the world of PHP and the like, transitioning over to .NET takes a while to think oh-so-slightly out of the box.

Basically my advice to Josh would have been... "If your function would work in a Console application displaying your results to the screen, then it will work with ASP.NET MVC."
Requesting Gravatar... haacked Aug 07, 2008 4:54 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
As Francois said, the "cheat" is storing that "private" variable in the HttpContext.Items. That variable value is available later on in the request. But then again, the chances of having a conflict with it is minimal. You could use a GUID for the key rather than "cytle_index"
Requesting Gravatar... Stephen Aug 07, 2008 5:51 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
Wouldn't consider that a cheat.. althought I'm a bad person because I don't feel right putting private objects in shared collections.. always have this feeling that they could get "molested".. but by who right? anyway..

The alternatives, thread static? but then asp.net doesn't guarentee a single thread per context - although I would say it would be a rarity that you see that happen..

Or you could hold a static dictionary of weak references to http contexts and indexes? but the effort for that seems really ott just to "protect" against a really bizzare scenario where code inside your domain could be abstractly malicious to itself..
Requesting Gravatar... Lance Fisher Aug 07, 2008 7:54 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
This is cool, but there are a few problems with the cheat.

1. If you have two cyclers on the page, you cannot control the starting string in cycles after the first cycle. Suppose I want two tables with bands, and the first row of each should get the "odd" class.

2. If you have two cyclers in the same loop, they will increment each other. Suppose I want a row in a table to have alternating classes, and I also want each cell to me marked with the alternating class.

3. You cannot have nested cyclers - they won't work right. This is probably more of an edge case.

Ideally, the context for the cycle would be the loop its operating in, and not the request. I can't really think of a great solution other than passing the index to the Cycle() method, but that complicates the api and excludes foreach loops.
Requesting Gravatar... Damien Guard Aug 07, 2008 7:57 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
The real problem is that this extension method can not be used more than once in a page because it will not re-initialize and if used within another loop will upset the index of the others.

One option would be to provide a key on the method, e.g.

public static string Cycle(this HtmlHelper html, string key, params string[] strings) {
var context = html.ViewContext.HttpContext;
int index = Convert.ToInt32(context.Items[key]);
context.Items[key] = index + 1;
return strings[index % strings.Length];
}

Which still leaves you open to naming conflicts. An alternative is:

public static string Cycle(this HtmlHelper html, params string[] strings) {
string key = string.Format("cycle_index_{0}", strings[0].GetHashCode() ^ strings[strings.Length-1].GetHashCode() ^ strings.Length.GetHashCode());
var context = html.ViewContext.HttpContext;
int index = Convert.ToInt32(context.Items[key]);
context.Items[key] = index + 1;
return strings[index % strings.Length];
}

Which still isn't perfect but at least makes up the key itself however will conflict if the first, last and number of items are the same. The only other alternative would involve going over each item which would be very expensive per-call.

[)amien

Requesting Gravatar... Keith Aug 07, 2008 10:42 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
If you want to support multiple cycles within one page, you can instead create a delegate that will return a new string each time:



public static Func Cycler(this HtmlHelper html, params string[] strings) {
List stringList = new List(strings);
Func cycler = null;
cycler = () =>
{
// Move the first item to the back of the list
string value = stringList[0];
stringList.RemoveAt(0);
stringList.Add(value);
// Modify the generator so that it uses the new list next time
cycler = html.Cycler(stringList.ToArray());

// return the first value this time
return value;
};
return cycler;
}



Of course, this means that you'll have to create an instance outside the loop, but it might be considered less of a "cheat".

Requesting Gravatar... Keith Aug 07, 2008 10:47 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
Doh! That should have been Func<string> where it says Func... Stupid html tag stripping...
Requesting Gravatar... AndrewK Aug 13, 2008 11:04 PM
# re: An Arbitrary Cycle Method For ASP.NET MVC
I agree with Keith. It seems using delegate and closure technique is the most natural solution to the "Cycle" issue.
Here is my variation:

public static Func Cycle(this HtmlHelper htmlHelper, params string[] cssClasses)
{
int l = cssClasses.Length;
int i = l-1;
return () =>
{
i = (i+1) % l;
return cssClasses[i];
};
}

But there is a drawback though. We need to use extra variable in View to get our cycle function.
Requesting Gravatar... Rusty Aug 25, 2008 2:17 AM
# re: An Arbitrary Cycle Method For ASP.NET MVC
cute! tri-color striping... I appreciate the:
1) link to the rail perspective
2) the attention to the community
3) the clever solution to the cycle problem - although I think class swapping for row coloring is better handled by jquery.
4) the entire mvc stack, of course
Requesting Gravatar... George Tsiokos Sep 03, 2008 1:26 AM
# Refactored...
public static string Cycle(this HtmlHelper html, params string[] strings) {
if (null == strings)
throw new ArgumentNullException("strings");
int index = CycleIndex;
CycleIndex = 1 + index;
return strings[index % strings.Length];
}
private static int CycleIndex {
get {
return Convert.ToInt32(html.ViewContext.HttpContext.Items["cycleIndex"]);
}
set {
html.ViewContext.HttpContext.Items["cycleIndex"] = value;
}
}

What do you have to say?

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