Day 10: HtmlHelper Extension Methods

This is an installment in a 30 day series on Bootstrap and the MVC Framework. To see more, check out Day 0 for an index.

As we extracted our Person template we stubbed in a placeholder for a person’s avatar. Rather than creating our own system for uploading, resizing and storing the images, we’ll use a commonly used service on the internet called Gravatar to display one that many users might already have.

A Gravatar Extension Method

The format for Gravatar images is as follows:

http://www.gravatar.com/avatar/MD5HASH?options

The MD5 hash is computed based on their email address, and there are a few options worth noting. Below are the querystring parameters we’ll be using to generate our image.

  • Default Image: the image or type of image to use or generate if there isn’t a Gravatar for the specified email address.
  • Size: the size of the image to be returned, always a square.
  • Rating: users self-specify their rating and can use different avatars for different audiences.

We’ll represent those options in an class that we’ll use as a parameter.  Create a Helpers folder, then create a class called GravatarOptions in it.

public class GravatarOptions
{
    public string DefaultImageType { get; set; }
    public string RatingLevel { get; set; }
    public int Size { get; set; }

    public class DefaultImage
    {
        public const string Default = "";
        public const string Http404 = "404";
        public const string MysteryMan = "mm";
        public const string Identicon = "identicon";
        public const string MonsterId = "monsterid";
        public const string Wavatar = "wavatar";
        public const string Retro = "retro";
    }

    public class Rating
    {
        public const string G = "g";
        public const string PG = "pg";
        public const string R = "r";
        public const string X = "x";
    }

    internal static GravatarOptions GetDefaults()
    {
        return new GravatarOptions
        {
            DefaultImageType = GravatarOptions.DefaultImage.Retro,
            Size = 150,
            RatingLevel = GravatarOptions.Rating.G
        };
    }
}
And finally, add a class called GravatarHelper, then add the following code:
public static class GravatarHelper
{
    public static HtmlString GravatarImage(this HtmlHelper htmlHelper, string emailAddress, GravatarOptions options = null)
    {
        if (options == null)
            options = GravatarOptions.GetDefaults();

        var imgTag = new TagBuilder("img");

        emailAddress = string.IsNullOrEmpty(emailAddress) ? string.Empty : emailAddress.Trim().ToLower();

        imgTag.Attributes.Add("src",
            string.Format("http://www.gravatar.com/avatar/{0}?s={1}{2}{3}",
                GetMd5Hash(emailAddress),
                options.Size,
                "&d=" + options.DefaultImageType,
                "&r=" + options.RatingLevel
                )
            );

        return new HtmlString(imgTag.ToString(TagRenderMode.SelfClosing));
    }

    // Source: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx
    private static string GetMd5Hash(string input)
    {
        byte[] data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(input));
        var sBuilder = new StringBuilder();
        for (int i = 0; i < data.Length; i++)
        {
            sBuilder.Append(data[i].ToString("x2"));
        }
        return sBuilder.ToString();
    }
}
Extension methods accept the type you are extending as the first parameter and operate on the instance of the object, though they are defined as a static. This may help understand why you must pass the first parameter as the type you want to extend with the _this_ modifier.  You can then optionally any additional parameters that you will need to work with. For us, we’re just accepting the email address and a GravatarOptions instance. In our helper, we create a tag builder for images, then build the URL based on the options the user has provided. If they haven’t provided the parameter, we simply load a set of defaults from our options class. _**Note**: This is an overly-simplified version of [Andrew Freemantle’s](https://github.com/AndrewFreemantle) work on his [Gravatar-HtmlHelper](https://github.com/AndrewFreemantle/Gravatar-HtmlHelper) project on GitHub. Please visit his project for a more complete implementation._ ## Using the Gravatar in our Person Template Our HTML helper will work like any other HTML helper, but we need to let the MVC Framework know where to find it. To do so, we’ll have to go and add the namespace to our Web.Config in our Views folder (located at Views\Web.Config). We could also add a using statement to each page where we want to use our helper, but adding it to the web config file makes it globally available throughout our views. Add the following to the Razor namespaces section in that file:
<add namespace="SimpleSite.Helpers"/>

We’ll need an email address, so add the following property in Person.cs.

public string EmailAddress { get; set; }

Next, jump back into your SimpleController class and update the instantiation of the person object so that they have a value in there:

var person = new Person
{
FirstName = “Billy Jo”,
LastName = “McGuffery”,
BirthDate = new DateTime(1990, 6,1),
LikesMusic = true,
EmailAddress = Bill@jo.com,
Skills = new List<string>() { “Math”, “Science”, “History” }
};

Now, update your Person.cshtml template by replacing the image tag with the following:

@Html.GravatarImage(Model.EmailAddress)

Then, pop into your browser to see the fruits of your effort!

image

The gravatar image will be updated for any email address that you put in. You can try playing with the different defaults, your own email address or other options.

Another note: I wouldn’t actually advocate creating a full implementation of an HtmlHelper method for Gravatars. The point of this exercise was to walk through a simple implementation, but there are much more complete helpers available on NuGet, ready to be deployed to your app. If you’d like to work on one of them, they are pretty much all open source, so feel free to contribute!

Next Steps

Great, now we have a basic extension method to make outputting a Gravatar much more simply, but we’re going to need to address our Index view. It’s typically a list of entities, with a link to a details view, not just a single item. Tomorrow, we’ll tackle getting that list set up and move the single view version to it’s own action and view.