ASP.NET Weblogs

Welcome to ASP.NET Weblogs Sign in | Join | Help
in Search

The Technical Adventures of Adam Weigert

LINQ: Expressive Html Tag Building

I hate building HTML tags in code, but it needs to be done. I just wanted to make it a little cleaner. So I came up with this method that utilizes LINQ expressions to generate the attributes for the tag. This is really only clean with simple tags, but I use it with my other tag building methods to keep them clean as well. I've seen others look for something like this and thought it'd be helpful posting it here. I haven't deeply tested this code but it shows the general concept and I'm sure it needs cleaned up a little. The following is an example calling the Tag method:

   1:  Tag( "a", "The Technical Adventures of Adam Weigert", href => "http://weblogs.asp.net/adweigert/" );
   2:  Tag( "div", "LINQ Expressions Rock", style => "font-size: 250%; font-weight: bold;", id => "title" );

This is the actual method, I love how simple the LINQ expression makes building the attributes.

   1:  public string Tag( string tagName, string innerHtml, params Expression<Func<string, string>>[] attributes )
   2:  {
   3:      XElement tag = new XElement( XName.Get( tagName, string.Empty ) );
   4:      
   5:      if ( attributes != null )
   6:      {
   7:          foreach ( var attribute in attributes )
   8:          {
   9:              string attributeName = attribute.Parameters[ 0 ].Name;
  10:              string attributeValue = attribute.Compile()(string.Empty);
  11:   
  12:              tag.SetAttributeValue( XName.Get( attributeName, string.Empty ), attributeValue );
  13:          }
  14:      }
  15:   
  16:      if ( !string.IsNullOrEmpty( innerHtml ) )
  17:      {
  18:          tag.Add( XElement.Parse( "<xml>" + innerHtml + "</xml>" ).Nodes() );
  19:      }
  20:   
  21:      return tag.ToString();
  22:  }

Update: Used XElement instead, and better innerHtml handling. Thanks to everyone that helped improve this method.

Comments

 

paul.vencill said:

Why not just build as XElement(s) and call ToString?

January 1, 2009 11:31 PM
 

Xtek said:

I have to agree.  I think using XElement/XDocument would be the way to go as it also gives you many XML-related methods/properties to help you out forming valid XHTML/XML/HTML.

January 2, 2009 1:31 AM
 

Ben Amada said:

You could optionally replace the foreach loop with a Linq query ...

if (attributes != null)

{

tag.Append(" " +

string.Join(" ",

(from a in attributes

select new

{

value =

string.Format("{0}=\"{1}\"",

a.Parameters[0].Name,

HttpUtility.HtmlEncode(a.Compile()(string.Empty)))

}).Select(v => v.value).ToArray()));

}

January 2, 2009 1:46 AM
 

Web Development Community said:

You are voted (great) - Trackback from Web Development Community

January 2, 2009 2:16 AM
 

adweigert said:

I updated it to use XElement instead.

January 2, 2009 8:12 AM
 

Ben Amada said:

With the new XElement version, an XmlException is thrown every time in the Try block.

"Data at the root level is invalid. Line 1, position 1."

Having an exception occur everytime is very expensive.  You'd be better off just using tag.Add(innerHtml) or even tag.Value = innerHtml.

January 2, 2009 1:40 PM
 

adweigert said:

I guess I could change the param to innerText instead, because that's what tag.Add(innerHtml) or tag.Value = innerHtml would do (it doesn't parse the value). There is no InnerXml property I could set as far as I know.

January 2, 2009 1:45 PM
 

Ben Amada said:

Why is any parsing necessary.  The documentation explains XElement.Parse() expects a string containing XML, like "<Root> <Child> </Child> </Root>" (hence the exception that is occurring when passing non-Xml data into it).

msdn.microsoft.com/.../bb468714.aspx

If you use tag.Add(innerHtml) and innerHtml contains characters that need to be Html encoded (such as an ampersand), you'll see those characters encoded when running ToString().  Other than this Html encoding which is already provided, I don't see why any parsing would be needed when setting innerHtml.

January 2, 2009 2:04 PM
 

adweigert said:

Tag("div", "<h1>Hello World</h1>") will render text as "<h1>Hello World</h1>". Like I said, simply changing the parameter name to text or innerText would allow me to always just use tag.Value = text and only use this for simple tags.

January 2, 2009 2:55 PM
 

Ben Amada said:

Are you sure?  For Tag("div", "<h1>Hello World</h1>"), I get the following value:

"<div>&lt;h1&gt;Hello World&lt;/h1&gt;</div>"

Maybe you're looking at the value that is rendered by your browser?  The browser is of course going to decode any encoded Html characters ... which is expected behavior.

January 2, 2009 3:05 PM
 

Ben Amada said:

Taking a second look, I guess what you mean is you may want an actual <h1> tag rendered within a <div>.  I think the line of code below will handle innerHtml regardless if innerHtml is "some arbitrary text" or "<h1>Hello World</h1>".  You can see the line below just wraps innerHtml into tags so Parse() won't fail.

tag.Add(XElement.Parse("<xml>" + innerHtml + "</xml>").FirstNode);

Having an innerText parameter (in addition to innerHtml) might be handy depending on if you want the literal text <h1>Hello World</h1> to display or Hello World in an <h1> tag.

January 2, 2009 3:47 PM
 

Ben Amada said:

A more robust line of code would be:

tag.Add(XElement.Parse("<xml>" + innerHtml + "</xml>").Nodes());

This will also handle an innerHtml value like the one below.  The line of code in my last comment using FirstNode would only return the first <h1> tag.

<h1>Hello World</h1><h1>Another h1</h1>

January 2, 2009 4:58 PM
 

adweigert said:

Thanks guys, I've updated the code.

January 3, 2009 9:25 AM
 

nolovelust said:

im getting

CS0246: The type or namespace name 'var' could not be found (are you missing a using directive or an assembly reference?)

eror at

foreach ( var attribute in attributes )

any idea why?

January 11, 2009 8:46 AM
 

adweigert said:

I would imagine you aren't using the .NET 3+ compiler.

January 11, 2009 9:03 AM
 

nolovelust said:

i am using .net 3.5

January 11, 2009 12:49 PM