Conventional HTML in ASP.NET MVC: Building tags

In order to provide conventional HTML, you have to build on a solid foundation. Because most of the time I want to tweak my rules in C#, it’s important that I’m able to tweak the output of HTML programmatically. This is where ASP.NET and Fubu MVC start to diverge.

In ASP.NET MVC, all of the HtmlHelper methods return a single type – the MvcHtmlString. It’s really only concerned with making sure that a raw string that contains HTML is only encoded for output once.

But if you actually want to modify that output, you’re in a bit of a spot. For example, you might want Html.ActionLink, but slightly modify the output. You can pass parameters to modify it, but only through an anonymous object/dictionary thingy:

Html.ActionLink("Edit", "Edit", null, new { @class="nav" })

It’s a bit clumsy, and difficult to modify after the fact. Once ActionLink is called, you only have the opaque, raw string to work with. Underneath the covers, MVC does use an object to build up the HTML – the TagBuilder object. Unfortunately, none of the existing HtmlHelper methods allow you to talk about things in terms of a TagBuilder object, it’s all internal.

Enter HtmlTags – the Fubu MVC HtmlTags library. It allows us to talk about an HTML tag in terms of a model of a tag, programmatically modifying/extending/replacing and waiting until the last possible moment to take that and turn it into HTML.

Programmatic HTML

Instead of using the existing HtmlHelper extensions, let’s create our own. We’ll come back to form/input/display methods later, but what about something like a link?

First, our own link method:

public static HtmlTag Link<TController>(
    this HtmlHelper helper,
    Expression<Action<TController>> action,
    string linkText)
    where TController : Controller
{
    var url = LinkBuilder.BuildUrlFromExpression(
      helper.ViewContext.RequestContext,
      RouteTable.Routes, action);
    return new HtmlTag("a", t =>
    {
        t.Text(linkText);
        t.Attr("href", url);
    });
}

A couple of things we see here. First, instead of string building, we’re dealing with an actual object. We instantiate the tag with its element, then initialize with a couple of values. We could have also done:

public static HtmlTag Link<TController>(
    this HtmlHelper helper,
    Expression<Action<TController>> action,
    string linkText)
    where TController : Controller
{
    var url = LinkBuilder.BuildUrlFromExpression(
      helper.ViewContext.RequestContext,
      RouteTable.Routes, action);
    return new HtmlTag("a")
      .Text(linkText)
      .Attr("href", url);
}

HtmlTags uses a syntax similar to jQuery, where we chain methods to continuously manipulate the object. In our view, we then use it just like our normal ActionLink methods:

@(Html.Link<ContactsController>(c => c.Edit(Model.Id), "Edit"))

Those extra parenthesis are there because we’re using explicit generic parameters, and those pesky less than/greater than characters confuse our Razor parser.

At runtime, Razor automatically calls .ToString() on any expression. That’s when our HtmlTag object is converted to properly escaped/encoded HTML, but not before. This allows us to manipulate the HtmlTag however we like:

@(Html.Link<ContactsController>(
  c => c.Edit(Model.Id),
  "Edit")
  .Data("foo", "bar")
  .Attr("title", "Edit Me!")
  .AddClass("btn")
  .WrapWith("div"))

Because our HtmlTag is an object and not a string, we can programmatically manipulate tag, add children tags, add a wrapper tag, perform smart attribute manipulation (data attributes, CSS classes etc).

Additionally, because we’re building up objects, we can easily build up more HtmlHelper extensions:

public static HtmlTag ButtonLink<TController>(
    this HtmlHelper helper,
    Expression<Action<TController>> action,
    string linkText)
    where TController : Controller
{
  return helper.Link(action, linkText).AddClass("btn");
}

Finally, because our HtmlTags are objects, we can

*drumroll*

Test our HtmlHelper extension methods! Well, as long as we figure out how to build up an HtmlHelper object ourselves (not horrible, but not easy) (actually it’s horrible. sorry).

The nice thing about HtmlTags are they’re just a simple library for creating HTML tags, that’s it. You can replace your usage of the existing HtmlHelper extension methods whenever you need to build a custom HtmlHelper extension.

With HtmlTags in place, we’re ready to build on top of this solid foundation and create more interesting conventions with the full Fubu MVC goodness.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://wynia.org jwynia

    I keep wondering how the helper approach took over mindshare instead of partial views. It’s weird to me to have some of the output HTML come from views and some come from HTML helper classes in C#. When all of the HTML is in the view layer, the question of presentation is separated in a way that the HTML helper approach doesn’t. And, the question of how to change the appearance of an element is answered the same way whether the HTML is part of a “component” or “control” or “just part of the view”.

    • http://solutionizing.net/ Keith Dahlby

      Just because there’s not a separate view file doesn’t mean HTML helpers are any less part of the view layer. Partials’ dependence on a view model makes them ill-suited for bits of markup that require multiple inputs (a link’s title, text and href – it would be silly to need a LinkModel), and some markup is just too important not to test in ways that partials don’t support.

      • Ricardo Peres

        You can always have the partial view model as dynamic

    • Optimus Primus

      I agree with you. It’s like they cannot make up their mind where to “separate the concerns”. It’s interesting to see the different approaches though. I think HTMLHelpers are crap. Same thing different day…

  • Francois Joly

    I don’t really ‘see’ what’s so great about what you’re presenting here.

    “return helper.Link(action, linkText).AddClass(“btn”);”

    If you really want control over the outputted HTML why don’t you just use the Url.Action counterpart and type the HTML yourself. So if I want my buttons to have the “btn” and “btn-large” I have to recompile my application? I guess you could always have a “WithClasses(params string[] classes)” method.

    “.WrapWith(“div”)”

    What’s wrong with “[link]“?. I don’t really see the value in adding a dependency to my projects just so I can use extension methods instead of typing the HTML myself. Well the only advantage would be type safety.

    • jbogard

      If you want your buttons to have the “btn” class – yes – because you’re modifying them everywhere.

      MVC has those overloads to pass in attributes with the funky “new {}” stuff, but you’d have to add that _everywhere_. But that still doesn’t solve the issue of once MVC creates the HTML string, it’s essentially read-only. We have a lot of cases where we have some default behavior, but want to add custom stuff in certain places. Because MVC doesn’t expose things in this manner, it’s actually quite tough to really extend the existing HtmlHelper extensions.

  • Harry McIntyre

    I’ve been using CsQuery to do something similar(ish). I think there’s scope for an entire View Engine where you just manipulate a DOM model using c#, calling out to traditional helper partials where necessary.

  • Pingback: Conventional HTML in ASP.NET MVC: Building tags - Program In .NET

  • NOtherDev

    I like this approach very much. I’ve also proposed quite a similar API to build HTML structure in code exactly resembling it – http://notherdev.blogspot.com/2012/03/loquacious-html-builder-based-on-xsd.html. It was a sketch of implementation that could also be bound to MVC helpers infrastructure to achieve the same result.

  • Optimus Primus

    This is really cool information. I am dealing with Custom HTMLHelpers that do all sorts of crazy things. It would be cool to have a better handle on HTMLHelpers as they are nasty little creatures…

  • Peter

    Hi,

    Hi would you use this without StructureMap…with any other IoC…I cannot get the dependencies to be aligned…would you know what needs to be setup?

    Thnx!