When I created the prototype of the MVC Toolkit (the HTML Helpers), there was a method in there that I was particularly fond of: ToFormattedList(). This Extension method would take an IEnumerable (or IEnumerable<T>) and create a simple formatted list out of it. It's since been replaced, but I liked it so much I thought I'd do a quick post about it.
This is a Public, Service, Announcement
There may be better ways to do this, but sometimes it's nice to just use one line of code to create an ordered list or unordered list. Rather than beat this to death, here's the code:
/// <summary> /// Creates a formatted list of items based on the passed in format /// </summary> public static string ToFormattedList(this IEnumerable list, ListType listType) { string outerListFormat = ""; string listFormat = ""; switch (listType) { case ListType.Ordered: outerListFormat = "<ol>{0}</ol>"; listFormat = "<li>{0}</li>"; break; case ListType.Unordered: outerListFormat = "<ul>{0}</ul>"; listFormat = "<li>{0}</li>"; break; case ListType.TableCell: outerListFormat = "{0}"; listFormat = "<td>{0}</td>"; break; default: break; } return string.Format(outerListFormat, ToFormattedList(list, listFormat)); }
/// <summary> /// Creates a formatted list of items based on the passed in format /// </summary> /// <param name="list">The item list</param> /// <param name="format">The single-place format string to use</param> public static string ToFormattedList(IEnumerable list, string format) { StringBuilder sb = new StringBuilder(); foreach (object item in list) { sb.AppendFormat(format, item.ToString()); } return sb.ToString(); }
I'm working with enums so I don't have to pass in strings (although I still can with the overload). My enum is declared like this:
/// <summary> /// Types of HTML Lists /// </summary> public enum ListType { Ordered, Unordered, TableCell }
You can have whatever lists you like here - options, table rows - whatever you need. I'm keeping this simple for the sake of the blog post here.
To use this, Extension method, you first need an Enumerable object. I'll use a string array, but any object will do. The ToString() method will be called on that object so you might want to override it's core methods if you're not using a primitive.
I've created a string array to test this method:
string[] names = new string[] { "larry", "moe", "curly" };
To output this as a list, all I need to do is reference the new "ToFormattedList()" method:
<%=names.ToFormattedList(ListType.Unordered)%>
This will output:
Hope you find this helpful.
I'm having some trouble getting the latest download to run locally. Maybe just IR st00pid or something... Here's what I'm doing:
1. Download and extract.
2. Restore database.
3. Change references to "localhost" in connection strings to "myserver\SQLEXPRESS" because I'm running SQL Server Express.
4. Build and run the app.
What I keep getting - both when trying to hit any page and when running the unit tests - are several "object not defined" errors pointing at code in the Execute method of the Controller class.
Is there a setup step I'm missing somewhere? The download seems to have all of the external DLL references it needs, so I don't think it's a versioning problem with MVC. Any ideas?
You knew this was coming...
I'm guessing you're not using ReSharper to help you refactor your code b/c I see some leftover, unused code in ToFormattedList(this IEnumerable list, ListType listType). Namely, you don't need the first two lines b/c the variables are never used.
Also, what's with all the ceremony? Getting an Enumerator and then calling MoveNext... why not iterate over the items with a foreach. Or even better, use something like an Each() extension method on IEnumerable to get Ruby-esq blocks? stevenharman.net/.../get-ruby-esq-ea
Just wanted to nit-pick. :)
Hi Steve :) love to refactor this. There was a reason for MoveNext() but I can't remember what it is :). Don't know if I follow you about the un-needed code; but hit me! Whaddya got?
Rob,
Steve is pointing out that the following lines:
StringBuilder sb = new StringBuilder();
IEnumerator en = list.GetEnumerator();
... aren't necessary because the variables "sb" and "en" are not used after they are declared.
I think Steve's referring to this in the first method:
public static string ToFormattedList(this IEnumerable list, ListType listType) {
// -> StringBuilder sb = new StringBuilder();
// -> IEnumerator en = list.GetEnumerator();
...
What two lines ? :):) Edited - jeez i'm going blind!
i also have exactly the same issue as jdc has. same steps same errors with object not defined.
I could not figure out the problem until now.
@tufi: When you say "I could not figure out the problem until now" does that mean you figured out the problem? What was it?
no i haven't found any solution sorry , i wanted to say that i am still trying to figure out what is the problem.
i get "Object reference not set to an instance of an object" error.
looks like that overrided method GetControllerInstance(Type controllerType) from StructureMapControllerFactory is getting a bad controllerType argument for me.
public static class ListFormatter
{
private static readonly IDictionary<ListType, ListFormats> formatters = new Dictionary<ListType, ListFormats>();
static ListFormatter()
{
formatters.Add(ListType.Ordered, new ListFormats() { ItemFormat = "<li>{0}</li>", ListFormat = "<ol>{0}</ol>" });
formatters.Add(ListType.Unordered, new ListFormats() { ItemFormat = "<li>{0}</li>", ListFormat = "<ul>{0}</ul>" });
formatters.Add(ListType.TableCell, new ListFormats() { ItemFormat = "<td>{0}</td>", ListFormat = "{0}" });
formatters.Add(ListType.TableRow, new ListFormats() { ItemFormat = "<tr>{0}</tr>", ListFormat = "<table>{0}<table>" });
}
public static string ToFormattedList<T>(IEnumerable<T> items, ListType type)
{
return ToFormattedList<T>(items, type, s => s.ToString());
}
public static string ToFormattedList<T>(IEnumerable<T> items, ListType type, Func<T, string> toString)
{
string listFormat = formatters[type].ListFormat;
string itemFormat = formatters[type].ItemFormat;
var itemsProjected = items.Select(item => string.Format(itemFormat, toString(item)));
return string.Format(listFormat, string.Join(string.Empty, itemsProjected.ToArray()));
}
}
Generics FTW! ToFormattedList<T> is really a one-liner, but I broke it up into multiple statements to make it less cryptic.
Rather than relying on ToString(), you can pass a delegate/lambda to format the string however you like.
PS. I wish that Graffiti didn't munge my code. :(