Performance of HtmlHelper vs Partial - asp.net-mvc

Is there a performance difference between using an HtmlHelper or a Partial for a given task?
For example, I'm writing an HtmlHelper "control" to create a link in an editor with the following signature:
public static HtmlString RecordNameLink(
this HtmlHelper htmlHelper,
string linkText,
string editActionName,
object editRouteValues,
string deleteActionName = null,
object deleteRouteValues = null)
In this case the edit button will always be displayed, and the delete button will only be displayed if it is included. Alternately, I could do this:
#Html.Partial("Controls/RecordNameLink", Model)
(Or pass a partial-specific model)
But is there a reason to choose one over the other, specifically does one have better performance than the other? (though I'm open to learning more about the differences in general)
Thanks.

In MVC 3, for this sort of thing its going to be faster for you to render with an html helper than a Partial.
Do a test where you render a partial 100+ times in a loop, vs have the partial contain the loop (partial render per row of a table, vs partial render of all rows of a table). You might be quite surprised at the result.
Your HTML Helper will skip the viewengine having to hunt for the partial, the call to the Virtual Path Provider to load it, etc.

Related

ASP.NET MVC with Razor, custom HtmlHelper outputs just text

I am experimenting with custom HtmlHelpers, but can't get even a basic one to work correctly. My code (just for testing sake) looks like this -
My class -
namespace HtmlHelpers.Extensions
{
public static class Helpers
{
public static string MySubmitButton(this HtmlHelper helper)
{
return String.Format("<input type=\"submit\" value=\"Submit This\">");
}
}
}
In my view -
#using HtmlHelpers.Extensions;
#Html.MySubmitButton()
This I believe should generate a simple submit button, but instead, it justs writes the following text to the screen -
<input type="submit" value="Submit">
I inspected the element, and for some reason the entire input element is being surrounded with double quotes.
Anyone know why? Thanks!
I believe you should be returning a MvcHtmlString class. Try
public static MvcHtmlString MySubmitButton(this HtmlHelper helper)
{
return MvcHtmlString.Create("<input type=\"submit\" value=\"Submit This\">");
}
although there's probably better ways to do this using a TagBuilder if you look at examples online, or the MVC source code since it's open source, you can look at their html helpers(although they are pretty complicated due to the way they layer them).
To directly answer your question of "why" it was displaying that as if it were a string, is Razor tries to be safe and convert anything you display as text instead of HTML/script. For example, #Model.PeronName will escape any characters in the peron's name with HTML character codes. Consider if there was no protection like this, and one of your users changed their name to be <script>someDangerousJavascriptThatWouldChangeCurrentUsersPassword()</script>, then posted on your forum or anywhere their name would appear, then other users visit the page, and that javascript runs and POSTS a change password form for that current user's password to some password that the hacker chose in the script. There are a wide variety of complicated attacks like this, or users might accidentally enter angle brackets(and while fairly harmless if treated as HTML they will mess up your page display).
For that reason MVC will assume just about any string is not HTML and thus replace things like <script> with <script> which basically is a way of saying "this is not html/script, I want you to display the less-than symbol and greater than symbol". If you wanted to display something as HTML, there is a #Html.Raw() helper for that, and it won't clean output, and thus should never be used with a string that you concatenated together from any data that a user my supply.

Is there a function similar to Eval() in asp.net razor?

Is there a way by which I can do somethin like this inside a Razor view:
<h1>Normal razor code</h2>
#Html.Action("NormalRazorCode")
#Eval(" #Html.Action(\"RuntimeEval\") ")
Basically a text-to-razor compiler at runtime (that doesnt create a whole new view like RazorEngine does for example).
I think you could assume that the views exist at compile time, and create the actual files at runtime, this way the ViewEngine will work the way it does by default
basically you could create a Html.Eval helper that will create the .cshtml file and after Render it using Html.Action or Html.Partial
I wanted to do something similar; I have model data (under my control) stored in a database, and it would simplify my life if I was able to include HTML helpers in those strings that could be "expanded" when included in a page.
Main motivation was to allow me to re-use existing partial views.
There's no eval function, but you can easily write an extension method that will evaluate methods that you choose to allow in advance. In my case, I want to evaluate calls to #Html.Partial(). The example here is pretty simple - it looks specifically for #Html.Partial("somePartialView") calls and replaces it with the actual partial:
public static IHtmlString ExpandHtmlString(
this HtmlHelper htmlHelper,
String html)
{
if (String.IsNullOrEmpty(html))
return new HtmlString(html);
const String IDENTIFY_PARTIAL = #"#Html.Partial\(""([a-zA-Z0-9\-_]*)""\)";
var partialFinder = new Regex(IDENTIFY_PARTIAL);
var matches = partialFinder.Matches(html);
foreach (Match m in matches) {
var matchedStr = m.Value;
var viewName = m.Groups[1].Value;
var partial = htmlHelper.Partial(viewName);
html = html.Replace(matchedStr, partial.ToHtmlString());
}
return new HtmlString(html);
}
And you call it from your Razor page as so:
#Html.ExpandHtmlString((String)Model.SomeStringField)
You could easily expand on this to to evaluate a set of methods or operators that you decide in advance you will accept.

ASP.NET MVC Generic Partial View Pattern

I have a partial view that I want to be generic. According to this question, partial views cannot be generic. So I instead made an HtmlHelper extension that handles the pieces for which I want type-safety, then hands off the rest to a real partial view.
Usually my helper is called on page load, which works fine, but sometimes I want to add a row or something through AJAX. When this happens, the controller cannot use my "partial view" since it does not have access to the HtmlHelper.
Apart from having a partial view with a model of type object, is there anything I can do?
I'm using Razor, if that is important.
A simplified version of what I'm doing:
public static MvcHtmlString DoStuff<T>(this HtmlHelper html, IEnumerable<T> data,
Func<T, ViewModelType> StronglyTypedFn, string PartialName)
{
// the pre- and post-processing for the partial view is complex enough I'd like
// to encapsulate it. But I want the encapsulation to include the safety
// benefits that generics give.
var mappedData = data.Select(StronglyTypedFn);
string htmlData = "";
foreach(var model in mappedData){
htmlData += html.Partial(PartialName, model);
}
htmlData += "some boilerplate footer html";
return htmlData;
}
I realize that in this example I have so few lines of code outside the partial view that it seems pointless to have a helper, but in my real example it is more complex.
Now, in an ajax call I want to return Html.DoStuff(). But I can't, because this requires access to the HtmlHelper, and the helper isn't available inside a controller.
You could just have a simple action method that calls the partial for one model instance
public PartialViewResult Single(string partialName) {
return PartialView(partialName);
}
You could use a View with a Dynamic type instead of object.
But... It seems as if there's some misunderstanding here because the Controller shouldn't try to render the view at all. Could you post the Controller code?
The better option is, IMO, returning a JsonResult for your ajax request and adding the row/rows on client side using JS.

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.

How to alter the rendering of links in ASP.NET MVC?

Our designers have come up with button styles for an application which require the addition of <span> tags inside the <a> tags of our links.
In ASP.NET we implemented this by adding an App_Browsers entry for Link Buttons.
How would I go about doing this in ASP.NET MVC?
I've contemplated creating my own versions of all of the various HTML helper functions for creating ActionLinks and RouteLinks but this seems to be quite a 'brute force' way of doing things.
Is there a nice elegant way of doing it?
I know we could write some simple jQuery to do it, but we'd rather have the markup coming out of the server correctly in the first place.
Actually I think writing a new helper is exactly the way I would go. Seems to me that that's exactly what they are there for and it makes them very re-usable too.
You could always write one extension method, that takes another one (one of the built-in ones) as an argument, and wrappes the <span> around your link text before calling it. It should be quite easy to do with lambdas...
public static string SpanLink(this HtmlHelper helper,
string linkText, object args, Action<string> action)
where TController : IController
{
action("<span>" + linkText + "</span>", args);
}
And to call it:
<%= Html.SpanLink<HomeController>("link text", (s) => Html.ActionLink<HomeController>(c => c.Index(s));
(This code is typed directly into the answer field of SO - I haven't even checked it to make sure it compiles. So bear with me if it doesn't work on the first try...)

Resources