Yesterday, I was creating a simple HTML helper for displaying images. Nothing fancy. The code for the helper is contained in Listing 1.
Listing 1 – Helpers\ImageHelper.cs
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplication1.Helpers
{
public static class ImageHelper
{
public static string Image(this HtmlHelper helper, string id, string url, string alternateText)
{
return Image(helper, id, url, alternateText, null);
}
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes)
{
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", url);
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.SelfClosing);
}
}
}
This helper method uses a TagBuilder to render a standard HTML <img> tag. You can supply the image Id, URL, AlternateText, and HTML attributes when calling the Html.Image() helper.
However, I hit one roadblock. I wanted to enable users to pass application relative URLs to the Html.Image() helper. For example, I wanted users to be able to call the Html.Image() helper within an MVC view like this:
<%= Html.Image(“myImage”, “~/Content/Rover.jpg”, “Picture of Rover”) %>
When you use an ASP.NET Web Forms Web Control, you can assign paths to properties that contain the tilde character (~). For example, you can use a path like ~/Content/Rover.jpg. The control replaces the tilde with the application path automatically. So, you get /MyApp/Content/Rover.jpg. Being able to create application relative URLs is very useful because you might need to move your application to a new virtual directory in the future and you want your URLs to continue to work.
The ASP.NET Web Forms framework uses the Control.ResolveUrl() method to do this. Unfortunately, because this is a method of the Control class, this method is not available within an HTML helper.
I was stuck until Levi -- one of the super genius developers on the ASP.NET MVC team -- explained that I could use the Url.Content() method to do the same thing as the Control.ResolveUrl() method. So, here is how I can rewrite the Html.Image() helper to support application relative URLs:
Listing 2 – Helpers\ImageHelper.cs
using System.Web.Mvc;
using System.Web.Routing;
namespace MvcApplication1.Helpers
{
public static class ImageHelper
{
public static string Image(this HtmlHelper helper, string id, string url, string alternateText)
{
return Image(helper, id, url, alternateText, null);
}
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes)
{
// Instantiate a UrlHelper
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", urlHelper.Content(url));
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.SelfClosing);
}
}
}
Notice that I create an instance of a URL helper like this:
// Instantiate a UrlHelper
var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
And then I can use the URL helper to resolve a URL like this:
// Add attributes
builder.MergeAttribute("src", urlHelper.Content(url));
Using UrlHelper.Content() does the same thing as Control.ResolveUrl(). I became curious about how the UrlHelper.Content() method worked internally. Because all of the the ASP.NET MVC framework source code is available to be downloaded from www.CodePlex.com, I was able to look up the implementation of the UrlHelper.Content() method quickly. It looks something like:
public string Content(string contentPath)
{
if (string.IsNullOrEmpty(contentPath))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentPath");
}
if (contentPath[0] == '~')
{
return VirtualPathUtility.ToAbsolute(contentPath, this.RequestContext.HttpContext.Request.ApplicationPath);
}
return contentPath;
}
Notice that the UrlHelper.Content() method does use the VirtualPathUtility class. So this has consequences for testability.