Comparing MVC 3 Helpers: Using Extension Methods and Declarative Razor @helper Syntax

HTML Helpers provide a clean way to encapsulate view code so you can keep your views simple and markup focused. There are lots of built in HTML Helpers in the System.Web.Mvc.HtmlHelper class, but one of the best features is that you can easily create your own helpers. While you've been able to create your own helpers since MVC 1 using extension methods, the Razor view engine gives you a new option to create helpers using the @helper keyword.

Extension Method helpers

Previous versions of ASP.NET MVC allowed you to do that by writing extension methods on the HtmlHelper class, and that's still available in MVC 3. Helpers are just methods which return strings, so they're pretty easy to set up. Let's look at an example from the MVC Music Store - a helper which which truncates a string to make sure it's smaller than a specified length.

2011-03-23 13h31_39

The extension method needs to be in a static class, and I generally like to create a separate folder for organization.

2011-03-23 12h56_37

The code is standard extension method syntax - the first parameter uses the this keyword to indicate that it's extending an HtmlHelper, and the rest is just a simple method that returns a string. The method takes a string and a max length, chops the string if needed, and returns the result.

using System.Web.Mvc;
 
namespace MvcMusicStore.Helpers
{
    public static class HtmlHelpers
    {
        public static string Truncate(this HtmlHelper helper, string input, int length)
        {
            if (input.Length <= length)
            {
                return input;
            }
            else
            {
                return input.Substring(0, length) + "...";
            }
        }
    }
}

Note: Make sure you're extending the HtmlHelper defined in System.Web.Mvc and not System.Web.WebPages. The using statement is important.

Our views can make use of this by either importing the namespace with the @using keyword to the views or by adding the helper's namespace to the pages/namespaces element in the web.config.

We could use this in the default Home / Index.cshtml view as below:

@{
    ViewBag.Title = "Home Page";
}
@using RazorHelpers.Helpers

<h2>@Html.Truncate(ViewBag.Message as string, 8)</h2>

Notice that we need to cast the ViewBag.Message as a string, or we'll get a compiler error when the page is rendered: CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'Truncate' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

2011-03-23 14h08_55

Alternative, if we wanted to add the namespace to the web.config in the Views folder, we could use the helper class in all our views without a @using statement. That would look like this:

<system.web.webPages.razor>
  <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
      <add namespace="System.Web.Mvc" />
      <add namespace="System.Web.Mvc.Ajax" />
      <add namespace="System.Web.Mvc.Html" />
      <add namespace="System.Web.Routing" />
      <add namespace="RazorHelpers.Helpers" />
    </namespaces>
  </pages>
</system.web.webPages.razor>

Razor Declarative Helpers

Razor allows us to implement this using either an in-page code block or in another file, using a more declarative syntax using an @helper block.

Razor helpers inline

To implement this helper inline, we'd just add the @helper block in the page as shown below.

@{
    ViewBag.Title = "Home Page";
}

@helper TruncateString(string input, int length)
{
    if (input.Length <= length) {
        @input
    } else {
        @input.Substring(0, length)<text>...</text>
    }
}

<h2>@Truncate(ViewBag.Message, 8)</h2>

The code is very similar, but there are a few differences:

  • I've tightened it up a bit since it's able to take advantage of Razor syntax to directly write out the value rather than return a string
  • We also don't need to bother with the this HtmlHelper business because it's not an extension method.
  • We don't need to cast the input as a string, since the Razor engine can figure that out for us on the fly
  • Since it's not an extension method, we directly call @Truncate() rather than @Html.Truncate()

Reusable helpers within your project: Use App_Code

You can move Razor helpers to another file within your project, but you'll need it to be in the App_Code folder. Right-click on the project and select "Add / New Folder" and call the folder "App_Code".

2011-03-23 14h22_50

You'll see that the App_Code folder gets a special icon:

2011-03-23 14h27_27

Now we can add a new Razor view to that folder using "Add / New Item / MVC 3 View Page (Razor)". You can name it what you want, but you'll be using the filename when you call the helper. I called mine CustomHelpers.cshtml.

Replace the generated code in the new CSHTML file with the @helper block we just used above, so the entire source of the new CustomHelpers.cshtml file will read as follows.

@helper TruncateString(string input, int length)
{
    if (input.Length <= length) {
        @input
    } else {
        @input.Substring(0, length)<text>...</text>
    }
}

In order to use that in a view, we call the helper using the name we gave the view page, like this:

@{
    ViewBag.Title = "Home Page";
}

<h2>@CustomHelpers.Truncate(ViewBag.Message, 8)</h2>

Notice that we don't need a @using statement to pull it in, we just call into the CustomHelpers class.

Turning Razor Helpers in reusable libraries

David Ebbo posted about how to turn your Razor helpers into libraries using the Single File Generator. This uses a custom build tool create a .cs or .vb file based on the view, which means you can move it wherever you'd like. Running that generator on the CustomHelpers.cshtml file would create CustomHelpers.cs, which could be used within my project, in a code library, etc.

Summary

Extension method based Custom HTML Helpers still have some benefits. They're code files, they can live in any directory, and since they're extension methods off the HtmlHelper class they're very discoverable - just type @Html. and see them in the list with all the other built in helpers.

Razor declarative helpers have some nice benefits, though:

  • You can easily whip one up inline in a view, then upsize it to a separate files in App_Code, then to a reusable library. I like the low barrier to entry - it removes any thought from writing helpers whenever possible.
  • The syntax is a little simpler - rather than thinking about writing an extension method, you just write some Razor code that writes out values and it all works.
  • Razor helpers are a little more dynamic due to the later compile - e.g. I didn't need to cast the ViewBag.Message to a string.
  • You don't need to worry about namespaces. That's one of the most common problems beginners run into in using the helpers in the MVC Music Store sample.

21 Comments

  • While the new @helper process is great help the few points that you summarize up with introduce bad habits. Here are a few weak points I see.

    1. Helpers should be created with plenty of consideration for its reuse and testability. A helper should never be made where it causes a developer to become complacent and unaware of what they are really doing with a helper method.

    2. While Razor helpers do suppress the need for explicit casting of data types, this can be very very bad for a beginner. ViewBag.Message being an arbitrary type can cause trouble when it is a type that is not easily converted over to a string. If my controller placed a StreamReader, for example, your helper would cry for mercy.

    3. While inline helpers remove the need for worry in regards to namespaces, you end up with a namespace if you want to reuse your helper, which is the entire point of writing one. Otherwise a little segment of inline logic would suffice.

    In the end it doesn't help. If you use the local app_code folder you are back at having to manage namespaces. Moving helpers out to a CS Library, and a namespace they need to handle. This new namespace is a little more vague then the standard. Locally you just give it the filename, and those change often so it can muck up things. Externally, now you are creating a namespace with DLL namespace + the_helper_file.

    Very good article about bringing things together. How does one go about unit testing these new helpers now?

  • The LINQ extension method .Take() will truncate any string for you. The neat thing is that if a string is 10 characters long and you want to take 15, it just returns the 10 without failing.

  • Didn't realise that helpers in ASP.NET MVC are declared as extension methods!
    I guess no IoC injection then :-(

  • I was trying to come up with a @helper the other day that took generic arguments, e.g.

    @helper LabelAndControlFor(...)
    {
    ...
    }

    But Razor thought I was going back to HTML as soon as it saw the generic angle brackets in the helper name. Went with an HtmlHelper in the end, but wondered if there was a way of escaping the generic brackets so that razor didn't get confused.

  • Hey Jon, I think you have a type when calling the razor helper, you defined it as TruncateString but you're calling it using "Truncate"

  • If you're extending the System.Web.Mvc.HtmlHelper class you should be returning an MvcHtmlString.

    return MvcHtmlString.Create("string");

  • Thanks for the post! It's what I was looking for. I have questions similar to others that have left comments. I won't ask though, since there have been no responses to any feedback.

    I will say though, I'm surprised to see the amount of published spam comments on this article!!!

  • This doesn't work for me.

    I created the app_code folder, created the view, put some code in it, but in none of the views are finding the CustomHelpers.etc functions I put there. Is there something else that must be done to make VS link the two together?

  • I got a weird problem with my custom helper class. When i am hitting a action method using $.post, the updated result of helper class is not updating on the view.

  • Thanks for another informative site. Where else could I get that type of info written in such an ideal way' I've a project that I am just now working on, and I've been on the look out for such information.
    profile

  • Its like you read my mind! You seem to know a lot about this, like you wrote the book in
    it or something. I think that you can do with some pics
    to drive the message home a bit, but other than that,
    this is magnificent blog. A great read. I'll definitely be back.

  • Heya i'm for the first time here. I found this board and I to find It really useful & it helped me out much. I hope to give something again and aid others like you helped me.

  • Wir sind einer der Louis Vuitton Taschen Online Shop, wir billige Louis Vuitton Taschen bieten, kaufen Sie es auf unserer Louis Vuitton Outlet online!

  • xmenrk
    fqfcxe
    sxuxpw
    daxtyy
    ujkrjw
    omvcbd

  • oyqgc nnamdi asomugha jersey
    yizjd ryan kerrigan jersey
    hmpab steven jackson jersey
    xrrkz jake ballard jersey
    cmshi christian ponder jersey

  • variation teachers see thought of low cost purses being hereditary
    decrease of train since skiing in addition stagnant careers

  • Thanks for sharing such a good idea, post is fastidious,
    thats why i have read it completely

  • I am in love with this web site. I have visited this blog so often.
    I found this web site on the search engines. I have received a nice stuff of information.
    Many thanks.

  • Well written article. It will be helpful to anybody, which includes me.

    Continue the good work. I cannot wait to read more posts.

  • Can I simply just say what a comfort to find an
    individual who truly knows what they are discussing over the internet.
    You actually understand how to bring an issue to light and make
    it important. More and more people have to read this and understand this side of the
    story. It's surprising you're not more popular given that you most certainly possess the gift.

  • If some one desires expert view regarding blogging and
    site-building after that i advise him/her to go to see
    this blog, Keep up the fastidious job.

Comments have been disabled for this content.