using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
/// <summary>
/// Helper to avoid the tag soup that comes from rendering Lists of items.
/// </summary>
public static class ListExtensions {
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list, Func<T, string> action,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action);
}
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
Func<T, string> action1,
Func<T, string> action2,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action1, action2);
}
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
Func<T, string> action1,
Func<T, string> action2,
Func<T, string> action3,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action1, action2, action3);
}
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
Func<T, string> action1,
Func<T, string> action2,
Func<T, string> action3,
Func<T, string> action4,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action1, action2, action3, action4);
}
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
Func<T, string> action1,
Func<T, string> action2,
Func<T, string> action3,
Func<T, string> action4,
Func<T, string> action5,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action1, action2, action3, action4, action5);
}
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
Func<T, string> action1,
Func<T, string> action2,
Func<T, string> action3,
Func<T, string> action4,
Func<T, string> action5,
Func<T, string> action6,
string enclosure, string itemTemplate, string empty) {
return ProcessTemplateWithFunctions(list, itemTemplate, empty, enclosure, action1, action2, action3, action4, action5, action6);
}
static string ProcessTemplateWithFunctions<T>(IEnumerable<T> list, string itemTemplate, string empty,
string enclosure,
Func<T, string> action1,
Func<T, string> action2 = null,
Func<T, string> action3 = null,
Func<T, string> action4 = null,
Func<T, string> action5 = null,
Func<T, string> action6 = null) {
var outerTemplate = new StringBuilder();
var innerTemplate = new StringBuilder();
if (list==null || list.Count() == 0) {
innerTemplate.Append(string.Format(itemTemplate, empty ?? "No items to display."));
}
else {
foreach (var item in list) {
if (action1 != null & action2 != null & action3 != null & action4 != null & action5 != null & action6 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item), action2(item), action3(item), action4(item), action5(item), action6(item)));
}
else if (action1 != null & action2 != null & action3 != null & action4 != null & action5 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item), action2(item), action3(item), action4(item), action5(item)));
}
else if (action1 != null & action2 != null & action3 != null & action4 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item), action2(item), action3(item), action4(item)));
}
else if (action1 != null & action2 != null & action3 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item), action2(item), action3(item)));
}
else if (action1 != null & action2 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item), action2(item)));
}
else if (action1 != null) {
innerTemplate.Append(string.Format(itemTemplate, action1(item)));
}
}
}
outerTemplate.Append(string.Format(enclosure, innerTemplate));
return outerTemplate.ToString();
}
}
Refactorings
No refactoring yet !
Ants
August 19, 2010, August 19, 2010 03:02, permalink
Why not take advantage of the C# params keyword and move the enclosure, itemTemplate, and empty parameters before the actions?
public static class ListExtensions
{
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
string enclosure, string itemTemplate, string empty,
params Func<T, string>[] actions)
{
var innerTemplate = new StringBuilder();
int count = 0;
if(list != null && actions.Length > 0)
{
foreach (var item in list)
{
var values = new List<string>();
foreach (var action in actions)
values.Add(action(item));
innerTemplate.AppendFormat(itemTemplate, values);
++count;
}
}
if (count == 0)
innerTemplate.AppendFormat(itemTemplate, empty ?? "No items to display.");
return string.Format(enclosure, innerTemplate);
}
}
Wolfbyte
August 20, 2010, August 20, 2010 09:30, permalink
Working off of @Ants version...
public static class ListExtensions
{
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
string enclosure, string itemTemplate, string empty,
params Func<T, string>[] actions)
{
return String.Format(enclosure,
(list == null || !actions.Any()
? null
: String.Join(null, list.Select(
i => String.Format(itemTemplate, actions.Select(
a => a(i)).Cast<object>().ToArray()))))
?? empty
?? "No items to display");
}
}
Ants
August 20, 2010, August 20, 2010 18:13, permalink
@Wolfbyte: Nice conversion to use Select()'s to iterate over the list and actions. I'm curious if you had a reason for deciding to forgo the efficiency of using a StringBuilder to join each of the formatted itemTemplates?
Wolfbyte
August 23, 2010, August 23, 2010 04:23, permalink
@Ants: If you look at the implementation of String.Join is uses a StringBuilder internally anyway, it handles null values and it specifies intent at a higher level of abstraction. This code is very LISPy as I'm finding more and more of my code becoming. Maybe John McCarthy was on to something. Now I just need a pattern-matching operator in C#
Ants
August 23, 2010, August 23, 2010 10:46, permalink
@Wolfbyte: Thanks. I was guessing that String.Join() used a StringBuilder under the covers, but I wasn't willing to make that assumption.
Perfect, with the addition of the pattern matching operator, we can get L# :-)
jmcd
August 24, 2010, August 24, 2010 08:47, permalink
Not quite a pattern matching operator, but I did add a placeholder parser to get the current list index while rendering.
Thanks guys!
public static class ListExtensions {
public static string RenderList<T>(this HtmlHelper helper, IEnumerable<T> list,
string enclosure, string itemTemplate, string empty,
params Func<T, string>[] actions) {
try {
return String.Format(enclosure,
(list == null || !actions.Any()
? null
: String.Join(null, list.Select( (i, index) => String.Format(
itemTemplate.Replace( "$loop$", (index+1).ToString() ),
actions.Select(a => a(i)).Cast<object>().ToArray()))))
?? empty
?? "No items to display");
}
catch (FormatException e) {
throw new FormatException( "Your parameters in the template probably don't match up with the provided Item details. Check them!", e);
}
}
}
The idea behind this helper, written using C#4.0 features, is to simplify the tag soup when rendering a list of items, which may possibly be empty. I support up to six possible item details to be injected into the supplied HTML template.
A possible usage would look like:
<%=Html.RenderList(Model.GroupMembers,
/*{0}*/ x => string.Format("/ProfileImage/ProfileImageSmall/{0}", x.Account.Profile.Nickname),
/*{1}*/ x => x.Account.Profile.Nickname,
/*{2}*/ x => x.Account.Profile.FriendlyName, x=> x.IsAdmin==true ? "Administrator":"Member",
"<div class=\"C32FL\"><ul class=\"ls2 pbl\">{0}</ul></div>",
"<li><img src=\"{0}\" /><h2>{1} ({3})</h2><p>{2}</p></li>",
"<li>There are no members in this Group yet.</li>") %>
I'd be interested to see any refactorings to streamline the actual helper!