49

I have question, that maybe someone here wouldn't mind to help me with. I have lets say 3 datatables, each one of them has the following columns:

size, quantity, amount, duration

Name of datatables and values

LivingRoom
================
1
1
1
1
2
2
2
2

BathRoom
================
3
3
3
3
4
4
4
4

BedRoom
=================
5
5
5
5
6
6
6
6

Now i am trying to build an html invoice to were i can loop through all the datatables and output the following html output, very basic:

<table>
  <tr>
    <td>Area</td>
  </tr>
  <tr>
    <td>Living Room</td>
  </tr>

  <tr>
    <td>Size</td>
    <td>Quantity</td>
    <td>Amount</td>
    <td>Duration</td>
  </tr>
  <tr>
    <td>1</td>
    <td>1</td>
    <td>1</td>
    <td>1</td>
  </tr>
  <tr>
    <td>2</td>
    <td>2</td>
    <td>2</td>
    <td>2</td>
  </tr>

  <tr>
    <td>Area</td>
  </tr>
  <tr>
    <td>Bathroom</td>
  </tr>

  <tr>
    <td>Size</td>
    <td>Quantity</td>
    <td>Amount</td>
    <td>Duration</td>
  </tr>
  <tr>
    <td>3</td>
    <td>3</td>
    <td>3</td>
    <td>3</td>
  </tr>
  <tr>
    <td>4</td>
    <td>4</td>
    <td>4</td>
    <td>4</td>
  </tr>

  <tr>
    <td>Area</td>
  </tr>
  <tr>
    <td>Bedroom</td>
  </tr>

  <tr>
    <td>Size</td>
    <td>Quantity</td>
    <td>Amount</td>
    <td>Duration</td>
  </tr>
  <tr>
    <td>5</td>
    <td>5</td>
    <td>5</td>
    <td>5</td>
  </tr>
  <tr>
    <td>6</td>
    <td>6</td>
    <td>6</td>
    <td>6</td>
  </tr>
</table>

So pretty much the area would have the name of the datatable, and then under each area loop that specific datatable and output the datat in that format. I can't figure out the looping logic or how to do this, i've been breaking my head for the last few days on this. maybe i'm just thinking about it in the wrong way but i could really use some help on this.

dipenparmar12
  • 3,042
  • 1
  • 29
  • 39
Brad Hazelnut
  • 1,603
  • 5
  • 21
  • 33
  • Have you considered using a ``? – Amber Oct 30 '13 at 13:06
  • But would that work having multiple datatables? and would i be able to format it like this? – Brad Hazelnut Oct 30 '13 at 13:08
  • 1
    can't you merge all into one http://msdn.microsoft.com/en-us/library/system.data.datatable.merge.aspx? – Zaki Oct 30 '13 at 13:12
  • I'm not sure if you can put all datatables in one listview, but you should easily be able to structure it as you have. If you can't do all datatables in one listview, you can put three listviews in your table and populate them accordingly. Or if they are categorised, a nested listview. – Amber Oct 30 '13 at 13:13

10 Answers10

126

use this function:

    public static string ConvertDataTableToHTML(DataTable dt)
    {
        string html = "<table>";
        //add header row
        html += "<tr>";
        for(int i=0;i<dt.Columns.Count;i++)
            html+="<td>"+dt.Columns[i].ColumnName+"</td>";
        html += "</tr>";
        //add rows
        for (int i = 0; i < dt.Rows.Count; i++)
        {
            html += "<tr>";
            for (int j = 0; j< dt.Columns.Count; j++)
                html += "<td>" + dt.Rows[i][j].ToString() + "</td>";
            html += "</tr>";
        }
        html += "</table>";
        return html;
    }
Omer Eldan
  • 1,757
  • 1
  • 11
  • 10
12
public static string toHTML_Table(DataTable dt)
{
    if (dt.Rows.Count == 0) return ""; // enter code here

    StringBuilder builder = new StringBuilder();
    builder.Append("<html>");
    builder.Append("<head>");
    builder.Append("<title>");
    builder.Append("Page-");
    builder.Append(Guid.NewGuid());
    builder.Append("</title>");
    builder.Append("</head>");
    builder.Append("<body>");
    builder.Append("<table border='1px' cellpadding='5' cellspacing='0' ");
    builder.Append("style='border: solid 1px Silver; font-size: x-small;'>");
    builder.Append("<tr align='left' valign='top'>");
    foreach (DataColumn c in dt.Columns)
    {
        builder.Append("<td align='left' valign='top'><b>");
        builder.Append(c.ColumnName);
        builder.Append("</b></td>");
    }
    builder.Append("</tr>");
    foreach (DataRow r in dt.Rows)
    {
        builder.Append("<tr align='left' valign='top'>");
        foreach (DataColumn c in dt.Columns)
        {
            builder.Append("<td align='left' valign='top'>");
            builder.Append(r[c.ColumnName]);
            builder.Append("</td>");
        }
        builder.Append("</tr>");
    }
    builder.Append("</table>");
    builder.Append("</body>");
    builder.Append("</html>");

    return builder.ToString();
}
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
Suman Banerjee
  • 1,923
  • 4
  • 24
  • 40
  • 2
    Good for copy&paste, I'd add a couple of things: 1) html encode all table values, for example: builder.Append(System.Web.HttpUtility.HtmlEncode(c.ColumnName)) instead of builder.Append(c.ColumnName) 2) specify title as a function argument – sarh Sep 10 '14 at 16:26
  • 1
    i can't understanding your comment @sarh – Suman Banerjee Sep 10 '14 at 17:51
  • 2
    Regarding p.1 : c.ColumnName can have some illegal characters that needs to be escaped in HTML. For exmaple, if string "<" will be put to HTML as is, such HTML will be broken. It should be escaped like "<". And this can be done with System.Web.HttpUtility. Regarding p.2 - that's just code decaration, I think it is better to pass required title to this method instead of generating arbitrary title with new GUID value. Anyway +1 to your answer, because there is a full html and StringBuilder is better than string concatenation for such a large number of string modifications. – sarh Sep 11 '14 at 11:48
  • I understand that this has been answered for so long, but we encourage answers that are explained well, not just copy paste. – Malcolm Salvador Jan 20 '17 at 01:43
10

I have seen some solutions here worth noting, as Omer Eldan posted. but here follows. ASP C#

using System.Data;
using System.Web.UI.HtmlControls;

public static Table DataTableToHTMLTable(DataTable dt, bool includeHeaders)
{
    Table tbl = new Table();
    TableRow tr = null;
    TableCell cell = null;

    int rows = dt.Rows.Count;
    int cols = dt.Columns.Count;

    if (includeHeaders)
    {
        TableHeaderRow htr = new TableHeaderRow();
        TableHeaderCell hcell = null;
        for (int i = 0; i < cols; i++)
        {
            hcell = new TableHeaderCell();
            hcell.Text = dt.Columns[i].ColumnName.ToString();
            htr.Cells.Add(hcell);
        }
        tbl.Rows.Add(htr);
    }

    for (int j = 0; j < rows; j++)
    {
        tr = new TableRow();
        for (int k = 0; k < cols; k++)
        {
            cell = new TableCell();
            cell.Text = dt.Rows[j][k].ToString();
            tr.Cells.Add(cell);
        }
        tbl.Rows.Add(tr);
    }
    return tbl;
}

why this solution? Because you can easily just add this to a panel ie:

panel.Controls.Add(DataTableToHTMLTable(dtExample,true));

Second question , why do you have one column datatables and not just array's? Are you sure that these DataTables are uniform, because if the data is jagged then it's no use. If You really have to join these DataTables, there is many examples of Linq operations, or just use (beware though of same name columns as this will conflict in both linq operations and this solution if not handled):

public DataTable joinUniformTable(DataTable dt1, DataTable dt2)
{
    int dt2ColsCount = dt2.Columns.Count;
    int dt1lRowsCount = dt1.Rows.Count;

    DataColumn column;
    for (int i = 0; i < dt2ColsCount; i++)
    {
        column = new DataColumn();
        string colName = dt2.Columns[i].ColumnName;
        System.Type colType = dt2.Columns[i].DataType;
        column.ColumnName = colName;
        column.DataType = colType;
        dt1.Columns.Add(column);

        for (int j = 0; j < dt1lRowsCount; j++)
        {
            dt1.Rows[j][colName] = dt2.Rows[j][colName];
        }
    }
    return dt1;
}

and your solution would look something like:

panel.Controls.Add(DataTableToHTMLTable(joinUniformTable(joinUniformTable(LivDT,BathDT),BedDT),true));

interpret the rest, and have fun.

LokizFenrir
  • 340
  • 3
  • 9
  • this might be an indirect answer, but i am sure these functions will help you in the right direction and be powerful to use in multiple projects, thumbs up if you found this helpful. (and you visitors too,if you want to be nice) – LokizFenrir Oct 14 '14 at 07:51
  • If you want to render this table to a string use http://stackoverflow.com/a/1525087/74585 – Matthew Lock Oct 30 '14 at 07:55
  • @MatthewLock Why would I want to render it as a string? I have tested this and it was more efficient and syntactically correct. But thanks anyway. – LokizFenrir Dec 17 '14 at 07:07
  • 2
    I wanted to save it to a string to insert into a HTML email. – Matthew Lock Dec 19 '14 at 03:18
4

The first answer is correct, but if you have a large amount of data (in my project I had 8.000 rows * 8 columns) is tragically slow.... Having a string that becomes that large in c# is why that solution is forbiden

Instead using a large string I used a string array that I join at the end in order to return the string of the html table. Moreover, I used a linq expression ((from o in row.ItemArray select o.ToString()).ToArray()) in order to join each DataRow of the table, instead of looping again, in order to save as much time as possible.

This is my sample code:

private string MakeHtmlTable(DataTable data)
{
            string[] table = new string[data.Rows.Count] ;
            long counter = 1;
            foreach (DataRow row in data.Rows)
            {
                table[counter-1] = "<tr><td>" + String.Join("</td><td>", (from o in row.ItemArray select o.ToString()).ToArray()) + "</td></tr>";

                counter+=1;
            }

            return "</br><table>" + String.Join("", table) + "</table>";
}
ealef
  • 534
  • 3
  • 11
  • c# ASP supports the manipulation of actual HTML controls, just include the correct using. `using System.Web.UI.HtmlControls;` – LokizFenrir Oct 14 '14 at 07:32
  • Your solution works well, thanks. Side note: there are no column headers, but one can add them, if needed. – Nicolas Mar 31 '16 at 12:32
  • 1
    Ok, sorry for double commenting, but I just decided after writing the first comment to do it: insert this just before the foreach loop (and don't forget to increment the array size by one!): `table[0] = "" + string.Join("", (from a_Col in data.Columns.Cast() select a_Col.ColumnName).ToArray()) + "";` – Nicolas Mar 31 '16 at 12:46
4

Just in case anyone arrives here and was hoping for VB (I did, and I didn't enter c# as a search term), here's the basics of the first response..

Public Shared Function ConvertDataTableToHTML(dt As DataTable) As String
    Dim html As String = "<table>"
    html += "<tr>"
    For i As Integer = 0 To dt.Columns.Count - 1
        html += "<td>" + System.Web.HttpUtility.HtmlEncode(dt.Columns(i).ColumnName) + "</td>"
    Next
    html += "</tr>"
    For i As Integer = 0 To dt.Rows.Count - 1
        html += "<tr>"
        For j As Integer = 0 To dt.Columns.Count - 1
            html += "<td>" + System.Web.HttpUtility.HtmlEncode(dt.Rows(i)(j).ToString()) + "</td>"
        Next
        html += "</tr>"
    Next
    html += "</table>"
    Return html
End Function
DJDave
  • 865
  • 1
  • 13
  • 28
3

From this link

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Text;
using System.Xml;

namespace ClientUtil
{
public class DataTableUtil
{

public static string DataTableToXmlString(DataTable dtData)
{
if (dtData == null || dtData.Columns.Count == 0)
return (string) null;
DataColumn[] primaryKey = dtData.PrimaryKey;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(“<TABLE>”);
stringBuilder.Append(“<TR>”);
foreach (DataColumn dataColumn in (InternalDataCollectionBase) dtData.Columns)
{
if (DataTableUtil.IsPrimaryKey(dataColumn.ColumnName, primaryKey))
stringBuilder.Append(“<TH IsPK=’true’ ColType='”).Append(Convert.ToString(dataColumn.DataType == typeof (object) ? (object) typeof (string) : (object) dataColumn.DataType)).Append(“‘>”).Append(dataColumn.ColumnName.Replace(“&”, “”)).Append(“</TH>”);
else
stringBuilder.Append(“<TH IsPK=’false’ ColType='”).Append(Convert.ToString(dataColumn.DataType == typeof (object) ? (object) typeof (string) : (object) dataColumn.DataType)).Append(“‘>”).Append(dataColumn.ColumnName.Replace(“&”, “”)).Append(“</TH>”);
}
stringBuilder.Append(“</TR>”);
int num1 = 0;
foreach (DataRow dataRow in (InternalDataCollectionBase) dtData.Rows)
{
stringBuilder.Append(“<TR>”);
int num2 = 0;
foreach (DataColumn dataColumn in (InternalDataCollectionBase) dtData.Columns)
{
string str = Convert.IsDBNull(dataRow[dataColumn.ColumnName]) ? (string) null : Convert.ToString(dataRow[dataColumn.ColumnName]).Replace(“<“, “&lt;”).Replace(“>”, “&gt;”).Replace(“\””, “&quot;”).Replace(“‘”, “&apos;”).Replace(“&”, “&amp;”);
if (!string.IsNullOrEmpty(str))
stringBuilder.Append(“<TD>”).Append(str).Append(“</TD>”);
else
stringBuilder.Append(“<TD>”).Append(“</TD>”);
++num2;
}
stringBuilder.Append(“</TR>”);
++num1;
}
stringBuilder.Append(“</TABLE>”);
return ((object) stringBuilder).ToString();
}

protected static bool IsPrimaryKey(string ColumnName, DataColumn[] PKs)
{
if (PKs == null || string.IsNullOrEmpty(ColumnName))
return false;
foreach (DataColumn dataColumn in PKs)
{
if (dataColumn.ColumnName.ToLower().Trim() == ColumnName.ToLower().Trim())
return true;
}
return false;
}

public static DataTable XmlStringToDataTable(string XmlData)
{
DataTable dataTable = (DataTable) null;
IList<DataColumn> list = (IList<DataColumn>) new List<DataColumn>();
if (string.IsNullOrEmpty(XmlData))
return (DataTable) null;
XmlDocument xmlDocument1 = new XmlDocument();
xmlDocument1.PreserveWhitespace = true;
XmlDocument xmlDocument2 = xmlDocument1;
xmlDocument2.LoadXml(XmlData);
XmlNode xmlNode1 = xmlDocument2.SelectSingleNode(“/TABLE”);
if (xmlNode1 != null)
{
dataTable = new DataTable();
int num = 0;
foreach (XmlNode xmlNode2 in xmlNode1.SelectNodes(“TR”))
{
if (num == 0)
{
foreach (XmlNode xmlNode3 in xmlNode2.SelectNodes(“TH”))
{
bool result = false;
string str = xmlNode3.Attributes[“IsPK”].Value;
if (!string.IsNullOrEmpty(str))
{
if (!bool.TryParse(str, out result))
result = false;
}
else
result = false;
Type type = Type.GetType(xmlNode3.Attributes[“ColType”].Value);
DataColumn column = new DataColumn(xmlNode3.InnerText, type);
if (result)
list.Add(column);
if (!dataTable.Columns.Contains(column.ColumnName))
dataTable.Columns.Add(column);
}
if (list.Count > 0)
{
DataColumn[] dataColumnArray = new DataColumn[list.Count];
for (int index = 0; index < list.Count; ++index)
dataColumnArray[index] = list[index];
dataTable.PrimaryKey = dataColumnArray;
}
}
else
{
DataRow row = dataTable.NewRow();
int index = 0;
foreach (XmlNode xmlNode3 in xmlNode2.SelectNodes(“TD”))
{
Type dataType = dataTable.Columns[index].DataType;
string s = xmlNode3.InnerText;
if (!string.IsNullOrEmpty(s))
{
try
{
s = s.Replace(“&lt;”, “<“);
s = s.Replace(“&gt;”, “>”);
s = s.Replace(“&quot;”, “\””);
s = s.Replace(“&apos;”, “‘”);
s = s.Replace(“&amp;”, “&”);
row[index] = Convert.ChangeType((object) s, dataType);
}
catch
{
if (dataType == typeof (DateTime))
row[index] = (object) DateTime.ParseExact(s, “yyyyMMdd”, (IFormatProvider) CultureInfo.InvariantCulture);
}
}
else
row[index] = Convert.DBNull;
++index;
}
dataTable.Rows.Add(row);
}
++num;
}
}
return dataTable;
}
}
}
Hasan Fathi
  • 5,610
  • 4
  • 42
  • 60
Jaye
  • 152
  • 4
2

In case somebody is still searching for a solution that avoids (to some extent) the usage of hard-coded strings, you can make use of HtmlTextWriter Nuget package:

// NOTE: dt is the `DataTable` type object
StringBuilder sbControlHtml = new StringBuilder();
using (StringWriter stringWriter = new StringWriter())
{
    using HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
    htmlWriter.RenderBeginTag(HtmlTextWriterTag.Table);

    // add header row
    htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
    foreach(DataColumn col in dt.Columns)
    {
        htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
        htmlWriter.Write(col.ColumnName);
        htmlWriter.RenderEndTag();
    }
    htmlWriter.RenderEndTag();

    // add rows
    foreach(DataRow row in dt.Rows)
    {
        htmlWriter.RenderBeginTag(HtmlTextWriterTag.Tr);
        foreach(DataColumn col in dt.Columns)
        {
            htmlWriter.RenderBeginTag(HtmlTextWriterTag.Td);
            htmlWriter.Write(row[col].ToString());
            htmlWriter.RenderEndTag();
        }
        htmlWriter.RenderEndTag();
    }
    
    htmlWriter.RenderEndTag();
    sbControlHtml.Append(stringWriter.ToString());
}

The good thing about this library is that is supports .Net Standard. In my case, I needed .Net Core alternative to HtmlTextWriter which was only available on .Net Framework. I simply put this code in .Net Standard project and then referenced it from .Net Core one.

Ivan B
  • 84
  • 8
1

If your'e using Web Forms then Grid View can work very nicely for this

The code looks a little like this.

aspx page.

<asp:GridView ID="GridView1" runat="server" DataKeyNames="Name,Size,Quantity,Amount,Duration"></asp:GridView>

You can either input the data manually or use the source method in the code side

public class Room
{
    public string Name
    public double Size {get; set;}
    public int Quantity {get; set;}
    public double Amount {get; set;}
    public int Duration {get; set;}
}

protected void Page_Load(object sender, EventArgs e)
{
    if(!IsPostBack)//this is so you can keep any data you want for the list
    {
        List<Room> rooms=new List<Room>();
        //then use the rooms.Add() to add the rooms you need.
        GridView1.DataSource=rooms
        GridView1.Databind()
    }
}

Personally I like MVC4 the client side code ends up much lighter than Web Forms. It is similar to the above example with using a class but you use a view and Controller instead.

The View would look like this.

@model YourProject.Model.IEnumerable<Room>

<table>
    <th>
        <td>@Html.LabelFor(model => model.Name)</td>
        <td>@Html.LabelFor(model => model.Size)</td>
        <td>@Html.LabelFor(model => model.Quantity)</td>
        <td>@Html.LabelFor(model => model.Amount)</td>
        <td>@Html.LabelFor(model => model.Duration)</td>
   </th>
foreach(item in model)
{
    <tr>
        <td>@model.Name</td>
        <td>@model.Size</td>
        <td>@model.Quantity</td>
        <td>@model.Amount</td>
        <td>@model.Duration</td>
   </tr>
}
</table>

The controller might look something like this.

public ActionResult Index()
{
    List<Room> rooms=new List<Room>();
    //again add the items you need

    return View(rooms);
}

Hope this helps :)

Zane Chung
  • 154
  • 6
  • 1
    Perhaps the [WebGrid](http://msdn.microsoft.com/en-us/magazine/hh288075.aspx) could be another alternative in MVC4. – Jon Oct 30 '13 at 14:49
0

Since I didn't see this in any of the other answers, and since it's more efficient (in lines of code and in speed), here's a solution in VB.NET using a stringbuilder and lambda functions with String.Join instead of For loops for the columns.

Dim sb As New StringBuilder
sb.Append("<table>")
sb.Append("<tr>" & String.Join("", dt.Columns.OfType(Of DataColumn)().Select(Function(x) "<th>" & x.ColumnName & "</th>").ToArray()) & "</tr>")
For Each row As DataRow In dt.Rows
    sb.Append("<tr>" & String.Join("", row.ItemArray.Select(Function(f) "<td>" & f.ToString() & "</td>")) & "</tr>")
Next
sb.Append("</table>")

You can add your own styles to this pretty easily.

WATYF
  • 409
  • 1
  • 5
  • 16
-1

As per my understanding you need to show 3 tables data in one html table using asp.net with c#.

I think best you just create one dataset with 3 DataTable object.

Bind that dataset to GriView directly on page load.

  • Perhaps you could add a code example, as this answer is pretty vague. – Eric Hauenstein Sep 02 '14 at 14:09
  • Here is the example to add datatable to dataset. http://msdn.microsoft.com/en-us/library/aeskbwf7(v=vs.110).aspx For binding dataset to gridview http://stackoverflow.com/questions/11099619/how-to-bind-dataset-to-datagridview-in-windows-application – Socialwebi Sep 02 '14 at 14:12