diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs index 22f12864d4..9feec71ef2 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs @@ -662,6 +662,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } tagBuilder.MergeAttribute("name", fullName, true); + + AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); AddValidationAttributes(viewContext, tagBuilder, modelExplorer, expression); // If there are any errors for a named field, we add this CSS attribute. @@ -1170,12 +1172,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures var suppliedTypeString = tagBuilder.Attributes["type"]; if (_placeholderInputTypes.Contains(suppliedTypeString)) { - modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, _metadataProvider); - var placeholder = modelExplorer.Metadata.Placeholder; - if (!string.IsNullOrEmpty(placeholder)) - { - tagBuilder.MergeAttribute("placeholder", placeholder); - } + AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); } var valueParameter = FormatValue(value, format); @@ -1288,6 +1285,27 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures return tagBuilder; } + /// + /// Adds a placeholder attribute to the . + /// + /// A instance for the current scope. + /// A instance. + /// The for the . + /// Expression name, relative to the current model. + protected virtual void AddPlaceholderAttribute( + ViewDataDictionary viewData, + TagBuilder tagBuilder, + ModelExplorer modelExplorer, + string expression) + { + modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression(expression, viewData, _metadataProvider); + var placeholder = modelExplorer.Metadata.Placeholder; + if (!string.IsNullOrEmpty(placeholder)) + { + tagBuilder.MergeAttribute("placeholder", placeholder); + } + } + /// /// Adds validation attributes to the if client validation /// is enabled. diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs new file mode 100644 index 0000000000..e9014ad9a8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs @@ -0,0 +1,54 @@ +// 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 System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.TestCommon; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Rendering +{ + public class HtmlHelperTextBoxAreaTest + { + [Fact] + public void TextAreaFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid() + { + // Arrange + var model = new TextAreaModelWithAPlaceholder(); + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); + + // Act + var textArea = helper.TextAreaFor(m => m.Property1); + + // Assert + var result = HtmlContentUtilities.HtmlContentToString(textArea); + Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal); + } + + [Fact] + public void TextAreaFor_DoesNotGeneratePlaceholderAttribute_WhenNoPlaceholderPresentInModel() + { + // Arrange + var model = new TextAreaModelWithoutAPlaceholder(); + var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); + + // Act + var textArea = helper.TextAreaFor(m => m.Property1); + + // Assert + var result = HtmlContentUtilities.HtmlContentToString(textArea); + Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal); + } + + private class TextAreaModelWithAPlaceholder + { + [Display(Prompt = "placeholder")] + public string Property1 { get; set; } + } + + private class TextAreaModelWithoutAPlaceholder + { + public string Property1 { get; set; } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs index d18b6e5367..d2568dcbc1 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs @@ -1,8 +1,9 @@ // 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.AspNetCore.Mvc.TestCommon; +using System; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.Rendering @@ -23,10 +24,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act - var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type })); + var textBox = helper.TextBoxFor(m => m.Property1, new { type }); - // Assert - Assert.True(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]""")); + // Assert + var result = HtmlContentUtilities.HtmlContentToString(textBox); + Assert.Contains(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal); } [Theory] @@ -49,10 +51,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering var helper = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act - var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type })); + var textBox = helper.TextBoxFor(m => m.Property1, new { type }); - // Assert - Assert.False(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]""")); + // Assert + var result = HtmlContentUtilities.HtmlContentToString(textBox); + Assert.DoesNotContain(@"placeholder=""HtmlEncode[[placeholder]]""", result, StringComparison.Ordinal); } private class TextBoxModel