2012-06-20 7 views
9

Quiero generar una tabla HTML a partir de un par de parámetros especificados. En concreto, los dos parámetros que quiero pasar a mi método son: Lista IEnumerable, y algún subconjunto de propiedades de T. Por ejemplo, digamos que tengo una lista de esta clase:Generar tabla HTML de la lista de clase genérica con propiedades especificadas

class Person 
{ 
    string FirstName 
    string MiddleName 
    string LastName 
} 

Digamos que la lista tiene 5 personas en ella. Quiero ser capaz de obtener una tabla HTML de esa clase (o cualquier otra clase arbitraria) haciendo algo como esto:

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

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

Estoy seguro de que hay una mejor manera de especificar qué propiedades quiero la tabla generada desde (o qué propiedades quiero excluir de la tabla, sería mejor ya que generalmente querré la mayoría o todas las propiedades de la clase), pero no estoy seguro de cómo (nunca he usado el reflejo, pero estoy adivinando cómo es). Además, el método debe aceptar una lista de cualquier tipo de clase.

¿Alguna idea inteligente sobre cómo lograr esto?

Respuesta

19

Tal vez algo como esto?

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() en repetidas ocasiones puede ser asesino rendimiento en un bucle estrecho. Puede ser mejor guardarlo en un diccionario.

+0

¿Qué ocurre si intentas seleccionar una cadena int y una cadena, por ejemplo? ¿'P' se convertirá en' objeto', o no se compilará? –

+0

@TimS. Gracias, actualicé la respuesta. –

+0

¿Puedes explicar qué está haciendo "fxn (item)"? – birdus

3

Aquí hay dos enfoques, uno que utiliza reflexión:

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

O usando 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); 

Con las lambdas, lo que sucede es que estás pasando métodos para el método GetMyTable para obtener cada uno propiedad. Esto tiene beneficios sobre la reflexión como la tipificación fuerte, y probablemente el rendimiento.

+0

Gracias por la ayuda, Tim. Agradezco ver múltiples maneras de hacer esto. – birdus

6

Esto es lo que hice y parece funcionar bien y no es un gran golpe de rendimiento.

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

Para usarlo sólo tiene que pasar la ToHtmlTable() una lista Ejemplo:

documentos list = GetMyListOfDocuments(); var table = documents.ToHtmlTable();

Cuestiones relacionadas