Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate HTML table from list of generic class with specified properties

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?

like image 685
birdus Avatar asked Jun 20 '12 18:06

birdus


3 Answers

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 .

like image 79
L.B Avatar answered Nov 02 '22 01:11

L.B


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

like image 13
VirDeus Avatar answered Nov 01 '22 23:11

VirDeus


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.

like image 4
Tim S. Avatar answered Nov 02 '22 00:11

Tim S.