Allow the use of the Prompt property of DisplayAttribute for the placeholder attribute of input fields (addresses #3723)
This commit is contained in:
parent
78e7179221
commit
8b726ddc0c
|
|
@ -258,6 +258,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <value>The order value of the current metadata.</value>
|
||||
public abstract int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text to display as a placeholder value for an editor.
|
||||
/// </summary>
|
||||
public abstract string Placeholder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text to display when the model is <c>null</c>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -432,6 +432,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Placeholder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (DisplayMetadata.Placeholder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return DisplayMetadata.Placeholder();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ModelPropertyCollection Properties
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
/// </summary>
|
||||
public int Order { get; set; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate which is used to get a value for the
|
||||
/// model's placeholder text. See <see cref="ModelMetadata.Placeholder"/>.
|
||||
/// </summary>
|
||||
public Func<string> Placeholder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to include in the model value in display.
|
||||
/// See <see cref="ModelMetadata.ShowForDisplay"/>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
if (displayAttribute != null)
|
||||
{
|
||||
displayMetadata.Description = () => displayAttribute.GetDescription();
|
||||
displayMetadata.Placeholder = () => displayAttribute.GetPrompt();
|
||||
}
|
||||
|
||||
// DisplayFormatString
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
private static readonly MethodInfo ConvertEnumFromStringMethod =
|
||||
typeof(DefaultHtmlGenerator).GetTypeInfo().GetDeclaredMethod(nameof(ConvertEnumFromString));
|
||||
|
||||
// See: (http://www.w3.org/TR/html5/forms.html#the-input-element)
|
||||
private static readonly string[] _placeholderInputTypes =
|
||||
new[] { "text", "search", "url", "tel", "email", "password", "number" };
|
||||
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
private readonly IClientModelValidatorProvider _clientModelValidatorProvider;
|
||||
private readonly IModelMetadataProvider _metadataProvider;
|
||||
|
|
@ -1134,12 +1138,24 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
nameof(expression));
|
||||
}
|
||||
|
||||
var inputTypeString = GetInputTypeString(inputType);
|
||||
var tagBuilder = new TagBuilder("input");
|
||||
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
|
||||
tagBuilder.MergeAttributes(htmlAttributes);
|
||||
tagBuilder.MergeAttribute("type", GetInputTypeString(inputType));
|
||||
tagBuilder.MergeAttribute("type", inputTypeString);
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
var valueParameter = FormatValue(value, format);
|
||||
var usedModelState = false;
|
||||
switch (inputType)
|
||||
|
|
|
|||
|
|
@ -520,6 +520,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
public override string Placeholder
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override ModelPropertyCollection Properties
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
|
|||
Assert.Null(metadata.NullDisplayText);
|
||||
Assert.Null(metadata.TemplateHint);
|
||||
Assert.Null(metadata.SimpleDisplayProperty);
|
||||
Assert.Null(metadata.Placeholder);
|
||||
|
||||
Assert.Equal(10000, ModelMetadata.DefaultOrder);
|
||||
Assert.Equal(ModelMetadata.DefaultOrder, metadata.Order);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
{ new DisplayAttribute() { Description = "d" }, d => d.Description(), "d" },
|
||||
{ new DisplayAttribute() { Name = "DN" }, d => d.DisplayName(), "DN" },
|
||||
{ new DisplayAttribute() { Order = 3 }, d => d.Order, 3 },
|
||||
{ new DisplayAttribute() { Prompt = "Enter Value" }, d => d.Placeholder(), "Enter Value" },
|
||||
|
||||
{ new DisplayColumnAttribute("Property"), d => d.SimpleDisplayProperty, "Property" },
|
||||
|
||||
|
|
|
|||
|
|
@ -194,6 +194,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
{
|
||||
new DisplayAttribute { Name = "value" }, metadata => metadata.DisplayName
|
||||
},
|
||||
{
|
||||
new DisplayAttribute { Prompt = "value" }, metadata => metadata.Placeholder
|
||||
},
|
||||
{
|
||||
new DisplayFormatAttribute { DataFormatString = "value" },
|
||||
metadata => metadata.DisplayFormatString
|
||||
|
|
@ -422,6 +425,22 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
|
|||
Assert.Equal("description", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayAttribute_PromptAsPlaceholder()
|
||||
{
|
||||
// Arrange
|
||||
var display = new DisplayAttribute() { Prompt = "prompt" };
|
||||
var provider = CreateProvider(new[] { display });
|
||||
|
||||
var metadata = provider.GetMetadataForType(typeof(string));
|
||||
|
||||
// Act
|
||||
var result = metadata.Placeholder;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("prompt", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_FromResources_GetsRecomputed()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -399,6 +399,21 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
HtmlContentUtilities.HtmlContentToString(passwordResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PasswordFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input id=""HtmlEncode[[Property7]]"" name=""HtmlEncode[[Property7]]"" placeholder=""HtmlEncode[[placeholder]]"" type=""HtmlEncode[[password]]"" />";
|
||||
var model = new PasswordModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var result = helper.PasswordFor(m => m.Property7, htmlAttributes: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
private static ViewDataDictionary<PasswordModel> GetViewDataWithNullModelAndNonEmptyViewData()
|
||||
{
|
||||
return new ViewDataDictionary<PasswordModel>(new EmptyModelMetadataProvider())
|
||||
|
|
@ -443,6 +458,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
public Dictionary<string, string> Property3 { get; } = new Dictionary<string, string>();
|
||||
|
||||
public NestedClass Property4 { get; } = new NestedClass();
|
||||
|
||||
[Display(Prompt = "placeholder")]
|
||||
public string Property7 { get; set; }
|
||||
}
|
||||
|
||||
public class NestedClass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
// 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.ComponentModel.DataAnnotations;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Rendering
|
||||
{
|
||||
public class HtmlHelperTextBoxTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("text")]
|
||||
[InlineData("search")]
|
||||
[InlineData("url")]
|
||||
[InlineData("tel")]
|
||||
[InlineData("email")]
|
||||
[InlineData("number")]
|
||||
public void TextBoxFor_GeneratesPlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsValid(string type)
|
||||
{
|
||||
// Arrange
|
||||
var model = new TextBoxModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type }));
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]"""));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hidden")]
|
||||
[InlineData("date")]
|
||||
[InlineData("time")]
|
||||
[InlineData("range")]
|
||||
[InlineData("color")]
|
||||
[InlineData("checkbox")]
|
||||
[InlineData("radio")]
|
||||
[InlineData("submit")]
|
||||
[InlineData("reset")]
|
||||
[InlineData("button")]
|
||||
[InlineData("image")]
|
||||
[InlineData("file")]
|
||||
public void TextBoxFor_DoesNotGeneratePlaceholderAttribute_WhenDisplayAttributePromptIsSetAndTypeIsInvalid(string type)
|
||||
{
|
||||
// Arrange
|
||||
var model = new TextBoxModel();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
|
||||
// Act
|
||||
var result = HtmlContentUtilities.HtmlContentToString(helper.TextBoxFor(m => m.Property1, new { type }));
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Contains(@"placeholder=""HtmlEncode[[placeholder]]"""));
|
||||
}
|
||||
|
||||
private class TextBoxModel
|
||||
{
|
||||
[Display(Prompt = "placeholder")]
|
||||
public string Property1 { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue