Rendering corruption in ASP.NET MVC 3 Razor View Engine Using Custom HTML Extension
I just installed ASP.NET MVC 3 RC to try and upgrade an MVC 2 site. I encountered a rendering problem which I managed to repro outside of the site, using an MVC 3 project created from scratch.
Here is my Razor cshtml view:
@using Mvc3RCTest.Helpers
<h2>Demo Render Bug</h2>
<div class="content">
@{ Html.RenderTest(); }
</div>
RenderTest is an HTML extension defined as follows:
using System.Web;
using System.Web.Mvc;
namespace Mvc3RCTest.Helpers
{
public static class TestHtmlExtensions
{
public static void RenderTest(this HtmlHelper html)
{
HttpResponseBase r = html.ViewContext.HttpContext.Response;
r.Write("<ul>");
for (int i = 0; i < 10; ++i)
{
r.Write("<li>" + i + "</li>");
}
r.Write("</ul>");
}
}
}
When this is rendered, the HTML looks like the following:
<ul><li>0</li><li>1</li><li>2</li><li>3</li><li>4</li>开发者_开发问答;<li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>
<h2>Demo Render Bug</h2>
<div class="content">
</div>
As you can see, the output of the RenderTest HTML extension was incorrectly emitted before the rest of the Razor template. It seems as if the Razor rendering engine is trying to cache the entire output, without being aware that HTML extensions can write directly to the output.
Has anyone else seen this problem? Anyone know how to work around this, without having to redo all of my HTML extensions not to write directly to the output?
Unfortunately, all of your helpers should be writing to the ViewContext.Writer
, like so
public static void RenderTest(this HtmlHelper html)
{
var writer = html.ViewContext.Writer;
writer.Write("<ul>");
for (int i = 0; i < 10; ++i)
{
writer.Write("<li>" + i + "</li>");
}
writer.Write("</ul>");
}
Things might have worked for you in the aspx view engine, however that was purely coincidental. It's not that Razor is caching anything per se. Due to the inside-out rendering of Razor pages it writes things to temporary buffers which in turn get written to the response stream at the appropriate time only when you reach the top most layout page. If you write directly to the response stream then you will write things out of order.
Html helpers usually return an MvcHtmlString instead of writing directly to the response. Writing directly to the response reminds me the of old classic WebForms controls :-)
public static MvcHtmlString RenderTest(this HtmlHelper html)
{
var ul = new TagBuilder("ul");
var sb = new StringBuilder();
for (int i = 0; i < 10; ++i)
{
var li = new TagBuilder("li");
li.SetInnerText(i.ToString());
sb.Append(li.ToString());
}
ul.InnerHtml = sb.ToString();
return MvcHtmlString.Create(ul.ToString());
}
As a side note the TagBuilder
class has been moved to the System.Web.WebPages
assembly in MVC 3 RC so make sure this is referenced.
精彩评论