14

I want to generate an HTML table from a couple specified parameters. Specifically, the two parameters I want to pass into my method are: IEnumerable list, and some subset of properties of T. For example, let's say I have a List of this class:

class Person
{
  string FirstName
  string MiddleName
  string LastName
}

Let's say the list has 5 people in it. I want to be able to get an HTML table of that class (or any other arbitrary class) by doing something like this:

List<Person> people;
...add people to list

string HTML = GetMyTable(people, "FirstName", "LastName");

I'm sure there's a better way to specify which properties I want the table generated from (or which properties I want excluded from the table, that would be better since I'll usually want most or all of the class's properties), but I'm not sure how (I've never used reflection, but I'm guessing that's how). Also, the method should accept a list of any type of class.

Any clever ideas on how to accomplish this?

6 Answers 6

35

Maybe something like this?

var html = GetMyTable(people, x => x.LastName, x => x.FirstName);

public static string GetMyTable<T>(IEnumerable<T> list,params Func<T,object>[] fxns)
{

    StringBuilder sb = new StringBuilder();
    sb.Append("<TABLE>\n");
    foreach (var item in list)
    {
        sb.Append("<TR>\n");
        foreach(var fxn in fxns)
        {
            sb.Append("<TD>");
            sb.Append(fxn(item));
            sb.Append("</TD>");
        }
        sb.Append("</TR>\n");
    }
    sb.Append("</TABLE>");

    return sb.ToString();
}

--Version 2.0--

public static string GetMyTable<T>(IEnumerable<T> list, params  Expression<Func<T, object>>[] fxns)
{

    StringBuilder sb = new StringBuilder();
    sb.Append("<TABLE>\n");

    sb.Append("<TR>\n");
    foreach (var fxn in fxns)
    {
        sb.Append("<TD>");
        sb.Append(GetName(fxn));
        sb.Append("</TD>");
    }
    sb.Append("</TR> <!-- HEADER -->\n");


    foreach (var item in list)
    {
        sb.Append("<TR>\n");
        foreach (var fxn in fxns)
        {
            sb.Append("<TD>");
            sb.Append(fxn.Compile()(item));
            sb.Append("</TD>");
        }
        sb.Append("</TR>\n");
    }
    sb.Append("</TABLE>");

    return sb.ToString();
}

static string GetName<T>(Expression<Func<T, object>> expr)
{
    var member = expr.Body as MemberExpression;
    if (member != null)
        return GetName2(member);

    var unary = expr.Body as UnaryExpression;
    if (unary != null)
        return GetName2((MemberExpression)unary.Operand);

    return "?+?";
}

static string GetName2(MemberExpression member)
{
    var fieldInfo = member.Member as FieldInfo;
    if (fieldInfo != null)
    {
        var d = fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute;
        if (d != null) return d.Description;
        return fieldInfo.Name;
    }

    var propertInfo = member.Member as PropertyInfo;
    if (propertInfo != null)
    {
        var d = propertInfo.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute;
        if (d != null) return d.Description;
        return propertInfo.Name;
    }

    return "?-?";
}

PS: Calling fxn.Compile() repeatedly can be performance killer in a tight loop. It can be better to cache it in a dictionary .

Sign up to request clarification or add additional context in comments.

11 Comments

What happens if you try to select an int and a string, for example? Will P become object, or will it not compile?
Can you explain what "fxn(item)" is doing?
It invokes the delegate expressed as lambda (x=>x.LastName or x=>x.FirstName). Since x=>x.LastName equals to delegate(Person x){ return x.LastName;}
@birdus Similar to Func<Person,object> fxn = delegate(Person x){ return x.LastName;}; var lastName = fxn(person);
If each property in the Person class had a [Description] attribute (using either ComponentModel or EnterpriseServices namespace), is there a way to get that text within this method, so that I can use that text to create a title/header row for my table? So the first row of the table would say "First Name | Last Name" and the next row would be "John Smith" and so on.
|
13

This is what I did and it seems to work fine and not a huge performance hit.

    public static string ToHtmlTable<T>(this List<T> listOfClassObjects)
    {
        var ret = string.Empty;

        return listOfClassObjects == null || !listOfClassObjects.Any()
            ? ret
            : "<table>" +
              listOfClassObjects.First().GetType().GetProperties().Select(p => p.Name).ToList().ToColumnHeaders() +
              listOfClassObjects.Aggregate(ret, (current, t) => current + t.ToHtmlTableRow()) +
              "</table>";
    }

    public static string ToColumnHeaders<T>(this List<T> listOfProperties)
    {
        var ret = string.Empty;

        return listOfProperties == null || !listOfProperties.Any()
            ? ret
            : "<tr>" +
              listOfProperties.Aggregate(ret,
                  (current, propValue) =>
                      current +
                      ("<th style='font-size: 11pt; font-weight: bold; border: 1pt solid black'>" +
                       (Convert.ToString(propValue).Length <= 100
                           ? Convert.ToString(propValue)
                           : Convert.ToString(propValue).Substring(0, 100)) + "..." + "</th>")) +
              "</tr>";
    }

    public static string ToHtmlTableRow<T>(this T classObject)
    {
        var ret = string.Empty;

        return classObject == null
            ? ret
            : "<tr>" +
              classObject.GetType()
                  .GetProperties()
                  .Aggregate(ret,
                      (current, prop) =>
                          current + ("<td style='font-size: 11pt; font-weight: normal; border: 1pt solid black'>" +
                                     (Convert.ToString(prop.GetValue(classObject, null)).Length <= 100
                                         ? Convert.ToString(prop.GetValue(classObject, null))
                                         : Convert.ToString(prop.GetValue(classObject, null)).Substring(0, 100) +
                                           "...") +
                                     "</td>")) + "</tr>";
    }

To use it just pass the ToHtmlTable() a List Example:

List documents = GetMyListOfDocuments(); var table = documents.ToHtmlTable();

Comments

4

Here are two approaches, one using reflection:

public static string GetMyTable(IEnumerable list, params string[] columns)
{
    var sb = new StringBuilder();
    foreach (var item in list)
    {
        //todo this should actually make an HTML table, not just get the properties requested
        foreach (var column in columns)
            sb.Append(item.GetType().GetProperty(column).GetValue(item, null));
    }
    return sb.ToString();
}
//used like
string HTML = GetMyTable(people, "FirstName", "LastName");

Or using lambdas:

public static string GetMyTable<T>(IEnumerable<T> list, params Func<T, object>[] columns)
{
    var sb = new StringBuilder();
    foreach (var item in list)
    {
        //todo this should actually make an HTML table, not just get the properties requested
        foreach (var column in columns)
            sb.Append(column(item));
    }
    return sb.ToString();
}
//used like
string HTML = GetMyTable(people, x => x.FirstName, x => x.LastName);

With the lambdas, what's happening is you're passing methods to the GetMyTable method to get each property. This has benefits over reflection like strong typing, and probably performance.

2 Comments

Thanks for the help, Tim. I appreciate seeing multiple ways to doing this.
the lambda approach seems to be significantly better in terms of performance. my objective is to export all properties, rather than just individual ones, how could i achieve that using the lambda method, without having to explicitly list all of the properties
4

Good luck with

Extention Method

  public static class EnumerableExtension
{
    public static string ToHtmlTable<T>(this IEnumerable<T> list, List<string> headerList, List<CustomTableStyle> customTableStyles, params Func<T, object>[] columns)
    {
        if (customTableStyles == null)
            customTableStyles = new List<CustomTableStyle>();

        var tableCss = string.Join(" ", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Table).Where(w => w.ClassNameList != null).SelectMany(s => s.ClassNameList)) ?? "";
        var trCss = string.Join(" ", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Tr).Where(w => w.ClassNameList != null).SelectMany(s => s.ClassNameList)) ?? "";
        var thCss = string.Join(" ", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Th).Where(w => w.ClassNameList != null).SelectMany(s => s.ClassNameList)) ?? "";
        var tdCss = string.Join(" ", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Td).Where(w => w.ClassNameList != null).SelectMany(s => s.ClassNameList)) ?? "";

        var tableInlineCss = string.Join(";", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Table).Where(w => w.InlineStyleValueList != null).SelectMany(s => s.InlineStyleValueList?.Select(x => String.Format("{0}:{1}", x.Key, x.Value)))) ?? "";
        var trInlineCss = string.Join(";", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Tr).Where(w => w.InlineStyleValueList != null).SelectMany(s => s.InlineStyleValueList?.Select(x => String.Format("{0}:{1}", x.Key, x.Value)))) ?? "";
        var thInlineCss = string.Join(";", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Th).Where(w => w.InlineStyleValueList != null).SelectMany(s => s.InlineStyleValueList?.Select(x => String.Format("{0}:{1}", x.Key, x.Value)))) ?? "";
        var tdInlineCss = string.Join(";", customTableStyles?.Where(w => w.CustomTableStylePosition == CustomTableStylePosition.Td).Where(w => w.InlineStyleValueList != null).SelectMany(s => s.InlineStyleValueList?.Select(x => String.Format("{0}:{1}", x.Key, x.Value)))) ?? "";

        var sb = new StringBuilder();

        sb.Append($"<table{(string.IsNullOrEmpty(tableCss) ? "" : $" class=\"{tableCss}\"")}{(string.IsNullOrEmpty(tableInlineCss) ? "" : $" style=\"{tableInlineCss}\"")}>");
        if (headerList != null)
        {
            sb.Append($"<tr{(string.IsNullOrEmpty(trCss) ? "" : $" class=\"{trCss}\"")}{(string.IsNullOrEmpty(trInlineCss) ? "" : $" style=\"{trInlineCss}\"")}>");
            foreach (var header in headerList)
            {
                sb.Append($"<th{(string.IsNullOrEmpty(thCss) ? "" : $" class=\"{thCss}\"")}{(string.IsNullOrEmpty(thInlineCss) ? "" : $" style=\"{thInlineCss}\"")}>{header}</th>");
            }
            sb.Append("</tr>");
        }
        foreach (var item in list)
        {
            sb.Append($"<tr{(string.IsNullOrEmpty(trCss) ? "" : $" class=\"{trCss}\"")}{(string.IsNullOrEmpty(trInlineCss) ? "" : $" style=\"{trInlineCss}\"")}>");
            foreach (var column in columns)
                sb.Append($"<td{(string.IsNullOrEmpty(tdCss) ? "" : $" class=\"{tdCss}\"")}{(string.IsNullOrEmpty(tdInlineCss) ? "" : $" style=\"{tdInlineCss}\"")}>{column(item)}</td>");
            sb.Append("</tr>");
        }

        sb.Append("</table>");

        return sb.ToString();
    }

    public class CustomTableStyle
    {
        public CustomTableStylePosition CustomTableStylePosition { get; set; }

        public List<string> ClassNameList { get; set; }
        public Dictionary<string, string> InlineStyleValueList { get; set; }
    }

    public enum CustomTableStylePosition
    {
        Table,
        Tr,
        Th,
        Td
    }
}

Using

 private static void Main(string[] args)
    {
        var dataList = new List<TestDataClass>
        {
            new TestDataClass {Name = "A", Lastname = "B", Other = "ABO"},
            new TestDataClass {Name = "C", Lastname = "D", Other = "CDO"},
            new TestDataClass {Name = "E", Lastname = "F", Other = "EFO"},
            new TestDataClass {Name = "G", Lastname = "H", Other = "GHO"}
        };

        var headerList = new List<string> { "Name", "Surname", "Merge" };

        var customTableStyle = new List<EnumerableExtension.CustomTableStyle>
        {
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Table, InlineStyleValueList = new Dictionary<string, string>{{"font-family", "Comic Sans MS" },{"font-size","15px"}}},
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Table, InlineStyleValueList = new Dictionary<string, string>{{"background-color", "yellow" }}},
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Tr, InlineStyleValueList =new Dictionary<string, string>{{"color","Blue"},{"font-size","10px"}}},
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Th,ClassNameList = new List<string>{"normal","underline"}},
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Th,InlineStyleValueList =new Dictionary<string, string>{{ "background-color", "gray"}}},
            new EnumerableExtension.CustomTableStyle{CustomTableStylePosition = EnumerableExtension.CustomTableStylePosition.Td, InlineStyleValueList  =new Dictionary<string, string>{{"color","Red"},{"font-size","15px"}}},
        };

        var htmlResult = dataList.ToHtmlTable(headerList, customTableStyle, x => x.Name, x => x.Lastname, x => $"{x.Name} {x.Lastname}");
    }

    private class TestDataClass
    {
        public string Name { get; set; }
        public string Lastname { get; set; }
        public string Other { get; set; }
    }

Result

<table class="normal underline" style="font-family:Comic Sans MS;font-size:15px;background-color:yellow">
<tr style="color:Blue;font-size:10px">
    <th style="background-color:gray">Name</th>
    <th style="background-color:gray">Surname</th>
    <th style="background-color:gray">Merge</th>
</tr>
<tr style="color:Blue;font-size:10px">
    <td style="color:Red;font-size:15px">A</td>
    <td style="color:Red;font-size:15px">B</td>
    <td style="color:Red;font-size:15px">A B</td>
</tr>
<tr style="color:Blue;font-size:10px">
    <td style="color:Red;font-size:15px">C</td>
    <td style="color:Red;font-size:15px">D</td>
    <td style="color:Red;font-size:15px">C D</td>
</tr>
<tr style="color:Blue;font-size:10px">
    <td style="color:Red;font-size:15px">E</td>
    <td style="color:Red;font-size:15px">F</td>
    <td style="color:Red;font-size:15px">E F</td>
</tr>
<tr style="color:Blue;font-size:10px">
    <td style="color:Red;font-size:15px">G</td>
    <td style="color:Red;font-size:15px">H</td>
    <td style="color:Red;font-size:15px">G H</td
</tr>

2 Comments

Can you give a non-extension-method version?
Change with this public static string ToHtmlTable<T>(IEnumerable<T> list, List<string> headerList, List<CustomTableStyle> customTableStyles, params Func<T, object>[] columns)
0

Extending The answer of @Tim

    public string GetHtmlTable<T, Tproperty>(IEnumerable<T> list, params Expression<Func<T, Tproperty>>[] columns)
    {
        var sb = new StringBuilder();


        sb.AppendLine("<table>");
        sb.AppendLine("<tr>");
        foreach (var column in columns)
        {
            sb.Append("<th>");
            sb.Append(GetPropertyName(Activator.CreateInstance<T>(), column));
            sb.Append("</th>");
        }
        sb.AppendLine("</tr>");
        sb.AppendLine("<tbody>");

        foreach (var item in list)
        {
            sb.AppendLine("<tr>");

            foreach (var column in columns)
            {
                var func = column.Compile();
                sb.Append("<td>");
                sb.Append(func(item));
                sb.Append("</td>");

            }
            sb.AppendLine("</tr>");
        }
        sb.AppendLine("</tbody>");
        sb.AppendLine("</table>");

        return sb.ToString();
    }






    public string GetPropertyName<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> propertyLambda)
    {
        Type type = typeof(TSource);


        var expressionBody = propertyLambda.Body;
        if (expressionBody is UnaryExpression expression && expression.NodeType == ExpressionType.Convert)
        {
            expressionBody = expression.Operand;
        }


        MemberExpression member = (MemberExpression)expressionBody;
        if (member == null)
            return "";

        PropertyInfo propInfo = member.Member as PropertyInfo;
        if (propInfo == null)
            return "";

        if (type != propInfo.ReflectedType &&
            !type.IsSubclassOf(propInfo.ReflectedType)) return "";

        return propInfo.Name;
    }
}

Comments

0

This is the improved solution from L.B answer, it supports table header , using short clean code and can detect by Intellisense if you use wrong properties name

To use the function

List<Person> peoples = new List<Person>();
// ...add multiple Person to list<Person>
string HTML_Table = GetMyTable(peoples, x => x.FirstName, x => x.LastName);

The generic function looks like this...

using System.Text;
using System.Reflection;

    public static string GetMyTable<T>(IEnumerable<T> list,params Func<T,object>[] fxns)
    {
        if (list.Count() == 0)
        { return "";}

        // Dynamic build  HTML Table Header column name base on T\model object
        Type type = list.FirstOrDefault().GetType();
        PropertyInfo[] props = type.GetProperties();
        string THstr = "<tr>";
        foreach (var prop in props)
        {
            THstr+= "<TH>" + prop.Name + "</TH>";
        }
        THstr += "</tr>";

        // Build  remain data rows in HTML Table
        StringBuilder sb = new StringBuilder();
        // Inject bootstrap class base on your need
        sb.Append("<TABLE class='table table-sm table-dark'>\n");
        sb.Append(THstr);
        foreach (var item in list)
        {
            sb.Append("<TR>\n");
            foreach(var fxn in fxns)
            {
                sb.Append("<TD>");
                sb.Append(fxn(item));
                sb.Append("</TD>");
            }
            sb.Append("</TR>\n");
        }
        sb.Append("</TABLE>");

        return sb.ToString();
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.