Reputation: 9440
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...
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?
Upvotes: 4
Views: 2685
Reputation: 239290
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.
Upvotes: 2