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.
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.