From a707311d9e13675d78bf3b06d1cc2b22d7ded712 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 16 Sep 2015 10:03:03 -0700 Subject: [PATCH] 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. --- .../Rendering/Html/DefaultDisplayTemplates.cs | 12 ++-- .../Rendering/Html/DefaultEditorTemplates.cs | 12 ++-- .../Rendering/Html/DefaultHtmlGenerator.cs | 61 ++++++++----------- .../Rendering/Html/TagBuilder.cs | 16 ++--- .../ValidationMessageTagHelperTest.cs | 14 ++--- .../ValidationSummaryTagHelperTest.cs | 15 ++--- .../Rendering/TagBuilderTest.cs | 35 ++++++----- .../TagHelpers/TitleTagHelper.cs | 5 +- 8 files changed, 74 insertions(+), 96 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultDisplayTemplates.cs index 835d78da41..942b14400c 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultDisplayTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultDisplayTemplates.cs @@ -52,13 +52,11 @@ namespace Microsoft.AspNet.Mvc.Rendering selectTag.AddCssClass("tri-state"); selectTag.Attributes["disabled"] = "disabled"; - var content = new BufferedHtmlContent(); 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; } @@ -251,14 +249,14 @@ namespace Microsoft.AspNet.Mvc.Rendering if (!string.IsNullOrEmpty(label)) { var labelTag = new TagBuilder("div"); - labelTag.SetInnerText(label); + labelTag.InnerHtml.SetContent(label); labelTag.AddCssClass("display-label"); content.AppendLine(labelTag); } var valueDivTag = new TagBuilder("div"); valueDivTag.AddCssClass("display-field"); - valueDivTag.InnerHtml = templateBuilderResult; + valueDivTag.InnerHtml.SetContent(templateBuilderResult); content.AppendLine(valueDivTag); } else @@ -304,7 +302,7 @@ namespace Microsoft.AspNet.Mvc.Rendering { var hyperlinkTag = new TagBuilder("a"); hyperlinkTag.MergeAttribute("href", uriString); - hyperlinkTag.SetInnerText(linkedText); + hyperlinkTag.InnerHtml.SetContent(linkedText); return hyperlinkTag; } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultEditorTemplates.cs index 010eb39c2b..23533a0b81 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultEditorTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultEditorTemplates.cs @@ -274,22 +274,20 @@ namespace Microsoft.AspNet.Mvc.Rendering { var labelTag = new TagBuilder("div"); labelTag.AddCssClass("editor-label"); - labelTag.InnerHtml = label; + labelTag.InnerHtml.SetContent(label); content.AppendLine(labelTag); } var valueDivTag = new TagBuilder("div"); valueDivTag.AddCssClass("editor-field"); - - var innerContent = new BufferedHtmlContent(); - innerContent.Append(templateBuilderResult); - innerContent.AppendEncoded(" "); - innerContent.Append(htmlHelper.ValidationMessage( + + valueDivTag.InnerHtml.Append(templateBuilderResult); + valueDivTag.InnerHtml.AppendEncoded(" "); + valueDivTag.InnerHtml.Append(htmlHelper.ValidationMessage( propertyMetadata.PropertyName, message: null, htmlAttributes: null, tag: null)); - valueDivTag.InnerHtml = innerContent; content.AppendLine(valueDivTag); } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultHtmlGenerator.cs index cad068673d..27efd0fbc4 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/DefaultHtmlGenerator.cs @@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.Rendering var idString = TagBuilder.CreateSanitizedId(GetFullHtmlFieldName(viewContext, expression), IdAttributeDotReplacement); tagBuilder.Attributes.Add("for", idString); - tagBuilder.SetInnerText(resolvedLabelText); + tagBuilder.InnerHtml.SetContent(resolvedLabelText); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), replaceExisting: true); return tagBuilder; @@ -440,10 +440,8 @@ namespace Microsoft.AspNet.Mvc.Rendering // Convert each ListItem to an if requested. var listItemBuilder = GenerateGroupsAndOptions(optionLabel, selectList); - var tagBuilder = new TagBuilder("select") - { - InnerHtml = listItemBuilder - }; + var tagBuilder = new TagBuilder("select"); + tagBuilder.InnerHtml.SetContent(listItemBuilder); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */); 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 - // in case the value being rendered is something like "\r\nHello". - var innerContent = new BufferedHtmlContent(); - innerContent.Append(HtmlString.NewLine); - innerContent.Append(value); - tagBuilder.InnerHtml = innerContent; + // in case the value being rendered is something like "\r\nHello" + tagBuilder.InnerHtml.AppendLine(); + tagBuilder.InnerHtml.Append(value); return tagBuilder; } @@ -630,11 +626,12 @@ namespace Microsoft.AspNet.Mvc.Rendering if (!string.IsNullOrEmpty(message)) { - tagBuilder.SetInnerText(message); + tagBuilder.InnerHtml.SetContent(message); } else if (modelError != null) { - tagBuilder.SetInnerText(ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState)); + tagBuilder.InnerHtml.SetContent( + ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState)); } if (formContext != null) @@ -672,7 +669,7 @@ namespace Microsoft.AspNet.Mvc.Rendering headerTag = viewContext.ValidationSummaryMessageElement; } var messageTag = new TagBuilder(headerTag); - messageTag.SetInnerText(message); + messageTag.InnerHtml.SetContent(message); wrappedMessage.AppendLine(messageTag); } 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. // Otherwise, list individual property errors. - var htmlSummary = new BufferedHtmlContent(); var isHtmlSummaryModified = false; var modelStates = ValidationHelpers.GetModelStateList(viewContext.ViewData, excludePropertyErrors); + var htmlSummary = new TagBuilder("ul"); foreach (var modelState in modelStates) { foreach (var modelError in modelState.Errors) @@ -695,8 +692,8 @@ namespace Microsoft.AspNet.Mvc.Rendering if (!string.IsNullOrEmpty(errorText)) { var listItem = new TagBuilder("li"); - listItem.SetInnerText(errorText); - htmlSummary.AppendLine(listItem); + listItem.InnerHtml.SetContent(errorText); + htmlSummary.InnerHtml.AppendLine(listItem); isHtmlSummaryModified = true; } } @@ -704,15 +701,10 @@ namespace Microsoft.AspNet.Mvc.Rendering if (!isHtmlSummaryModified) { - htmlSummary.AppendEncoded(HiddenListItem); - htmlSummary.Append(HtmlString.NewLine); + htmlSummary.InnerHtml.AppendEncoded(HiddenListItem); + htmlSummary.InnerHtml.AppendLine(); } - var unorderedList = new TagBuilder("ul") - { - InnerHtml = htmlSummary - }; - var tagBuilder = new TagBuilder("div"); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); @@ -724,11 +716,9 @@ namespace Microsoft.AspNet.Mvc.Rendering { tagBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName); } - - var innerContent = new BufferedHtmlContent(); - innerContent.Append(wrappedMessage); - innerContent.Append(unorderedList); - tagBuilder.InnerHtml = innerContent; + + tagBuilder.InnerHtml.Append(wrappedMessage); + tagBuilder.InnerHtml.Append(htmlSummary); if (formContext != null && !excludePropertyErrors) { @@ -911,7 +901,7 @@ namespace Microsoft.AspNet.Mvc.Rendering internal static TagBuilder GenerateOption(SelectListItem item, string text) { var tagBuilder = new TagBuilder("option"); - tagBuilder.SetInnerText(text); + tagBuilder.InnerHtml.SetContent(text); if (item.Value != null) { @@ -1117,10 +1107,8 @@ namespace Microsoft.AspNet.Mvc.Rendering [NotNull] string url, object htmlAttributes) { - var tagBuilder = new TagBuilder("a") - { - InnerHtml = new StringHtmlContent(linkText), - }; + var tagBuilder = new TagBuilder("a"); + tagBuilder.InnerHtml.SetContent(linkText); tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes)); tagBuilder.MergeAttribute("href", url); @@ -1317,13 +1305,12 @@ namespace Microsoft.AspNet.Mvc.Rendering groupBuilder.MergeAttribute("disabled", "disabled"); } - var optGroupContent = new BufferedHtmlContent().Append(HtmlString.NewLine); + groupBuilder.InnerHtml.AppendLine(); foreach (var item in group) { - optGroupContent.AppendLine(GenerateOption(item)); + groupBuilder.InnerHtml.AppendLine(GenerateOption(item)); } - - groupBuilder.InnerHtml = optGroupContent; + listItemBuilder.AppendLine(groupBuilder); } else diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/TagBuilder.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/TagBuilder.cs index a177ee1881..495e031a33 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/TagBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/Html/TagBuilder.cs @@ -24,13 +24,18 @@ namespace Microsoft.AspNet.Mvc.Rendering TagName = tagName; Attributes = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + + InnerHtml = new BufferedHtmlContent(); } - public IDictionary Attributes { get; private set; } + /// + /// Gets the set of attributes that will be written to the tag. + /// + public IDictionary 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; } /// /// The with which the tag is written. @@ -187,11 +192,6 @@ namespace Microsoft.AspNet.Mvc.Rendering } } - public void SetInnerText(string innerText) - { - InnerHtml = new StringHtmlContent(innerText); - } - /// public void WriteTo(TextWriter writer, IHtmlEncoder encoder) { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs index 91642d279e..7b1e3626a6 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs @@ -5,13 +5,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Actions; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Routing; -using Microsoft.Framework.WebEncoders.Testing; using Moq; using Xunit; @@ -143,10 +143,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers string childContent, string outputContent, string expectedOutputContent) { // Arrange - var tagBuilder = new TagBuilder("span2") - { - InnerHtml = new HtmlString("New HTML") - }; + var tagBuilder = new TagBuilder("span2"); + tagBuilder.InnerHtml.SetContentEncoded("New HTML"); tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-hello", "world"); @@ -204,10 +202,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers string childContent, string expectedOutputContent) { // Arrange - var tagBuilder = new TagBuilder("span2") - { - InnerHtml = new HtmlString("New HTML") - }; + var tagBuilder = new TagBuilder("span2"); + tagBuilder.InnerHtml.SetContentEncoded("New HTML"); tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-hello", "world"); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index 490924c984..ddd8478568 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Actions; using Microsoft.AspNet.Mvc.ModelBinding; @@ -13,7 +14,6 @@ using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; -using Microsoft.Framework.WebEncoders; using Moq; using Xunit; @@ -131,11 +131,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationSummary() { // Arrange - var tagBuilder = new TagBuilder("span2") - { - InnerHtml = new HtmlString("New HTML") - }; - + var tagBuilder = new TagBuilder("span2"); + tagBuilder.InnerHtml.SetContentEncoded("New HTML"); tagBuilder.Attributes.Add("data-foo", "bar"); tagBuilder.Attributes.Add("data-hello", "world"); tagBuilder.Attributes.Add("anything", "something"); @@ -225,10 +222,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public async Task ProcessAsync_GeneratesValidationSummaryWhenNotNone(ValidationSummary validationSummary) { // Arrange - var tagBuilder = new TagBuilder("span2") - { - InnerHtml = new HtmlString("New HTML") - }; + var tagBuilder = new TagBuilder("span2"); + tagBuilder.InnerHtml.SetContentEncoded("New HTML"); var generator = new Mock(); generator diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs index e81640fd14..fa83cf6cfe 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.Mvc.TestCommon; using Microsoft.Framework.WebEncoders.Testing; 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] [InlineData("HelloWorld", "HelloWorld")] [InlineData("ĦHelloWorld", "zHelloWorld")] @@ -119,5 +104,23 @@ namespace Microsoft.AspNet.Mvc.Core.Rendering // Assert Assert.Equal(output, result); } + + [Fact] + public void WriteTo_IncludesInnerHtml() + { + // Arrange + var tagBuilder = new TagBuilder("p"); + tagBuilder.InnerHtml.AppendEncoded("Hello"); + tagBuilder.InnerHtml.Append(", World!"); + + // Act + using (var writer = new StringWriter()) + { + tagBuilder.WriteTo(writer, new CommonTestEncoder()); + + // Assert + Assert.Equal("

HelloHtmlEncode[[, World!]]

", writer.ToString()); + } + } } } \ No newline at end of file diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs index 08361f0373..4ba4c8fdf5 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs @@ -1,6 +1,7 @@ // 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. +using Microsoft.AspNet.Html.Abstractions; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; @@ -26,8 +27,8 @@ namespace ActivatorWebSite.TagHelpers (HtmlHelper as ICanHasViewContext)?.Contextualize(ViewContext); var builder = new TagBuilder("h2"); - var title = ViewContext.ViewBag.Title; - builder.SetInnerText(title); + var title = (string)ViewContext.ViewBag.Title; + builder.InnerHtml.SetContent(title); output.PreContent.SetContent(builder); } }