Wednesday, March 25, 2009

Cool IEnumberable extension method. Useful in ASP.NET MVC

So recently at work we started revamping our site and we've decided to use ASP.NET MVC. I'm not going to cover ASP.NET MVC in this blog post (hopefully in the future) but in short, it's Microsoft's new Web Framework that doesn't use WebForms. No more postbacks for every little thing! Woohoo! However, without WebForms, that means no more ASP.NET controls. Therefore, it was useful for us in a few places to be able to generate an HTML table from a collection of various different objects. So, I ended up writing a really cool extension method which I think can be beneficial since it covers generics, and reflection (two topics that wasn't explored thoroughly enough in SetFocus IMO).

So, first I'll show the code, then I'll do my best to explain:

        public static string ToHtmlTable<T>(this IEnumerable<T> list,string tableSyle, string headerStyle, string rowStyle, string alternateRowStyle)
        {
            var result = new StringBuilder();
            if (String.IsNullOrEmpty(tableSyle))
            {
                result.Append("<table id=\"" + typeof(T).Name + "Table\">");
            }
            else
            {
                result.Append("<table id=\"" + typeof(T).Name + "Table\" class=\"" + tableSyle + "\">");
            }
 
            var propertyArray = typeof(T).GetProperties();
 
            foreach (var prop in propertyArray)
            {
                if (String.IsNullOrEmpty(headerStyle))
                {
                    result.AppendFormat("<th>{0}</th>", prop.Name);
                }
                else
                {
                    result.AppendFormat("<th class=\"{0}\">{1}</th>",headerStyle, prop.Name);
                }
            }
 
 
            for (int i = 0; i < list.Count(); i++)
            {
                if (!String.IsNullOrEmpty(rowStyle) && !String.IsNullOrEmpty(alternateRowStyle))
                {
                    result.AppendFormat("<tr class=\"{0}\">", i%2==0 ? rowStyle : alternateRowStyle);
                }
 
                else
                {
                    result.AppendFormat("<tr>");
                }
                foreach (var prop in propertyArray)
                {
                    object value = prop.GetValue(list.ElementAt(i), null);
                    result.AppendFormat("<td>{0}</td>", value ?? String.Empty);
                }
                result.AppendLine("</tr>");
            }
 
            result.Append("</table>");
 
            return result.ToString();
        }


Ok, let's start with the parameters:

        public static string ToHtmlTable<T>(this IEnumerable<T> list,string tableSyle, string headerStyle, string rowStyle, string alternateRowStyle)


This will allow us to pass in the CSS class of each piece in the HTML table. The way it's coded is that you can pass in null or empty strings and it'll ignore it. Otherwise, it'll enter that as the class name. IE:

<table class="tableClass">


Also notice that the method is generic, so any collection of any kind will work with this method.

            var result = new StringBuilder();
            if (String.IsNullOrEmpty(tableSyle))
            {
                result.Append("<table id=\"" + typeof(T).Name + "Table\">");
            }
            else
            {
                result.Append("<table id=\"" + typeof(T).Name + "Table\" class=\"" + tableSyle + "\">");
            }


Here we start building up the HTML table. If a CSS class was supplied for the table, then we put that in the table tag, otherwise, we don't put any class name. I gave it an ID based on the name of the type that T represents. This may be useful if you want to apply further CSS, so it's good practice to give every element an ID, but in our example we won't actually make use of it. I just left it there for completeness.

            var propertyArray = typeof(T).GetProperties();


Here's where we reflect over T to get an array of PropertyInfo objects. GetProperties returns a PropertyInfo[] array.

            foreach (var prop in propertyArray)
            {
                if (String.IsNullOrEmpty(headerStyle))
                {
                    result.AppendFormat("<th>{0}</th>", prop.Name);
                }
                else
                {
                    result.AppendFormat("<th class=\"{0}\">{1}</th>",headerStyle, prop.Name);
                }


Next, we want to set up the table headers. The headers of the HTML table will be the actual property name. So say you were passing in a List to this method, and your Car class had properties such as "Year", "Make" etc, then that's what would actually appear as the column headers. That's what the PropertyInfo.Name property represents.

            for (int i = 0; i < list.Count(); i++)
            {
                if (!String.IsNullOrEmpty(rowStyle) && !String.IsNullOrEmpty(alternateRowStyle))
                {
                    result.AppendFormat("<tr class=\"{0}\">", i%2==0 ? rowStyle : alternateRowStyle);
                }
 
                else
                {
                    result.AppendFormat("<tr>");
                }


Here we start enumerating the actual IEnumerable that was passed in. For every element in the collection, we want to add a row to our table. Thefore, we first open the tag. However, if the styles for rows are set (both rowStyle and alternateRowStyle) then we need to apply the appropriate class to the row. We can simply check if i is currently an even number (i % 2 == 0). If it is, then we're on a regular row, if it's odd, then we're on an alternate row. If no style is specified, just do a regular tag.

                foreach (var prop in propertyArray)
                {
                    object value = prop.GetValue(list.ElementAt(i), null);
                    result.AppendFormat("<td>{0}</td>", value ?? String.Empty);
                }
                result.AppendLine("</tr>");
            }


Now, we go back to using our PropertyInfo array. For each item in our collection, we want to enumerate over all of it's properties, and get the value for that property. Therefore, first we call the GetValue method on the current PropertyInfo. The first parameter to GetValue, is the object of whose value you want to get. The second one, is if parameters are neccessary to access that property like an indexer. We won't be dealing with that here so we just pass null.

Then, if value isn't null, we append that to the table, else we append a String.Empty. You'll notice the syntax is value ?? String.Empty. That's actually called the "null coalescing operator" It's basically a shorthand way of saying "If value isn't null, use value, else use String.Empty".

            result.Append("</table>");
 
            return result.ToString();
        }


Finally, we just close up the tag and then the tag. Then, we return the result. Now, let's actually test this!

Create a winforms project and add a button to the form, and a WebBrowser control. Then, create a simple Person class like this:

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }


No big deal, simple class with three properties. Then, in the button click, create a new List and add a few Person objects to that list:
        private void button1_Click(object sender, EventArgs e)
        {
            var personList = new List<Person>();
            personList.Add(new Person
            {
                FirstName = "Alex",
                LastName = "Friedman",
                Age = 27
            });
 
            personList.Add(new Person
            {
                FirstName = "Jack",
                LastName = "Bauer",
                Age = 45
            });
 
            personList.Add(new Person
            {
                FirstName = "Cloe",
                LastName = "O'Brien",
                Age = 35
            });
 
            personList.Add(new Person
            {
                FirstName = "John",
                LastName = "Doe",
                Age = 30
            });
 
            string html = @"<style type = ""text/css""> .tableStyle{border: solid 5 green;} 
th.header{ background-color:#FF3300} tr.rowStyle { background-color:#33FFFF; 
border: solid 1 black; } tr.alternate { background-color:#99FF66; 
border: solid 1 black;}</style>";
            html += personList.ToHtmlTable("tableStyle", "header", "rowStyle", "alternate");
            this.webBrowser1.DocumentText = html;
        }


Then, I created an html string with all the different CSS styles to simulate a real life scenario where you'll actually have those classes in your CSS file. Finally, call the ToHtmlTable method, and set the webBrowser's DocumentText to the HTML. This is the result:





FirstNameLastNameAge
AlexFriedman27
JackBauer45
CloeO'Brien35
JohnDoe30


Yuck, those colors are nasty! But you get the idea. The nice thing is, since it's generic, and on IEnumerable, any collection of objects that you have can now be turned into an HTML table by calling this one method. Gotta love generics and reflection.

4 comments:

Vasili Puchko said...

It's not cool and not useful. You are reinventing the wheel. Table from MvcContrib is much more cool and useful.

A.Friedman said...

I've actually never heard of MvcContrib, I'll check it out though, thanks. Regardless, this works just fine for our needs, so I don't really see a point in taking a dependency on another dll just for this.

Unknown said...

Good Simple example.

MvcContrib is nice but overkill for a quick property dump.

samdc said...

Thank you for posting this. This code helped me. And I learned from this. I'm new to ASP.NET MVC and I have used this code to quickly export data in Excel.