ASP.NET MVC: Custom Html Helpers in Razor - asp.net-mvc

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>");
}

Related

ASP.Net MVC Action methods compile time errors in Razor cshtml files

e Is there any way where one can produce compile time errors if an action method is not available in the controller through using the Html.Action?
For example, let us say we have a controller named LayoutMainPartialViewsController and within such a controller, we have a method Header which outputs the Header Partial View located at /Views/Shared/PartialViews/_Header.cshtml
public class LayoutMainPartialViewsController : Controller
{
//
// GET: /LayoutMainPartialViews/
public ActionResult Header()
{
return PartialView("/Views/Shared/PartialViews/_Header.cshtml");
}
}
Then to include such partial view, we would call within Razor
#Html.Action("Header", "LayoutMainPartialViews");
If for some reason, the method named Header is renamed to Header2, no compile time errors are given but only run time errors.
I would like to be able to have compile time errors to minimize as much as possible runtime errors as they are much more difficult to identify.
On another note, would it make sense to call something like?
#(new LayoutMainPartialViewsController().Header())
The method above returns an ActionResult which needs to be converted as String the same way Html.Action does.
This way, if we change Header to Header2 and have <MvcBuildViews>true</MvcBuildViews> in the csproj, an error is provided compile time.
T4MVC is a T4 template for ASP.NET MVC apps that creates strongly typed helpers that eliminate the use of literal strings in many places.
This does exactly what I require and removes the usage of 'magic strings' as opposed to strongly typed identifiers. Compile time errors are provided even in views if the <MvcBuildViews>true</MvcBuildViews> is set to true.
Thanks #StuartLC for the tip!

Asp.net mvc html.raw doesnt resolve

#Html.Raw(HttpUtility.HtmlDecode(Model.Id.ToString()))
always return 0 in the Index.cshtml file placed under "EditorTemplates" folder
Am i doing anything incorrect
I have the model defined at the top of the page as
#model [Namespace].ViewModel
If the id is numeric, there is no reason to use Html.Raw at all. There is no HTML code in the value that should aviod the normal HTML encoding.
Just use:
#Model.Id
The value will implicitly be converted to a string, so the ToString call is not needed either.
If it doesn't show the expected value, you know that it wasn't there in the first place.

Html.RenderPartial giving me strange overload error?

I made a test partial page named _Test.cshtml and put it in the same directory as my view that will be calling it, here it is:
<div>hi</div>
And in the calling cshtml view, I simply put:
#Html.RenderPartial("_Test")
Which gives me the error:
CS1502: The best overloaded method
match for
'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)'
has some invalid arguments
I have also tried the full path with the same result.
I am very confused as to why this is acting this way, I assume I am missing something simple?
You are getting this error because Html.RenderXXX helpers return void - they have nothing to return because they are writing stuff directly* to response. You should use them like this:
#{ Html.RenderPartial("_Test"); }
There is also Html.Partial helper, which will work with your syntax, but I'd not recommend using it unless you have to, because of performance (it first composes given partial view into string, and then parent view puts it into response*).
* this is not entirely true, they are actually being rendered into ViewContext.Writer and once whole page is rendered and composed, the whole thing goes to response

Custom HtmlHelper extension method usage problem in asp.net mvc razor

I have converted my classic asp.net mvc views to razor. In a view there is a usage of an extension method (it has overloads) of HtmlHelper:
#Html.CustomAction<AccountController, LogOnModel>("displayText", x => x.Register())
And CustomAction signature is:
public static HtmlString CustomAction<TController, TModel>(this HtmlHelper<TModel> view, string displayText, Expression<Func<TController, object>> where TController : Controller
I have also enabled view compilation at build time (through .proj file). When I build the project I get these errors pointing to that line:
Argument 1: cannot convert from
'method group' to
'System.Web.WebPages.HelperResult'
The best overloaded method match for
'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)'
has some invalid arguments
What is the reason of these errors? How can I correct it?
The Razor parser sees < and thinks it's an HTML tag. Therefore, it only parses Html.CustomAction as the expression.
You need to wrap the call in parentheses to force it to treat the entire call as a single expression:
#(Html.CustomAction<AccountController, LogOnModel>("displayText", x => x.Register()))

Adapting a Custom Html Helper for Razor (it uses HtmlTextWriter so returns void)

The Problem
I have a very nifty menu Html helper written for WebFormViewEngine views. This engine allows your helpers to return void, and still be able to use:
#Html.Theseus
This is great for my helper, because it can then render the menu using HtmlTextWriter, that renders directly to the output stream.
In Razor views, however, the Html helpers are expected to return a value (usually MvcHtmlString) which is what gets added to the output. Small difference, big consequence.
There is a way around this, as pointed out to me by GvS (see ASP.NET MVC 2 to MVC 3: Custom Html Helpers in Razor) as follows:
If the helper returns void, then do the following:
#{Html.Theseus;}
(Essentially, you are just calling the method, rather than rendering into the view).
Whilst still neat, this is not quite the same as #Html.Theseus. So...
My code is complex but works very well, so am loath to go through major edits, ie, replacing the HtmlTextWriter with another writer. A snippet of the code goes like:
writer.AddAttribute(HtmlTextWriterAttribute.Href, n.Url);
writer.AddAttribute(HtmlTextWriterAttribute.Title, n.Description);
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.WriteEncodedText(n.Title);
writer.RenderEndTag();
// Recursion, if any
// Snip off the recursion at this level if specified by depth
// Use a negative value for depth if you want to render the entire sitemap from the starting node
if ((currentDepth < depth) || (depth < 0))
{
if (hasChildNodes)
{
// Recursive building starts here
// Open new ul tag for the child nodes
// "<ul class='ChildNodesContainer {0} Level{1}'>";
writer.AddAttribute(HtmlTextWriterAttribute.Class, "Level" + currentDepth.ToString());
writer.RenderBeginTag(HtmlTextWriterTag.Ul);
// BuildMenuLevel calls itself here to
// recursively traverse the sitemap hierarchy,
// building the menu as I go.
// Note: this is where I increase the currentDepth variable!
BuildChildMenu(currentDepth + 1, depth, n, writer);
// Close ul tag for the child nodes
writer.RenderEndTag();
}
}
It wouldn't be fun to re write with TagBuilders. As it stands, it renders any type of menu, including the "Incremental Navigation" as described in my 4guysfromrolla article:
Implementing Incremental Navigation with ASP.NET
The Options:
I guess I could return an empty MvcHtmlString, but that is pretty much the definition of a hack...
The only alternative is to head off into the sunset and rewrite the helper using the TagBuilder to build each tag, add that to a StringBuilder, then build the next tag, etc, and then use the StringBuilder instance to create the MvcHtmlString. Seriously ugly, unless I could do something like...
The Question:
Is there a way to:
Stop the HtmlTextWriter rendering to the stream and instead use it like a StringBuilder that at the end of the process I use to create an MvcHtmlString (or HtmlString)?
Sounds unlikely, even as I write...
PS:
The great thing about the HtmlTextWriter is that you can build large quantities of tags, instead of building them one by one as with a TagBuilder.
Contrary to the responses you received for your other question Razor does not require that you return an HtmlString. The problem with your code right now is that you are writing directly to the response stream. Razor executes things inside-out which means that you can mess up the response order (see a similar question).
So in your case you could probably do this (though i haven't tested it):
public static void Theseus(this HtmlHelper html)
{
var writer = new HtmlTextWriter(html.ViewContext.Writer);
...
}
Edit (follow up to address your comments):
Html Helpers are perfectly capable of either returning a HtmlString directly or void and writing to the context writer. For example, both Html.Partial and Html.RenderPartial work fine in Razor. I think what you are confusing is the syntax required to call one version and not the other.
For example, consider an Aspx view:
<%: Html.Partial("Name") %>
<% Html.RenderPartial("Name") %>
You call each method differently. If you flip things around, things will just not work. Similarly in Razor:
#Html.Partial("Name")
#{ Html.RenderPartial("Name"); }
Now it just so happens that the syntax to use a void helper is a lot more verbose in Razor compared to Aspx. However, both work just fine. Unless you meant something else by "the issue is with a html helper not being able to return void".
By the way, if you really want to call your helper using this syntax: #Html.Theseus() you could do this:
public static IHtmlString Theseus(this HtmlHelper html)
{
var writer = new HtmlTextWriter(html.ViewContext.Writer);
...
return new HtmlString("");
}
But that's a bit of a hack.

Resources