493e6c6fc3f24eb73295548cfd136574

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!

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 !

F9a9ba6663645458aa8630157ed5e71e

Ants

August 19, 2010, August 19, 2010 03:02, permalink

2 ratings. Login to rate!

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);
        }    
    }
861f311cc4a077c439099d0e5d251e73

Wolfbyte

August 20, 2010, August 20, 2010 09:30, permalink

1 rating. Login to rate!

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");
    }
}
F9a9ba6663645458aa8630157ed5e71e

Ants

August 20, 2010, August 20, 2010 18:13, permalink

No rating. Login to rate!

@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?

861f311cc4a077c439099d0e5d251e73

Wolfbyte

August 23, 2010, August 23, 2010 04:23, permalink

1 rating. Login to rate!

@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#

F9a9ba6663645458aa8630157ed5e71e

Ants

August 23, 2010, August 23, 2010 10:46, permalink

No rating. Login to rate!

@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# :-)

493e6c6fc3f24eb73295548cfd136574

jmcd

August 24, 2010, August 24, 2010 08:47, permalink

No rating. Login to rate!

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);
            }
        }

    }

Your refactoring





Format Copy from initial code

or Cancel