Is there more efficient way to build HTML table than the one I\'m trying on right now?
I\'m getting an object and it has some list of entities in it. So I need to pa
As I've recently come to play with creating IDisposable classes, I think this would be both efficient for this specific task, and much easier to read:
Create some very simple classes
/// <summary>
/// https://stackoverflow.com/a/36476600/2343
/// </summary>
public class Table : IDisposable
{
private StringBuilder _sb;
public Table(StringBuilder sb, string id = "default", string classValue="")
{
_sb = sb;
_sb.Append($"<table id=\"{id}\" class=\"{classValue}\">\n");
}
public void Dispose()
{
_sb.Append("</table>");
}
public Row AddRow()
{
return new Row(_sb);
}
public Row AddHeaderRow()
{
return new Row(_sb, true);
}
public void StartTableBody()
{
_sb.Append("<tbody>");
}
public void EndTableBody()
{
_sb.Append("</tbody>");
}
}
public class Row : IDisposable
{
private StringBuilder _sb;
private bool _isHeader;
public Row(StringBuilder sb, bool isHeader = false)
{
_sb = sb;
_isHeader = isHeader;
if (_isHeader)
{
_sb.Append("<thead>\n");
}
_sb.Append("\t<tr>\n");
}
public void Dispose()
{
_sb.Append("\t</tr>\n");
if (_isHeader)
{
_sb.Append("</thead>\n");
}
}
public void AddCell(string innerText)
{
_sb.Append("\t\t<td>\n");
_sb.Append("\t\t\t"+innerText);
_sb.Append("\t\t</td>\n");
}
}
}
Then you can define your table using:
StringBuilder sb = new StringBuilder();
using (Html.Table table = new Html.Table(sb))
{
foreach (var invalidCompany in mailMessageObject.InvalidCompanies)
{
using (Html.Row row = table.AddRow())
{
row.AddCell(invalidCompany.BusinessName);
row.AddCell(invalidCompany.SwiftBIC);
row.AddCell(invalidCompany.IBAN);
}
}
}
string finishedTable = sb.ToString();
It is a decent approach, and just 'what it takes' to output something as complicated as HTML - unless you want to do it using plain strings (which is just as messy, if not worse).
One improvement: do not use the same cell object multiple times, you run the risk of getting incorrect output. Improved code:
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.BusinessName });
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.SwiftBIC });
row.Cells.Add(new HtmlTableCell { InnerText = invalidCompany.IBAN });
Of course you can also create your own helpers for creating cells, for creating a row full of cells, etc. There are also good libraries for this, e.g. see https://www.nuget.org/packages/HtmlTags/.
I would just like to supplement Steve Harris' answer with a class library that is a little more built out. His answer is a totally elegant solution that made a windows service I was creating not have to reference System.Web for no good reason!
Classes Defined:
public static class Html
{
public class Table : HtmlBase, IDisposable
{
public Table(StringBuilder sb, string classAttributes = "", string id = "") : base(sb)
{
Append("<table");
AddOptionalAttributes(classAttributes, id);
}
public void StartHead(string classAttributes = "", string id = "")
{
Append("<thead");
AddOptionalAttributes(classAttributes, id);
}
public void EndHead()
{
Append("</thead>");
}
public void StartFoot(string classAttributes = "", string id = "")
{
Append("<tfoot");
AddOptionalAttributes(classAttributes, id);
}
public void EndFoot()
{
Append("</tfoot>");
}
public void StartBody(string classAttributes = "", string id = "")
{
Append("<tbody");
AddOptionalAttributes(classAttributes, id);
}
public void EndBody()
{
Append("</tbody>");
}
public void Dispose()
{
Append("</table>");
}
public Row AddRow(string classAttributes = "", string id = "")
{
return new Row(GetBuilder(), classAttributes, id);
}
}
public class Row : HtmlBase, IDisposable
{
public Row(StringBuilder sb, string classAttributes = "", string id = "") : base(sb)
{
Append("<tr");
AddOptionalAttributes(classAttributes, id);
}
public void Dispose()
{
Append("</tr>");
}
public void AddCell(string innerText, string classAttributes = "", string id = "", string colSpan = "")
{
Append("<td");
AddOptionalAttributes(classAttributes, id, colSpan);
Append(innerText);
Append("</td>");
}
}
public abstract class HtmlBase
{
private StringBuilder _sb;
protected HtmlBase(StringBuilder sb)
{
_sb = sb;
}
public StringBuilder GetBuilder()
{
return _sb;
}
protected void Append(string toAppend)
{
_sb.Append(toAppend);
}
protected void AddOptionalAttributes(string className = "", string id = "", string colSpan = "")
{
if (!id.IsNullOrEmpty())
{
_sb.Append($" id=\"{id}\"");
}
if (!className.IsNullOrEmpty())
{
_sb.Append($" class=\"{className}\"");
}
if (!colSpan.IsNullOrEmpty())
{
_sb.Append($" colspan=\"{colSpan}\"");
}
_sb.Append(">");
}
}
}
Usage:
StringBuilder sb = new StringBuilder();
using (Html.Table table = new Html.Table(sb, id: "some-id"))
{
table.StartHead();
using (var thead = table.AddRow())
{
thead.AddCell("Category Description");
thead.AddCell("Item Description");
thead.AddCell("Due Date");
thead.AddCell("Amount Budgeted");
thead.AddCell("Amount Remaining");
}
table.EndHead();
table.StartBody();
foreach (var alert in alertsForUser)
{
using (var tr = table.AddRow(classAttributes: "someattributes"))
{
tr.AddCell(alert.ExtendedInfo.CategoryDescription);
tr.AddCell(alert.ExtendedInfo.ItemDescription);
tr.AddCell(alert.ExtendedInfo.DueDate.ToShortDateString());
tr.AddCell(alert.ExtendedInfo.AmountBudgeted.ToString("C"));
tr.AddCell(alert.ExtendedInfo.ItemRemaining.ToString("C"));
}
}
table.EndBody();
}
return sb.ToString();
I think maybe you can add a function to get all the properties from your object. And then just iterate over them. Also you can create a list of properties that need to be displayed in your message.
private static PropertyInfo[] GetProperties(object obj)
{
return obj.GetType().GetProperties();
}
// -------
foreach (var invalidCompany in mailMessageObject.InvalidCompanies)
{
var properties = GetProperties(invalidCompany);
foreach (var p in properties)
{
string name = p.Name;
if(propertiesThatNeedToBeDisplayed.Contains(name)
{
cell.InnerText = p.GetValue(invalidCompany, null);
row.Cells.Add(cell);
table.Rows.Add(row);
}
}
}