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

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.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Piece of Broken Wood

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.

WebGrid containing a list of FAQs

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.

  • I really don't like the "magic strings." They are way too fragile and produce typos that cause runtime errors.
  • I like the IntelliSense with my Views.
  • When I compile, I know that I named the properties correctly because...well, I can compile (again, no magic strings). :-)

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!

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus