3

I'm trying to programmatically send an email of all of the dll files and their versions in a directory, recursively. I'd like to send the email as HTML output, using a table. Is there a good object-oriented way of doing this? I'd hate to write all of the tags by hand.

Something like:

private string getHTMLString()
{
    DirectoryInfo di = new DirectoryInfo("some directory");
    FileInfo[] files = di.GetFiles("*.dll", SearchOption.AllDirectories);
    foreach (FileInfo file in files)
    {
        Assembly assembly = Assembly.LoadFile(file.FullName);
        string version = assembly.GetName().Version.ToString();
    }
 }
David Hodgson
  • 10,104
  • 17
  • 56
  • 77
  • Once you have a given filename, it's a good idea to convert it into safe HTML text. While it's true that filenames can't contain many of the characters that are problematic when rendered as HTML, they can contain some, e.g. spaces, single quotes ('), and ampersands. – David R Tribble Jan 05 '10 at 00:15

8 Answers8

8

You can do it OO by instantiating a System.Web.UI.WebControls.Table, adding TableRows and TableCells and then calling Table.RenderControl into an HtmlTextWriter, but to be fair: That would suck :)

        var tbl = new System.Web.UI.WebControls.Table();

        DirectoryInfo di = new DirectoryInfo("some directory");
        FileInfo[] files = di.GetFiles("*.dll", SearchOption.AllDirectories);
        foreach (FileInfo file in files)
        {
            Assembly assembly = Assembly.LoadFile(file.FullName);
            string version = assembly.GetName().Version.ToString();

            var tr = new System.Web.UI.WebControls.TableRow();
            var tc = new System.Web.UI.WebControls.TableCell();
            tc.Text = HttpUtility.HtmlEncode(version);
            tr.Cells.Add(tc);
            tbl.Rows.Add(tr);
        }

        using (var ts = new StringWriter())
        using (var html = new System.Web.UI.HtmlTextWriter(ts))
        {
            // Not entirely sure about this part
            tbl.RenderControl(html);
            html.Flush();
            string htmlString = ts.ToString();
        }
Michael Stum
  • 177,530
  • 117
  • 400
  • 535
7

You can actually use the HtmlTextWriter and use the HtmlTextWriterTag enum to create the tags, without actually having to hand write the "<" and ">".

Example:

StringBuilder sb = new StringBuilder();

using (HtmlTextWriter w = new HtmlTextWriter(new StringWriter(sb)))
{
      w.RenderBeginTag(HtmlTextWriterTag.P);

      w.AddStyleAttribute(HtmlTextWriterStyle.Color, "red");

      w.RenderBeginTag(HtmlTextWriterTag.Span);

      w.Write("This is some text");

      w.RenderEndTag();

      w.RenderEndTag();
 }

 string html = sb.ToString();

It doesn't automatically create the HTML for you, but it does help you write HTML in a "more OO" way and it will help you (not ensure) write valid HTML (all tags have closing tags, etc).

bdowden
  • 993
  • 7
  • 11
7

It might be overkill, but you can use LINQ to XML to generate your HTML string... Here's your code, using LINQ and XElement to generate a simple table.

The only thing you need to be careful of is empty tags that are not valid as self-closing tags in HTML. For example, an empty TD: new XElement("td") will render as <td/> which is not valid HTML. You can fix this by inserting an empty string as content: new XElement("td", String.Empty) - this will output <td></td>.

private string GetHtmlString()
{
    DirectoryInfo di = new DirectoryInfo("some directory");
    FileInfo[] files = di.GetFiles("*.dll", SearchOption.AllDirectories);

    var container = new XElement("table",
        from file in files
        let assembly = Assembly.LoadFile(file.FullName)
        select new XElement("tr", 
            new XElement("td", file.FullName),
            new XElement("td", assembly.GetName().Version.ToString())
        )
    );

    return container.ToString();
 }
Joel Mueller
  • 28,324
  • 9
  • 63
  • 88
5

Something like this?

private string getHTMLString()
{
    DirectoryInfo di = new DirectoryInfo("some directory");
    FileInfo[] files = di.GetFiles("*.dll", SearchOption.AllDirectories);
    StringBuilder sb = new StringBuilder();
    sb.Append("<table>");
    foreach (FileInfo file in files)
    {
        Assembly assembly = Assembly.LoadFile(file.FullName);
        string version = assembly.GetName().Version.ToString();
        sb.AppendFormat("<tr><td>{0}</td><td>{1}</td></tr>", file.FullName, version);
    }
    sb.Append("</table>");
    return sb.ToString();

 }

Not really "object oriented" but I would argue the most logical.

DISCLAIMER: Compiled by hand

mletterle
  • 3,968
  • 1
  • 24
  • 24
  • Seems like the most straightforward way. I thought of writing something like an HTMLTableClass, which uses a StringBuilder to do something like the above. I was really hoping for some magic built-in library, but I guess it's just not there. Thanks. – David Hodgson Jan 05 '10 at 00:02
  • I'd strongly recommend doing HtmlEncode() on the file name before embedding it in HTML. – Cylon Cat Jan 05 '10 at 00:15
  • -1: I'd make it -1/2 if I could. -1 for using AppendFormat without any HTML Encoding. This can produce incorrect results depending on the contents of file.FullName and version. – John Saunders Jan 05 '10 at 01:13
2

Tags are tags. You'll have to write them.

You can use an HtmlTextWriter to do the actual writing, but there's no magical way to avoid that.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
1

What you are doing seems simple enough to simply do it by hand with a string buffer. If this were more complicated I'd suggest you use a template engine such as StringTemplate.

Jim Mitchener
  • 8,835
  • 7
  • 40
  • 56
1

You can use the XmlWriter to do this in a more OO-fashion:

StringBuilder sb = new StringBuilder();
XmlWriter xmlWri = XmlWriter.Create(sb);
xmlWri.WriteStartElement("html");
{
    xmlWri.WriteStartElement("body");
    {
        xmlWri.WriteAttributeString("bgcolor", "black");

        // More html stuff
    }
    xmlWri.WriteEndElement(); // body
}
xmlWri.WriteEndElement(); // html
xmlWri.Flush();
xmlWri.Close();
string html = sb.ToString();

Edit: It does get rather cumbersome to do this, and in the future if you want to change the HTML content you'll have to do it by code. A better way is to read in a pre-formatted HTML document with place-holders that map to the columns in the table, so all you'd need is a way to loop through all the columns and do a find/replace against the place-holders in the HTML document.

ajawad987
  • 4,439
  • 2
  • 28
  • 45
  • Yeah, I am personally of the opinion that the above is over-kill for the application. Using a template defined either in a file or the app.config is a good suggestion depending on the use case though. – mletterle Jan 05 '10 at 00:05
  • 1
    Templates can work, but you must be aware that you're not just working with strings. You must follow HTML encoding rules. Using a DOM or HTML API will do that part for you. – John Saunders Jan 05 '10 at 01:15
0

I'm currently writing a library that tackles that problem exactly. It allows you to construct your HTML with collection initializers, so it can be pretty concise. It also supports CSS styling, element attributes as optional parameters, etc. It's LGPL'd, so you can use it anywhere.

Your code will look like:

private string getHTMLString()
{
    DirectoryInfo di = new DirectoryInfo("some directory");
    FileInfo[] files = di.GetFiles("*.dll", SearchOption.AllDirectories);

    var table = new Table();

    foreach (FileInfo file in files)
    {
        Assembly assembly = Assembly.LoadFile(file.FullName);
        string version = assembly.GetName().Version.ToString();

        table.Add(new Tr { new Td { file.FullName }, new Td { version } });
    }

    return table.Render();
}

HTML++ on SourceForge.

Vladislav Zorov
  • 2,998
  • 19
  • 32