4

I want to create a Razor helper to cut down on on the amount of code I need to write. I added an App_Code folder to my project and added a file called MyHelpers.cshtml, with the following contents (trivial example for the purposes of illustration)...

@using System.Web.Mvc
@using System.Web.Mvc.Html
@helper HomeLink() {
  @Html.ActionLink("Home", "Index", "Home")
}

However, this gives a compiler exception, saying there isn't an overload for Html.ActionLink that takes three strings. The ActionLink code was copied from a Razor page where it works fine. I get similar problems when trying to use any of the other standard helpers inside my own helper.

If I try to use the MVC wrappers for Telerik's KendoUI, which are referenced like @Html.Kendo.ComboBox... then I get a red line under the Html with a message "Cannot convert instance type System.Web.WebPages.Html.HtmlHelper to System.Web.Mvc.HtmlHelper"

According to the note at the end of this blog post, this was a known issue with MVC3, but was supposed to be added in the next release...

http://weblogs.asp.net/scottgu/asp-net-mvc-3-and-the-helper-syntax-within-razor

According to this UserVoice page, the issue was actually fixed in VS2013...

http://aspnet.uservoice.com/forums/41201-asp-net-mvc/suggestions/3670180-support-helper-extensionmethod-this-htmlhelper-ht

I'm using VS2013 Update 4, in a brand new MVC5 project, and it doesn't seem to work. I've tried quite a few of the workarounds I found here, but none helped.

Anyone any ideas?

Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106
  • my guess is, that you cannot directly call helpers inside of helpers, see this post for a possible solution: http://stackoverflow.com/questions/10522049/can-you-use-a-helper-inside-an-helper – ovm Feb 04 '15 at 14:29
  • Hmm, looks like a hack, but it may work. What I don't understand is that Microsoft say they fixed this issue (see the UserVoice link in my post), so it should work without having to do stuff like this. Anyway, I'll give it a go and see if it works. Thx – Avrohom Yisroel Feb 04 '15 at 17:41

1 Answers1

2

Ugh. In-view helpers are an abomination. Microsoft should have never even created the possibility. The real way to do something like this would be to create either an extension or use a partial.

Extension

public static class HtmlHelperExtensions
{
    public static MvcHtmlString HomeLink(this HtmlHelper helper, string linkText = "Home")
    {
        return helper.ActionLink(linkText, "Index", "Home");
    }
}

Then, in your view:

@Html.HomeLink()

Or

@Html.HomeLink("This is my awesome Home link.")

Partial

In Views\Shared\_HomeLink.cshtml:

@Html.ActionLink("Home", "Index", "Home")

Then in your view:

@Html.Partial("_HomeLink")

This method allows overrides. For example, let's say you wanted the home link to go to the "homepage" of the store section of your site on views within the store section, you could add a new partial view, Views\Store\_HomeLink.cshtml:

@Html.ActionLink("Home", "Index", "Store")

And this view would take precedence over the one in Views\Shared for any view within Views\Store.

However, either approach is really overkill for something like this this. Don't go overboard in your quest to be DRY. You can use line numbers as a rough guide. If your abstraction takes as many lines as the code you're abstracting, then you should take a very hard look at whether it's worth while. Is the abstracted code likely to change? Does it have complex logic? Something like a call to Html.ActionLink is exceedingly simple and highly unlikely to offer any true ROI by abstracting it away.

Testing is another way to view this issue, as the purpose of DRY is really about reducing the amount of code that needs to be tested. Viewed in that light, Html.ActionLink is already thoroughly tested by Microsoft. Aside from just outright borking the method signature, which Intellisense would warn you about immediately, there's no way you can truly break it. However, by creating a helper, partial, etc. you're now introducing complexity. This is new code that can fail for any number of reasons. If your abstraction introduces complexity and new tests that would otherwise not be needed. Then, once again, you should step back and really think about if it's worth it.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • That's great, thanks. I had gone with the in-view helper as it looked simpler, but having just tried it with an extension method, it's actually not much harder for the type of code I want. I still think that the Razor-based way is going to be easier if you are returning predominantly HTML, but it may be that I just haven't explored the extension methods enough. – Avrohom Yisroel Feb 05 '15 at 10:13
  • As for my example, I did point out that the one I showed was trivially simple. I wouldn't bother creating a helper for something like that. I just didn't want to confuse the question with a sample that included all of the extra (and irrelevant as far as this question went) code that I'm using. – Avrohom Yisroel Feb 05 '15 at 10:14
  • 1
    @AvrohomYisroel: The key problem with in-view helpers is that they violate one of the core principles of MVC. Views should be as free of logic as possible. The goal is to have a very straight-forward UI layer that you could potentially hand off to someone with no programming experience to make changes as necessary. This allows having pure front-end developers that are skilled at things like JavaScript, but not so much C#, work on the pieces they need to work on, while your backend developers work on their pieces. – Chris Pratt Feb 05 '15 at 15:57
  • thanks for the comment. I agree with you about keeping the views clean of logic. I was looking at helpers as a sort of piece of code, rather than a view, but I guess the way I was doing it was really just a mini view, so probably not the best way like you said. – Avrohom Yisroel Feb 05 '15 at 16:05