ASP.NET MVC: Custom Html Helpers in Razor
I am having difficulty with Html Helpers when used with Razor. Said helpers worked fine in MVC 2 with the web form view engine. But not in razor. The error I get at runtime is:
Compiler Error Message: CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments
Source Error:
Line 1: @using Wingspan.Web.Mvc;
Line 2: @Html.IncrementalMenu(MenuBlock.Site)
Expanding the Show Detailed Compiler Output reveals:
d:\...\Views\Shared\MenuTop.cshtml(2,1): error CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments
d:\...\Views\Shared\MenuTop.cshtml(2,7): error CS1503: Argument 1: cannot convert from 'void' to 'System.Web.WebPages.HelperResult'
That indicates to me that razor doesn't like my helper, IncrementalMenu, returning void (which works fine in MVC 2 web form engine views).
I get no errors at Compile time, although the line of code (@Html.IncrementalMenu(...)) is red underlined with the following message:
Cannot implicitly convert type 'void' to 'object'
IncrementalMenu is in the Wingspan.Web.Mvc namespace. It's signature is as follows:
public static void IncrementalMenu(this HtmlHelper html, MenuBlock menuBlock)
{
// Uses an HtmlTextWriter to render a menu from the sitemap
}
I'm blowed if I know what is wrong...
PS:
The MenuBlock parameter is just an enum that identifies how the menu should render. Don't fixate on this as that is fine.
You can call your helper like this:
@{ Html.IncrementalMenu(MenuBlock.Site); }
WebForms syntax
<% Html.IncrementalMenu(MenuBlock.Site); %>
You just call your method, and the return value (if there is any) is ignored.
Code like this expects a return value, and writes the return value to the html stream:
@Html.YourHelper()
Webforms syntax:
<%: Html.YourHelper() %>
The same, if result value != IHtmlString:
<%= Server.HtmlEncode(Html.YourHelper()) %>
Addendum:
You can get the same, or similar, error with @Html.RenderPartial. In this case it is due to the fact that RenderPartial renders directly to the Response, so is not a string and needs to be coded inside a "Razor code block":
@{
Html.RenderPartial(...);
}
I suspect that is one of the reasons that Microsoft have included in ASP.NET MVC the new Html.Partial. As Html.Partial does return a string, it is OK to write:
@Html.Partial
Which looks a lot better. Given that one of Razor's declared objectives is to be easy on the eye, this is quite likely true.
It also kind of makes me, at least, feel more comfortable. I know what returning a string is, I do it all the time. But "returning to the Response" requires a few more brain cycles every time I think it.
And it fits with the old adage that finally Microsoft get their products right in version 3. EG, Access 97.
Which is a depressing simile. Cos they screwed things up in version 4, ie, Access 2000...
Your HTML helper should return MvcHtmlString which represents the html in order to work properly with Razor (and other view engines that are not the WebFormsViewEngine)
public static MvcHtmlString Label(this HtmlHelper html, string expression)
{
return MvcHtmlString.Create("<label>" + expression + "</label>");
}