Making TagBuilder implement IHtmlContent and removing ToHtmlContent.

- Making TagRenderMode a property in TagBuilder.
- Modifying places where TagBuilder is used to suit the new model.
- This reduces space occupied by a normal application by 11.8%.
This commit is contained in:
sornaks 2015-08-10 17:14:19 -07:00
parent 93d07147b2
commit f2540f9ba1
13 changed files with 150 additions and 167 deletions

View File

@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var antiforgeryTag = Generator.GenerateAntiforgery(ViewContext);
if (antiforgeryTag != null)
{
output.PostContent.Append(antiforgeryTag.ToString());
output.PostContent.Append(antiforgeryTag);
}
}
}

View File

@ -236,13 +236,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
}
var tagBuilder = Generator.GenerateCheckBox(
var checkBoxTag = Generator.GenerateCheckBox(
ViewContext,
modelExplorer,
For.Name,
isChecked: null,
htmlAttributes: htmlAttributes);
if (tagBuilder != null)
if (checkBoxTag != null)
{
// Do not generate current element's attributes or tags. Instead put both <input type="checkbox"/> and
// <input type="hidden"/> into the output's Content.
@ -251,12 +251,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var renderingMode =
output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag;
output.Content.Append(tagBuilder.ToHtmlContent(renderingMode));
checkBoxTag.TagRenderMode = renderingMode;
output.Content.Append(checkBoxTag);
tagBuilder = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
if (tagBuilder != null)
var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name);
if (hiddenForCheckboxTag != null)
{
output.Content.Append(tagBuilder.ToHtmlContent(renderingMode));
hiddenForCheckboxTag.TagRenderMode = renderingMode;
output.Content.Append(hiddenForCheckboxTag);
}
}
}

View File

@ -41,7 +41,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
inputTag.Attributes["checked"] = "checked";
}
return inputTag.ToHtmlContent(TagRenderMode.SelfClosing);
inputTag.TagRenderMode = TagRenderMode.SelfClosing;
return inputTag;
}
private static IHtmlContent BooleanTemplateDropDownList(IHtmlHelper htmlHelper, bool? value)
@ -52,17 +53,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
selectTag.Attributes["disabled"] = "disabled";
var content = new BufferedHtmlContent();
content.Append(selectTag.ToHtmlContent(TagRenderMode.StartTag));
foreach (var item in TriStateValues(value))
{
content.Append(
DefaultHtmlGenerator.GenerateOption(item, item.Text)
.ToHtmlContent(TagRenderMode.Normal));
content.Append(DefaultHtmlGenerator.GenerateOption(item, item.Text));
}
content.Append(selectTag.ToHtmlContent(TagRenderMode.EndTag));
return content;
selectTag.InnerHtml = content;
return selectTag;
}
// Will soon need to be shared with the default editor templates implementations.
@ -208,7 +205,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewData = htmlHelper.ViewData;
var templateInfo = viewData.TemplateInfo;
var modelExplorer = viewData.ModelExplorer;
var content = new BufferedHtmlContent();
if (modelExplorer.Model == null)
{
@ -229,6 +225,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
var content = new BufferedHtmlContent();
foreach (var propertyExplorer in modelExplorer.Properties)
{
var propertyMetadata = propertyExplorer.Metadata;
@ -237,25 +234,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
continue;
}
var divTag = new TagBuilder("div");
if (!propertyMetadata.HideSurroundingHtml)
{
var label = propertyMetadata.GetDisplayName();
if (!string.IsNullOrEmpty(label))
{
divTag.SetInnerText(label);
divTag.AddCssClass("display-label");
content.AppendLine(divTag.ToHtmlContent(TagRenderMode.Normal));
// Reset divTag for reuse.
divTag.Attributes.Clear();
}
divTag.AddCssClass("display-field");
content.Append(divTag.ToHtmlContent(TagRenderMode.StartTag));
}
var templateBuilder = new TemplateBuilder(
viewEngine,
htmlHelper.ViewContext,
@ -266,11 +244,26 @@ namespace Microsoft.AspNet.Mvc.Rendering
readOnly: true,
additionalViewData: null);
content.Append(templateBuilder.Build());
var templateBuilderResult = templateBuilder.Build();
if (!propertyMetadata.HideSurroundingHtml)
{
content.AppendLine(divTag.ToHtmlContent(TagRenderMode.EndTag));
var label = propertyMetadata.GetDisplayName();
if (!string.IsNullOrEmpty(label))
{
var labelTag = new TagBuilder("div");
labelTag.SetInnerText(label);
labelTag.AddCssClass("display-label");
content.AppendLine(labelTag);
}
var valueDivTag = new TagBuilder("div");
valueDivTag.AddCssClass("display-field");
valueDivTag.InnerHtml = templateBuilderResult;
content.AppendLine(valueDivTag);
}
else
{
content.Append(templateBuilderResult);
}
}
@ -312,8 +305,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var hyperlinkTag = new TagBuilder("a");
hyperlinkTag.MergeAttribute("href", uriString);
hyperlinkTag.SetInnerText(linkedText);
return hyperlinkTag.ToHtmlContent(TagRenderMode.Normal);
return hyperlinkTag;
}
}
}

View File

@ -256,28 +256,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
continue;
}
var divTag = new TagBuilder("div");
if (!propertyMetadata.HideSurroundingHtml)
{
var label = htmlHelper.Label(
propertyMetadata.PropertyName,
labelText: null,
htmlAttributes: null);
if (!string.IsNullOrEmpty(label.ToString()))
{
divTag.AddCssClass("editor-label");
divTag.InnerHtml = label; // already escaped
content.AppendLine(divTag.ToHtmlContent(TagRenderMode.Normal));
// Reset divTag for reuse.
divTag.Attributes.Clear();
}
divTag.AddCssClass("editor-field");
content.Append(divTag.ToHtmlContent(TagRenderMode.StartTag));
}
var templateBuilder = new TemplateBuilder(
viewEngine,
htmlHelper.ViewContext,
@ -288,18 +266,36 @@ namespace Microsoft.AspNet.Mvc.Rendering
readOnly: false,
additionalViewData: null);
content.Append(templateBuilder.Build());
var templateBuilderResult = templateBuilder.Build();
if (!propertyMetadata.HideSurroundingHtml)
{
content.Append(" ");
content.Append(htmlHelper.ValidationMessage(
var label = htmlHelper.Label(propertyMetadata.PropertyName, labelText: null, htmlAttributes: null);
if (!string.IsNullOrEmpty(label.ToString()))
{
var labelTag = new TagBuilder("div");
labelTag.AddCssClass("editor-label");
labelTag.InnerHtml = label;
content.AppendLine(labelTag);
}
var valueDivTag = new TagBuilder("div");
valueDivTag.AddCssClass("editor-field");
var innerContent = new BufferedHtmlContent();
innerContent.Append(templateBuilderResult);
innerContent.Append(" ");
innerContent.Append(htmlHelper.ValidationMessage(
propertyMetadata.PropertyName,
message: null,
htmlAttributes: null,
tag: null));
valueDivTag.InnerHtml = innerContent;
content.AppendLine(divTag.ToHtmlContent(TagRenderMode.EndTag));
content.AppendLine(valueDivTag);
}
else
{
content.Append(templateBuilderResult);
}
}

View File

@ -157,6 +157,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var tagBuilder = new TagBuilder("input");
tagBuilder.MergeAttribute("type", GetInputTypeString(InputType.Hidden));
tagBuilder.MergeAttribute("value", "false");
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
var fullName = GetFullHtmlFieldName(viewContext, expression);
tagBuilder.MergeAttribute("name", fullName);
@ -673,7 +674,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
var messageTag = new TagBuilder(headerTag);
messageTag.SetInnerText(message);
wrappedMessage.AppendLine(messageTag.ToHtmlContent(TagRenderMode.Normal));
wrappedMessage.AppendLine(messageTag);
}
else
{
@ -696,7 +697,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
var listItem = new TagBuilder("li");
listItem.SetInnerText(errorText);
htmlSummary.AppendLine(listItem.ToHtmlContent(TagRenderMode.Normal));
htmlSummary.AppendLine(listItem);
isHtmlSummaryModified = true;
}
}
@ -726,7 +727,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var innerContent = new BufferedHtmlContent();
innerContent.Append(wrappedMessage);
innerContent.Append(unorderedList.ToHtmlContent(TagRenderMode.Normal));
innerContent.Append(unorderedList);
tagBuilder.InnerHtml = innerContent;
if (formContext != null && !excludePropertyErrors)
@ -1015,6 +1016,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
var tagBuilder = new TagBuilder("input");
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("type", GetInputTypeString(inputType));
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
@ -1302,12 +1304,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
foreach (var group in groupedSelectList)
{
var optGroup = group.First().Group;
// Wrap if requested.
TagBuilder groupBuilder = null;
if (optGroup != null)
{
groupBuilder = new TagBuilder("optgroup");
var groupBuilder = new TagBuilder("optgroup");
if (optGroup.Name != null)
{
groupBuilder.MergeAttribute("label", optGroup.Name);
@ -1318,17 +1317,21 @@ namespace Microsoft.AspNet.Mvc.Rendering
groupBuilder.MergeAttribute("disabled", "disabled");
}
listItemBuilder.AppendLine(groupBuilder.ToHtmlContent(TagRenderMode.StartTag));
}
var optGroupContent = new BufferedHtmlContent().Append(Environment.NewLine);
foreach (var item in group)
{
optGroupContent.AppendLine(GenerateOption(item));
}
foreach (var item in group)
{
listItemBuilder.AppendLine(GenerateOption(item));
groupBuilder.InnerHtml = optGroupContent;
listItemBuilder.AppendLine(groupBuilder);
}
if (optGroup != null)
else
{
listItemBuilder.AppendLine(groupBuilder.ToHtmlContent(TagRenderMode.EndTag));
foreach (var item in group)
{
listItemBuilder.AppendLine(GenerateOption(item));
}
}
}
@ -1338,7 +1341,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
private IHtmlContent GenerateOption(SelectListItem item)
{
var tagBuilder = GenerateOption(item, item.Text);
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
}
}

View File

@ -214,7 +214,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
/// <inheritdoc />
@ -542,7 +542,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
/// <inheritdoc />
@ -643,11 +643,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
var elements = new BufferedHtmlContent();
elements.Append(checkbox.ToHtmlContent(TagRenderMode.SelfClosing));
elements.Append(hidden.ToHtmlContent(TagRenderMode.SelfClosing));
return elements;
return new BufferedHtmlContent().Append(checkbox).Append(hidden);
}
protected virtual string GenerateDisplayName([NotNull] ModelExplorer modelExplorer, string expression)
@ -690,7 +686,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected virtual IHtmlContent GenerateEditor(
@ -751,7 +747,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
htmlAttributes);
if (tagBuilder != null)
{
tagBuilder.ToHtmlContent(TagRenderMode.StartTag).WriteTo(ViewContext.Writer, _htmlEncoder);
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
}
return CreateForm();
@ -793,7 +790,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
htmlAttributes);
if (tagBuilder != null)
{
tagBuilder.ToHtmlContent(TagRenderMode.StartTag).WriteTo(ViewContext.Writer, _htmlEncoder);
tagBuilder.TagRenderMode = TagRenderMode.StartTag;
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
}
return CreateForm();
@ -819,7 +817,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.SelfClosing);
return tagBuilder;
}
protected virtual string GenerateId(string expression)
@ -847,7 +845,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected IHtmlContent GenerateListBox(
@ -869,7 +867,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected virtual string GenerateName(string expression)
@ -895,7 +893,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.SelfClosing);
return tagBuilder;
}
protected virtual IHtmlContent GenerateRadioButton(
@ -917,7 +915,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.SelfClosing);
return tagBuilder;
}
protected virtual IHtmlContent GenerateTextArea(
@ -939,7 +937,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected virtual IHtmlContent GenerateTextBox(
@ -961,7 +959,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.SelfClosing);
return tagBuilder;
}
protected virtual IHtmlContent GenerateValidationMessage(
@ -981,7 +979,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected virtual IHtmlContent GenerateValidationSummary(
@ -1001,7 +999,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return HtmlString.Empty;
}
return tagBuilder.ToHtmlContent(TagRenderMode.Normal);
return tagBuilder;
}
protected virtual string GenerateValue(string expression, object value, string format, bool useViewData)

View File

@ -9,10 +9,11 @@ using System.Text;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Mvc.Rendering
{
public class TagBuilder
public class TagBuilder : IHtmlContent
{
public TagBuilder(string tagName)
{
@ -31,6 +32,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
public string TagName { get; private set; }
/// <summary>
/// The <see cref="Rendering.TagRenderMode"/> with which the tag is written.
/// </summary>
/// <remarks>Defaults to <see cref="TagRenderMode.Normal"/>.</remarks>
public TagRenderMode TagRenderMode { get; set; } = TagRenderMode.Normal;
public void AddCssClass(string value)
{
string currentValue;
@ -125,7 +132,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
}
private void AppendAttributes(BufferedHtmlContent content)
private void AppendAttributes(TextWriter writer, IHtmlEncoder encoder)
{
foreach (var attribute in Attributes)
{
@ -136,11 +143,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
continue;
}
content.Append(" ");
content.Append(key);
content.Append("=\"");
content.Append(new StringHtmlContent(attribute.Value));
content.Append("\"");
writer.Write(" ");
writer.Write(key);
writer.Write("=\"");
encoder.HtmlEncode(attribute.Value, writer);
writer.Write("\"");
}
}
@ -185,48 +192,39 @@ namespace Microsoft.AspNet.Mvc.Rendering
InnerHtml = new StringHtmlContent(innerText);
}
/// <summary>
/// Converts the <see cref="TagBuilder"/> to <see cref="IHtmlContent"/> with the specified
/// <see cref="TagRenderMode"/>.
/// </summary>
/// <param name="renderMode"><see cref="TagRenderMode"/> with which the <see cref="TagBuilder"/>
/// should be written.</param>
/// <returns><see cref="IHtmlContent"/> containing the contents of the <see cref="TagBuilder"/>.</returns>
public IHtmlContent ToHtmlContent(TagRenderMode renderMode)
/// <inheritdoc />
public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
{
var content = new BufferedHtmlContent();
switch (renderMode)
switch (TagRenderMode)
{
case TagRenderMode.StartTag:
content.Append("<");
content.Append(TagName);
AppendAttributes(content);
content.Append(">");
writer.Write("<");
writer.Write(TagName);
AppendAttributes(writer, encoder);
writer.Write(">");
break;
case TagRenderMode.EndTag:
content.Append("</");
content.Append(TagName);
content.Append(">");
writer.Write("</");
writer.Write(TagName);
writer.Write(">");
break;
case TagRenderMode.SelfClosing:
content.Append("<");
content.Append(TagName);
AppendAttributes(content);
content.Append(" />");
writer.Write("<");
writer.Write(TagName);
AppendAttributes(writer, encoder);
writer.Write(" />");
break;
default:
content.Append("<");
content.Append(TagName);
AppendAttributes(content);
content.Append(">");
content.Append(InnerHtml);
content.Append("</");
content.Append(TagName);
content.Append(">");
writer.Write("<");
writer.Write(TagName);
AppendAttributes(writer, encoder);
writer.Write(">");
InnerHtml.WriteTo(writer, encoder);
writer.Write("</");
writer.Write(TagName);
writer.Write(">");
break;
}
return content;
}
private static class Html401IdUtil

View File

@ -9,8 +9,10 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.WebEncoders.Testing;
using Moq;
using Xunit;
@ -57,7 +59,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
htmlGenerator: htmlGenerator,
metadataProvider: metadataProvider);
var expectedPostContent = "Something" + htmlGenerator.GenerateAntiforgery(viewContext);
var expectedPostContent = "Something" +
HtmlContentUtilities.HtmlContentToString(
htmlGenerator.GenerateAntiforgery(viewContext),
new NullTestEncoder());
var formTagHelper = new FormTagHelper(htmlGenerator)
{
Action = "index",

View File

@ -282,6 +282,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
{ "class", "form-control" },
},
TagRenderMode = TagRenderMode.SelfClosing
};
htmlGenerator
.Setup(mock => mock.GenerateCheckBox(
@ -297,7 +298,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
tagHelper.ViewContext,
tagHelper.For.ModelExplorer,
tagHelper.For.Name))
.Returns(new TagBuilder("hidden"))
.Returns(new TagBuilder("hidden") { TagRenderMode = TagRenderMode.SelfClosing })
.Verifiable();
// Act

View File

@ -83,7 +83,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
},
};
return tagBuilder.ToHtmlContent(TagRenderMode.SelfClosing);
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
return tagBuilder;
}
protected override IDictionary<string, object> GetValidationAttributes(

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Framework.WebEncoders.Testing;
@ -73,36 +74,22 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering
[Theory]
[MemberData(nameof(RenderingTestingData))]
public void ToString_IgnoresIdAttributeCase(TagRenderMode renderingMode, string expectedOutput)
public void WriteTo_IgnoresIdAttributeCase(TagRenderMode renderingMode, string expectedOutput)
{
// Arrange
var tagBuilder = new TagBuilder("p");
// An empty value id attribute should not be rendered via ToString.
tagBuilder.Attributes.Add("ID", string.Empty);
tagBuilder.TagRenderMode = renderingMode;
// Act
var value = tagBuilder.ToHtmlContent(renderingMode);
using (var writer = new StringWriter())
{
tagBuilder.WriteTo(writer, new NullTestEncoder());
// Assert
Assert.Equal(expectedOutput, HtmlContentUtilities.HtmlContentToString(value, new NullTestEncoder()));
}
[Theory]
[MemberData(nameof(RenderingTestingData))]
public void ToHtmlString_IgnoresIdAttributeCase(TagRenderMode renderingMode, string expectedOutput)
{
// Arrange
var tagBuilder = new TagBuilder("p");
// An empty value id attribute should not be rendered via ToHtmlString.
tagBuilder.Attributes.Add("ID", string.Empty);
// Act
var value = tagBuilder.ToHtmlContent(renderingMode);
// Assert
Assert.Equal(expectedOutput, HtmlContentUtilities.HtmlContentToString(value, new NullTestEncoder()));
// Assert
Assert.Equal(expectedOutput, writer.ToString());
}
}
[Fact]

View File

@ -30,7 +30,7 @@ namespace ActivatorWebSite.TagHelpers
var content = await context.GetChildContentAsync();
output.Content.SetContent(HtmlHelper.Hidden(Name, content).ToString());
output.Content.SetContent(HtmlHelper.Hidden(Name, content));
}
}
}

View File

@ -28,7 +28,7 @@ namespace ActivatorWebSite.TagHelpers
var builder = new TagBuilder("h2");
var title = ViewContext.ViewBag.Title;
builder.SetInnerText(title);
output.PreContent.SetContent(builder.ToHtmlContent(TagRenderMode.Normal));
output.PreContent.SetContent(builder);
}
}
}