Html.Awkward

Thursday, February 17, 2011

I’ve seen the HTML helpers in ASP.NET MVC drive developers to madness. For example:

@Html.ActionLink("Detail", "Detail", "Car", new { id = 5 })

... produces ...

<a href="/Home/Detail?Length=3" id="5">Detail</a>

 

Notice "Length=3" in the query string? That's probably not what you expected. We see this output because we are calling the ActionLink overload where “Car” is interpreted as route data and the anonymous object we want to use for route data is instead interpreted as the HTML attributes parameter.

If you add one more parameter (a null on the end):

@Html.ActionLink("Detail", "Detail", "Car", new { id = 5 }, null)

... it renders ...

<a href="/Car/Detail/5">Detail</a>

And this is probably the output you want to see, because the third parameter went from being routing data to controller name, the fourth parameter went from being HTML attributes to route data, and the last parameter is now HTML attributes. All because we passed an empty (null) value as an additional parameter.

Kind of weird, don't you think?

But it doesn't stop with the overloads.

Html.Help!

Tell me if I'm missing something obvious...

Let's say you want to write a custom HTML helper to render an image tag with a specific purpose.

@Html.FlightChart("TopDestinations")

Let's also say the image tag needs to start with its src attribute set to a well known image. Sounds easy, but if you want to ensure the path is correct you might want to make a call to Url.Content - except the Url helper isn't available inside an Html helper method.

public static MvcHtmlString FlightChart(
    this HtmlHelper helper, 
    string chartName)
{
    var rollerPath = "?"; // Resolve(~/Content/roller.gif) ?
    
    var div = new TagBuilder("div");
    var image = new TagBuilder("img");
    image.MergeAttribute("data-chart", chartName);
    image.MergeAttribute("src", rollerPath);
    div.InnerHtml = image.ToString(TagRenderMode.SelfClosing);
    return MvcHtmlString.Create(div.ToString());
}

 

You could always make FlightChart an extension method for UrlHelper, but it feels strange to produce HTML markup from a Url helper. Another option is to construct a new instance of UrlHelper inside the the Html helper.

var rollerPath = 
    new UrlHelper(helper.ViewContext.RequestContext)
                 .Content("~/Content/roller.gif");   

But it seems silly to create a new UrlHelper when one is already available somewhere else inside of the request. What if we grabbed a reference to the existing UrlHelper?

var urlHelper = ((Controller) helper.ViewContext.Controller).Url;
var rollerPath = urlHelper.Content("~/Content/roller.gif");      

Eek! That's even worse!

What if we just forget about UrlHelper and turn to our old friend VirtualPathUtility?

var rollerPath = VirtualPathUtility.ToAbsolute("~/Content/roller.gif");

Better - but don't try to run any unit tests on the above code.


Comments
gravatar Damien Guard Sunday, February 20, 2011
That helper is open to HTML injection because it accepts parameters and blindly injects them into HTMl with String.Format.

Use TagBuilder instead, it is your friend.

[)amien

[)amien
gravatar scott Sunday, February 20, 2011
Thanks, @Damien, updated.
gravatar Jeremy D. Miller Sunday, February 20, 2011
Scott,

In all seriousness, go look at the equivalents in FubuMVC. *They* don't suffer from the same awkwardness as the OOTB experience in ASP.Net. With or without the Html convention support, you could use Fubu's helpers inside ASP.net MVC apps. Javier even has an MvcTurbine add in for just that.

If nothing else, go look at HtmlTags
gravatar Aleš Roub&#237;ček Sunday, February 20, 2011
In first case I prefer to stay as much in HTML as posible, so it's better to avoid Html.ActionLink helper at all. I prefer to write it like Detail.

In second case why not don't you promote the rollerPath to parameter?
tobi Sunday, February 20, 2011
The MVC folks just do not use their product enough. You only find such design errors if you dogfood.
gravatar Moshe Sunday, February 20, 2011
I always preferred the following form:

@Html.ActionLink("Detail", "Detail", new { controller = "Car", id = 5 })
gravatar scott Monday, February 21, 2011
@Jeremy:

I will, absolutely.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!