Fix #3087 - use IHtmlContentBuilder in TagBuilder

Improves authoring experience when using TagBuilder by taking advantage of
IHtmlContentBuilder an its extension methods.

TagBuilder.InnerHtml is no longer settable.
This commit is contained in:
Ryan Nowak 2015-09-16 10:03:03 -07:00
parent d03a851ab3
commit a707311d9e
8 changed files with 74 additions and 96 deletions

View File

@ -52,13 +52,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
selectTag.AddCssClass("tri-state"); selectTag.AddCssClass("tri-state");
selectTag.Attributes["disabled"] = "disabled"; selectTag.Attributes["disabled"] = "disabled";
var content = new BufferedHtmlContent();
foreach (var item in TriStateValues(value)) foreach (var item in TriStateValues(value))
{ {
content.Append(DefaultHtmlGenerator.GenerateOption(item, item.Text)); selectTag.InnerHtml.Append(DefaultHtmlGenerator.GenerateOption(item, item.Text));
} }
selectTag.InnerHtml = content;
return selectTag; return selectTag;
} }
@ -251,14 +249,14 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (!string.IsNullOrEmpty(label)) if (!string.IsNullOrEmpty(label))
{ {
var labelTag = new TagBuilder("div"); var labelTag = new TagBuilder("div");
labelTag.SetInnerText(label); labelTag.InnerHtml.SetContent(label);
labelTag.AddCssClass("display-label"); labelTag.AddCssClass("display-label");
content.AppendLine(labelTag); content.AppendLine(labelTag);
} }
var valueDivTag = new TagBuilder("div"); var valueDivTag = new TagBuilder("div");
valueDivTag.AddCssClass("display-field"); valueDivTag.AddCssClass("display-field");
valueDivTag.InnerHtml = templateBuilderResult; valueDivTag.InnerHtml.SetContent(templateBuilderResult);
content.AppendLine(valueDivTag); content.AppendLine(valueDivTag);
} }
else else
@ -304,7 +302,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
var hyperlinkTag = new TagBuilder("a"); var hyperlinkTag = new TagBuilder("a");
hyperlinkTag.MergeAttribute("href", uriString); hyperlinkTag.MergeAttribute("href", uriString);
hyperlinkTag.SetInnerText(linkedText); hyperlinkTag.InnerHtml.SetContent(linkedText);
return hyperlinkTag; return hyperlinkTag;
} }
} }

View File

@ -274,22 +274,20 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
var labelTag = new TagBuilder("div"); var labelTag = new TagBuilder("div");
labelTag.AddCssClass("editor-label"); labelTag.AddCssClass("editor-label");
labelTag.InnerHtml = label; labelTag.InnerHtml.SetContent(label);
content.AppendLine(labelTag); content.AppendLine(labelTag);
} }
var valueDivTag = new TagBuilder("div"); var valueDivTag = new TagBuilder("div");
valueDivTag.AddCssClass("editor-field"); valueDivTag.AddCssClass("editor-field");
var innerContent = new BufferedHtmlContent(); valueDivTag.InnerHtml.Append(templateBuilderResult);
innerContent.Append(templateBuilderResult); valueDivTag.InnerHtml.AppendEncoded(" ");
innerContent.AppendEncoded(" "); valueDivTag.InnerHtml.Append(htmlHelper.ValidationMessage(
innerContent.Append(htmlHelper.ValidationMessage(
propertyMetadata.PropertyName, propertyMetadata.PropertyName,
message: null, message: null,
htmlAttributes: null, htmlAttributes: null,
tag: null)); tag: null));
valueDivTag.InnerHtml = innerContent;
content.AppendLine(valueDivTag); content.AppendLine(valueDivTag);
} }

View File

@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var idString = var idString =
TagBuilder.CreateSanitizedId(GetFullHtmlFieldName(viewContext, expression), IdAttributeDotReplacement); TagBuilder.CreateSanitizedId(GetFullHtmlFieldName(viewContext, expression), IdAttributeDotReplacement);
tagBuilder.Attributes.Add("for", idString); tagBuilder.Attributes.Add("for", idString);
tagBuilder.SetInnerText(resolvedLabelText); tagBuilder.InnerHtml.SetContent(resolvedLabelText);
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), replaceExisting: true); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), replaceExisting: true);
return tagBuilder; return tagBuilder;
@ -440,10 +440,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Convert each ListItem to an <option> tag and wrap them with <optgroup> if requested. // Convert each ListItem to an <option> tag and wrap them with <optgroup> if requested.
var listItemBuilder = GenerateGroupsAndOptions(optionLabel, selectList); var listItemBuilder = GenerateGroupsAndOptions(optionLabel, selectList);
var tagBuilder = new TagBuilder("select") var tagBuilder = new TagBuilder("select");
{ tagBuilder.InnerHtml.SetContent(listItemBuilder);
InnerHtml = listItemBuilder
};
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */); tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement); tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
@ -537,11 +535,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
} }
// The first newline is always trimmed when a TextArea is rendered, so we add an extra one // The first newline is always trimmed when a TextArea is rendered, so we add an extra one
// in case the value being rendered is something like "\r\nHello". // in case the value being rendered is something like "\r\nHello"
var innerContent = new BufferedHtmlContent(); tagBuilder.InnerHtml.AppendLine();
innerContent.Append(HtmlString.NewLine); tagBuilder.InnerHtml.Append(value);
innerContent.Append(value);
tagBuilder.InnerHtml = innerContent;
return tagBuilder; return tagBuilder;
} }
@ -630,11 +626,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
{ {
tagBuilder.SetInnerText(message); tagBuilder.InnerHtml.SetContent(message);
} }
else if (modelError != null) else if (modelError != null)
{ {
tagBuilder.SetInnerText(ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState)); tagBuilder.InnerHtml.SetContent(
ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState));
} }
if (formContext != null) if (formContext != null)
@ -672,7 +669,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
headerTag = viewContext.ValidationSummaryMessageElement; headerTag = viewContext.ValidationSummaryMessageElement;
} }
var messageTag = new TagBuilder(headerTag); var messageTag = new TagBuilder(headerTag);
messageTag.SetInnerText(message); messageTag.InnerHtml.SetContent(message);
wrappedMessage.AppendLine(messageTag); wrappedMessage.AppendLine(messageTag);
} }
else else
@ -682,10 +679,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
// If excludePropertyErrors is true, describe any validation issue with the current model in a single item. // If excludePropertyErrors is true, describe any validation issue with the current model in a single item.
// Otherwise, list individual property errors. // Otherwise, list individual property errors.
var htmlSummary = new BufferedHtmlContent();
var isHtmlSummaryModified = false; var isHtmlSummaryModified = false;
var modelStates = ValidationHelpers.GetModelStateList(viewContext.ViewData, excludePropertyErrors); var modelStates = ValidationHelpers.GetModelStateList(viewContext.ViewData, excludePropertyErrors);
var htmlSummary = new TagBuilder("ul");
foreach (var modelState in modelStates) foreach (var modelState in modelStates)
{ {
foreach (var modelError in modelState.Errors) foreach (var modelError in modelState.Errors)
@ -695,8 +692,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (!string.IsNullOrEmpty(errorText)) if (!string.IsNullOrEmpty(errorText))
{ {
var listItem = new TagBuilder("li"); var listItem = new TagBuilder("li");
listItem.SetInnerText(errorText); listItem.InnerHtml.SetContent(errorText);
htmlSummary.AppendLine(listItem); htmlSummary.InnerHtml.AppendLine(listItem);
isHtmlSummaryModified = true; isHtmlSummaryModified = true;
} }
} }
@ -704,15 +701,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
if (!isHtmlSummaryModified) if (!isHtmlSummaryModified)
{ {
htmlSummary.AppendEncoded(HiddenListItem); htmlSummary.InnerHtml.AppendEncoded(HiddenListItem);
htmlSummary.Append(HtmlString.NewLine); htmlSummary.InnerHtml.AppendLine();
} }
var unorderedList = new TagBuilder("ul")
{
InnerHtml = htmlSummary
};
var tagBuilder = new TagBuilder("div"); var tagBuilder = new TagBuilder("div");
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
@ -724,11 +716,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
{ {
tagBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName); tagBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName);
} }
var innerContent = new BufferedHtmlContent(); tagBuilder.InnerHtml.Append(wrappedMessage);
innerContent.Append(wrappedMessage); tagBuilder.InnerHtml.Append(htmlSummary);
innerContent.Append(unorderedList);
tagBuilder.InnerHtml = innerContent;
if (formContext != null && !excludePropertyErrors) if (formContext != null && !excludePropertyErrors)
{ {
@ -911,7 +901,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
internal static TagBuilder GenerateOption(SelectListItem item, string text) internal static TagBuilder GenerateOption(SelectListItem item, string text)
{ {
var tagBuilder = new TagBuilder("option"); var tagBuilder = new TagBuilder("option");
tagBuilder.SetInnerText(text); tagBuilder.InnerHtml.SetContent(text);
if (item.Value != null) if (item.Value != null)
{ {
@ -1117,10 +1107,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
[NotNull] string url, [NotNull] string url,
object htmlAttributes) object htmlAttributes)
{ {
var tagBuilder = new TagBuilder("a") var tagBuilder = new TagBuilder("a");
{ tagBuilder.InnerHtml.SetContent(linkText);
InnerHtml = new StringHtmlContent(linkText),
};
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
tagBuilder.MergeAttribute("href", url); tagBuilder.MergeAttribute("href", url);
@ -1317,13 +1305,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
groupBuilder.MergeAttribute("disabled", "disabled"); groupBuilder.MergeAttribute("disabled", "disabled");
} }
var optGroupContent = new BufferedHtmlContent().Append(HtmlString.NewLine); groupBuilder.InnerHtml.AppendLine();
foreach (var item in group) foreach (var item in group)
{ {
optGroupContent.AppendLine(GenerateOption(item)); groupBuilder.InnerHtml.AppendLine(GenerateOption(item));
} }
groupBuilder.InnerHtml = optGroupContent;
listItemBuilder.AppendLine(groupBuilder); listItemBuilder.AppendLine(groupBuilder);
} }
else else

View File

@ -24,13 +24,18 @@ namespace Microsoft.AspNet.Mvc.Rendering
TagName = tagName; TagName = tagName;
Attributes = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); Attributes = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
InnerHtml = new BufferedHtmlContent();
} }
public IDictionary<string, string> Attributes { get; private set; } /// <summary>
/// Gets the set of attributes that will be written to the tag.
/// </summary>
public IDictionary<string, string> Attributes { get; }
public IHtmlContent InnerHtml { get; [param: NotNull] set; } = HtmlString.Empty; public IHtmlContentBuilder InnerHtml { get; }
public string TagName { get; private set; } public string TagName { get; }
/// <summary> /// <summary>
/// The <see cref="Rendering.TagRenderMode"/> with which the tag is written. /// The <see cref="Rendering.TagRenderMode"/> with which the tag is written.
@ -187,11 +192,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
} }
} }
public void SetInnerText(string innerText)
{
InnerHtml = new StringHtmlContent(innerText);
}
/// <inheritdoc /> /// <inheritdoc />
public void WriteTo(TextWriter writer, IHtmlEncoder encoder) public void WriteTo(TextWriter writer, IHtmlEncoder encoder)
{ {

View File

@ -5,13 +5,13 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions; using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Framework.WebEncoders.Testing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -143,10 +143,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
string childContent, string outputContent, string expectedOutputContent) string childContent, string outputContent, string expectedOutputContent)
{ {
// Arrange // Arrange
var tagBuilder = new TagBuilder("span2") var tagBuilder = new TagBuilder("span2");
{ tagBuilder.InnerHtml.SetContentEncoded("New HTML");
InnerHtml = new HtmlString("New HTML")
};
tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-foo", "bar");
tagBuilder.Attributes.Add("data-hello", "world"); tagBuilder.Attributes.Add("data-hello", "world");
@ -204,10 +202,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
string childContent, string expectedOutputContent) string childContent, string expectedOutputContent)
{ {
// Arrange // Arrange
var tagBuilder = new TagBuilder("span2") var tagBuilder = new TagBuilder("span2");
{ tagBuilder.InnerHtml.SetContentEncoded("New HTML");
InnerHtml = new HtmlString("New HTML")
};
tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-foo", "bar");
tagBuilder.Attributes.Add("data-hello", "world"); tagBuilder.Attributes.Add("data-hello", "world");

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Actions; using Microsoft.AspNet.Mvc.Actions;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
@ -13,7 +14,6 @@ using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing; using Microsoft.AspNet.Testing;
using Microsoft.Framework.WebEncoders;
using Moq; using Moq;
using Xunit; using Xunit;
@ -131,11 +131,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationSummary() public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationSummary()
{ {
// Arrange // Arrange
var tagBuilder = new TagBuilder("span2") var tagBuilder = new TagBuilder("span2");
{ tagBuilder.InnerHtml.SetContentEncoded("New HTML");
InnerHtml = new HtmlString("New HTML")
};
tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-foo", "bar");
tagBuilder.Attributes.Add("data-hello", "world"); tagBuilder.Attributes.Add("data-hello", "world");
tagBuilder.Attributes.Add("anything", "something"); tagBuilder.Attributes.Add("anything", "something");
@ -225,10 +222,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_GeneratesValidationSummaryWhenNotNone(ValidationSummary validationSummary) public async Task ProcessAsync_GeneratesValidationSummaryWhenNotNone(ValidationSummary validationSummary)
{ {
// Arrange // Arrange
var tagBuilder = new TagBuilder("span2") var tagBuilder = new TagBuilder("span2");
{ tagBuilder.InnerHtml.SetContentEncoded("New HTML");
InnerHtml = new HtmlString("New HTML")
};
var generator = new Mock<IHtmlGenerator>(); var generator = new Mock<IHtmlGenerator>();
generator generator

View File

@ -3,8 +3,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon;
using Microsoft.Framework.WebEncoders.Testing; using Microsoft.Framework.WebEncoders.Testing;
using Xunit; using Xunit;
@ -92,21 +92,6 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering
} }
} }
[Fact]
public void SetInnerText_HtmlEncodesValue()
{
// Arrange
var tagBuilder = new TagBuilder("p");
// Act
tagBuilder.SetInnerText("TestValue");
// Assert
Assert.Equal(
"HtmlEncode[[TestValue]]",
HtmlContentUtilities.HtmlContentToString(tagBuilder.InnerHtml));
}
[Theory] [Theory]
[InlineData("HelloWorld", "HelloWorld")] [InlineData("HelloWorld", "HelloWorld")]
[InlineData("¡HelloWorld", "zHelloWorld")] [InlineData("¡HelloWorld", "zHelloWorld")]
@ -119,5 +104,23 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering
// Assert // Assert
Assert.Equal(output, result); Assert.Equal(output, result);
} }
[Fact]
public void WriteTo_IncludesInnerHtml()
{
// Arrange
var tagBuilder = new TagBuilder("p");
tagBuilder.InnerHtml.AppendEncoded("<span>Hello</span>");
tagBuilder.InnerHtml.Append(", World!");
// Act
using (var writer = new StringWriter())
{
tagBuilder.WriteTo(writer, new CommonTestEncoder());
// Assert
Assert.Equal("<p><span>Hello</span>HtmlEncode[[, World!]]</p>", writer.ToString());
}
}
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Html.Abstractions;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
@ -26,8 +27,8 @@ namespace ActivatorWebSite.TagHelpers
(HtmlHelper as ICanHasViewContext)?.Contextualize(ViewContext); (HtmlHelper as ICanHasViewContext)?.Contextualize(ViewContext);
var builder = new TagBuilder("h2"); var builder = new TagBuilder("h2");
var title = ViewContext.ViewBag.Title; var title = (string)ViewContext.ViewBag.Title;
builder.SetInnerText(title); builder.InnerHtml.SetContent(title);
output.PreContent.SetContent(builder); output.PreContent.SetContent(builder);
} }
} }