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.

If you liked this blog post then please Subscribe to this blog.
posted on Wednesday, February 18, 2009 4:34 AM | Filed Under [ ASP.NET ASP.NET MVC Tips ]

Comments

Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Farrio
on 2/18/2009 2:41 PM

Thanks Stephen :)
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by chandradev
on 2/18/2009 3:40 PM

This is very nice code.
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Shiju Varghese
on 2/18/2009 5:54 PM

Stephen,
Nice tip.
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Murat
on 2/18/2009 10:16 PM

nice to know. Thanks.
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Nirmal Shah
on 2/19/2009 12:34 PM

Thanks Stephen...This will help.
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Evan Freeman
on 2/19/2009 2:14 PM

So what does this do if you don't supply the tilde? Return the relative pah you supply? Hmm interesting. I agree good tip.

Gravatar
# Thanks
Posted by Bhupesh
on 2/19/2009 5:13 PM

Thanks for your help
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Jana
on 2/19/2009 5:52 PM

New joinee
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Marc Brooks
on 2/19/2009 6:32 PM

I know this is completely aside from the point of the article, but why would an img tag _always_ need an id? In fact, when (rarely) would it need one?
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Stephen.Walther
on 2/20/2009 4:54 AM

@Marc -- Having an Id on an tag is useful when you need to refer to it in your Cascading Style Sheet file or JavaScript code. Sounds like you would want an overload of the Image() method without an id parameter which would only require three more lines of code :)
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Bob Armour
on 3/5/2009 1:58 AM

Stephen,
Interesting article - made even more interesting by the fact that I've just read an asp.net article (http://www.asp.net/learn/mvc/tutorial-35-cs.aspx) about Html helpers and was wondering why it didn't resolve the URLs internally - to reduce repetition in the aspx pages.

A bit of searching on the subject brought me to this page - imagine my surprise when I found that the code was not only almost identical to the orignal articel, but addressed the centralisation of URL resolution.

I assume that the original tutorial was written before this page - so, how about updating the tutorial?
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Chris McDermott
on 4/15/2009 5:54 AM

Stephen, you said "Notice that the UrlHelper.Content() method does use the VirtualPathUtility class. So this has consequences for testability"

I'm migrating up from Preview 5 where an ImageHelper that I had written, like yours using the Url.Content, passed all my unit tests without a great deal of mocking or context setup. I'm now finding in v 1.0 that they are failing and Url.Content is returning an empty string.

How have you unit tested your helper?
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by rickj1
on 4/20/2009 10:19 PM

I have been looking for a Image control and code for displaying images from a data base with a GUID for UserId I ca get the image tag to show but not the image I've run out of angles any ideas
thanks following your book up to chapter 6
it is bringing everything into light
still have a long way to go
thanks agian
Gravatar
# re: ASP.NET MVC Tip #47 – Using ResolveUrl in an HTML Helper
Posted by Jason Monroe
on 4/29/2009 11:39 AM

It should be noted, that VirtualPathUtility.ToAbsolute() will choke and die when trying to resolve a url with querystring parameters..

In other words, something like this:
~/Handlers/Imagehander.ashx?id=432432

when passed in will fail miserably.

We use a custom PathHelper class to wrap VirtualPathUtility.. Basically, it splits off the query string params, translates the root of the url and re-appends the split query string

public static class PathHelper
{
public static string ToAbsolute(string path, string root)
{
if (path.IndexOf('?') == -1)
path = VirtualPathUtility.ToAbsolute(path);
else
path = VirtualPathUtility.ToAbsolute(path.Substring(0, path.IndexOf('?')), root) +
path.Substring(path.IndexOf('?'));

return path;
}
}

So your ImageHelper's Image() method could be written like this:

public static string Image(this HtmlHelper helper, string id, string url, string alt, object htmlAttributes)
{
// Create our builder
var img = new TagBuilder("img");
img.GenerateId(id);
// Apply our attributes
img.MergeAttribute("src", PathHelper.ToAbsolute(url, helper.ViewContext.RequestContext.HttpContext.Request.ApplicationPath));
img.MergeAttribute("alt", alt);
img.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// return rendered tag
return img.ToString(TagRenderMode.SelfClosing);
}

Comments have been closed on this topic.