Real-World Refactoring: Use a generic HtmlHelper For Your WebGrid

January 4th, 2016

Displaying an update link can be very repetitive for every one of your WebGrids in ASP.NET MVC, but this refactoring makes it a bit easier.

When I worked on my WebGrid series, I touched on a number of different techniques on how to make it a commercial grade WebGrid.

Some readers mentioned why not have feature A added to the WebGrid? or how about Feature B? My readers are so damn smart! I love it!

Well, those features are coming, so stay tuned for more WebGrid goodness.

It will probably be another link added to the WebGrid series that points to a future post.

Click To Update

While I was working on a WebGrid feature, I ran across some smelly, and I mean smelly, code.

Check this out.

My standard way of editing an item in a webgrid is to make the first column or primary property a link to take them to the detail page.

Sounds simple, right?

So I coded this HtmlHelper.

public static MvcHtmlString EditFaqItem(this HtmlHelper helper, Faq item)
{
    if (item == nullreturn MvcHtmlString.Create("N/A");
 
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
 
    var anchor = new TagBuilder("a");
    anchor.Attributes.Add("href", urlHelper.UpdateFaqUrl(item));
    anchor.Attributes.Add("title", item.Question);
    anchor.InnerHtml = item.Question;
 
    return MvcHtmlString.Create(anchor.ToString(TagRenderMode.Normal));
}

My webgrid can easily call this EditFaqItem to create a link to the edit page so my webgrid column definition would look like this:

grid.Column("Question", format: (item) => Html.EditFaqItem(item.Value as Faq), canSort: false)

Now, that was just for one type (The FAQ class). I use web grids throughout my entire codebase.

Can you see where this is going?

As time moved on, I started getting a LOT of EditXXXXItem made for a specific type.

Too much code for something so small.

Time to Refactor! 

After looking over a lot of these Update HtmlHelpers links, I noticed I only required three parameters:

  1. The Object
  2. Anchor Text
  3. and Url

For a simple test, this was definitely something that needed to be refactored.

After some time, I came up with the following HtmlHelper called UpdateLink.

public static MvcHtmlString UpdateLink<T>(this HtmlHelper helper,
    T item, Func<Tstring> anchorAndTitleText,
    Func<UrlHelperstring> urlFunc, Func<Tstring> tooltip = nullwhere T : class
{
    if (item == nullreturn MvcHtmlString.Create("N/A");
 
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);
 
    var anchor = new TagBuilder("a");
    anchor.Attributes.Add("href", urlFunc(urlHelper));
 
    var text = anchorAndTitleText(item);
    var title = text;
    if (tooltip == null)
    {
        title = anchorAndTitleText(item);
    }
    anchor.Attributes.Add("title", title);
    anchor.InnerHtml = anchorAndTitleText(item);
 
    return MvcHtmlString.Create(anchor.ToString(TagRenderMode.Normal));
}

There are a number of reasons I went with Func<> for the majority of the parameters.

Since we're now using generics, we can pass in any object and have it return a link to our update page.

One Caveat

If you look at the column definition above, you'll notice that I like to keep code out of my Views.

This new UpdateLink HtmlHelper presents a problem because it's passing in a lot of parameters and adding more code to the View which feels annoying.

For now, I refactored the EditFaqItem() method.

public static MvcHtmlString EditFaqItem(this HtmlHelper helper, Faq faq)
{
    return helper.UpdateLink(faq,
        e => e.Question,
        y => y.UpdateFaqUrl(faq),
        u => String.Format("Update {0}", u.Question));
}

The WebGrid column definition in your Razor View doesn't even need touched.

If you decide to bypass the extra layer, you might as well go with the UpdateLink in the View, but in my eyes, it just muddies the View with even more code. 

grid.Column("Question", format: (item) => 
    Html.UpdateLink(item.Value as Faq, 
        e=> e.Question, 
        y=> y.UpdateFaqUrl(item.Value as Faq), 
        u=> String.Format("Update {0}", u.Question)), canSort: false)

I leave this decision for the reader to determine the best option based on your coding practices.

Conclusion

In this post, we took a number of HtmlHelpers and simplified it down to a simple HtmlHelper using generics. It does take a little more code, but I believe it's better for two reasons: It wraps our UpdateLink HtmlHelper making it easier and simpler to create links, and it removes our need for "magic strings."

Did this make sense? Post your comments below and Happy New Year!