Localize DataAnnotations (#5129)

This commit is contained in:
Ryan Brandenburg 2016-08-16 11:52:15 -07:00 committed by GitHub
parent 8de4ddc010
commit 889af50318
9 changed files with 270 additions and 28 deletions

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject EndProject

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Localization;
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{ {
@ -20,6 +21,13 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IDisplayMetadataProvider, IDisplayMetadataProvider,
IValidationMetadataProvider IValidationMetadataProvider
{ {
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public DataAnnotationsMetadataProvider(IStringLocalizerFactory stringLocalizerFactory)
{
_stringLocalizerFactory = stringLocalizerFactory;
}
/// <inheritdoc /> /// <inheritdoc />
public void CreateBindingMetadata(BindingMetadataProviderContext context) public void CreateBindingMetadata(BindingMetadataProviderContext context)
{ {
@ -80,11 +88,23 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
displayMetadata.DataTypeName = DataType.Html.ToString(); displayMetadata.DataTypeName = DataType.Html.ToString();
} }
var containerType = context.Key.ContainerType ?? context.Key.ModelType;
var localizer = _stringLocalizerFactory?.Create(containerType);
// Description // Description
if (displayAttribute != null) if (displayAttribute != null)
{ {
displayMetadata.Description = () => displayAttribute.GetDescription(); if (localizer != null &&
displayMetadata.Placeholder = () => displayAttribute.GetPrompt(); !string.IsNullOrEmpty(displayAttribute.Description) &&
displayAttribute.ResourceType == null)
{
displayMetadata.Description = () => localizer[displayAttribute.Description];
}
else
{
displayMetadata.Description = () => displayAttribute.GetDescription();
}
} }
// DisplayFormatString // DisplayFormatString
@ -96,7 +116,16 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
// DisplayName // DisplayName
if (displayAttribute != null) if (displayAttribute != null)
{ {
displayMetadata.DisplayName = () => displayAttribute.GetName(); if (localizer != null &&
!string.IsNullOrEmpty(displayAttribute.Name) &&
displayAttribute.ResourceType == null)
{
displayMetadata.DisplayName = () => localizer[displayAttribute.Name];
}
else
{
displayMetadata.DisplayName = () => displayAttribute.GetName();
}
} }
// EditFormatString // EditFormatString
@ -189,6 +218,21 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
displayMetadata.Order = displayAttribute.GetOrder().Value; displayMetadata.Order = displayAttribute.GetOrder().Value;
} }
// Placeholder
if (displayAttribute != null)
{
if (localizer != null &&
!string.IsNullOrEmpty(displayAttribute.Prompt) &&
displayAttribute.ResourceType == null)
{
displayMetadata.Placeholder = () => localizer[displayAttribute.Prompt];
}
else
{
displayMetadata.Placeholder = () => displayAttribute.GetPrompt();
}
}
// ShowForDisplay // ShowForDisplay
if (scaffoldColumnAttribute != null) if (scaffoldColumnAttribute != null)
{ {

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var stringLocalizerFactory = serviceProvider.GetService<IStringLocalizerFactory>(); var stringLocalizerFactory = serviceProvider.GetService<IStringLocalizerFactory>();
var validationAttributeAdapterProvider = serviceProvider.GetRequiredService<IValidationAttributeAdapterProvider>(); var validationAttributeAdapterProvider = serviceProvider.GetRequiredService<IValidationAttributeAdapterProvider>();
options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider()); options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider(stringLocalizerFactory));
options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider( options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider(
validationAttributeAdapterProvider, validationAttributeAdapterProvider,

View File

@ -8,6 +8,8 @@ using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
@ -58,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
object expected) object expected)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(typeof(string)); var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute })); var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute }));
@ -75,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_FindsDisplayFormat_FromDataType() public void CreateDisplayMetadata_FindsDisplayFormat_FromDataType()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var dataType = new DataTypeAttribute(DataType.Currency); var dataType = new DataTypeAttribute(DataType.Currency);
var displayFormat = dataType.DisplayFormat; // Non-null for DataType.Currency. var displayFormat = dataType.DisplayFormat; // Non-null for DataType.Currency.
@ -95,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_FindsDisplayFormat_OverridingDataType() public void CreateDisplayMetadata_FindsDisplayFormat_OverridingDataType()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var dataType = new DataTypeAttribute(DataType.Time); // Has a non-null DisplayFormat. var dataType = new DataTypeAttribute(DataType.Time); // Has a non-null DisplayFormat.
var displayFormat = new DisplayFormatAttribute() // But these values override the values from DataType var displayFormat = new DisplayFormatAttribute() // But these values override the values from DataType
@ -118,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_EditableAttributeFalse_SetsReadOnlyTrue() public void CreateBindingMetadata_EditableAttributeFalse_SetsReadOnlyTrue()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var editable = new EditableAttribute(allowEdit: false); var editable = new EditableAttribute(allowEdit: false);
@ -137,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_EditableAttributeTrue_SetsReadOnlyFalse() public void CreateBindingMetadata_EditableAttributeTrue_SetsReadOnlyFalse()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var editable = new EditableAttribute(allowEdit: true); var editable = new EditableAttribute(allowEdit: true);
@ -152,13 +154,47 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.False(context.BindingMetadata.IsReadOnly); Assert.False(context.BindingMetadata.IsReadOnly);
} }
// This is IMPORTANT. Product code needs to use GetName() instead of .Name. It's easy to regress.
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_NameFromResources_NullLocalizer()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
#if USE_REAL_RESOURCES
Name = nameof(Test.Resources.DisplayAttribute_Name),
ResourceType = typeof(Test.Resources),
#else
Name = nameof(DataAnnotations.Test.Resources.DisplayAttribute_Name),
ResourceType = typeof(TestResources),
#endif
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal("name from resources", context.DisplayMetadata.DisplayName());
}
// This is IMPORTANT. Product code needs to use GetName() instead of .Name. It's easy to regress. // This is IMPORTANT. Product code needs to use GetName() instead of .Name. It's easy to regress.
[Fact] [Fact]
public void CreateDisplayMetadata_DisplayAttribute_NameFromResources() public void CreateDisplayMetadata_DisplayAttribute_NameFromResources_WithLocalizer()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); // Nothing on stringLocalizer should be called
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>();
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var display = new DisplayAttribute() var display = new DisplayAttribute()
{ {
@ -184,10 +220,16 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
// This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress. // This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress.
[Fact] [Fact]
public void CreateDisplayMetadata_DisplayAttribute_DescriptionFromResources() public void CreateDisplayMetadata_DisplayAttribute_DescriptionFromResources_WithLocalizer()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); // Nothing on stringLocalizer should be called
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>();
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var display = new DisplayAttribute() var display = new DisplayAttribute()
{ {
@ -211,6 +253,141 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Equal("description from resources", context.DisplayMetadata.Description()); Assert.Equal("description from resources", context.DisplayMetadata.Description());
} }
// This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress.
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_DescriptionFromResources_NullLocalizer()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
#if USE_REAL_RESOURCES
Description = nameof(Test.Resources.DisplayAttribute_Description),
ResourceType = typeof(Test.Resources),
#else
Description = nameof(DataAnnotations.Test.Resources.DisplayAttribute_Description),
ResourceType = typeof(TestResources),
#endif
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal("description from resources", context.DisplayMetadata.Description());
}
// This is IMPORTANT. Product code needs to use GetPrompt() instead of .Prompt. It's easy to regress.
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_PromptFromResources_WithLocalizer()
{
// Arrange
// Nothing on stringLocalizer should be called
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>();
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var display = new DisplayAttribute()
{
#if USE_REAL_RESOURCES
Prompt = nameof(Test.Resources.DisplayAttribute_Prompt),
ResourceType = typeof(Test.Resources),
#else
Prompt = nameof(DataAnnotations.Test.Resources.DisplayAttribute_Prompt),
ResourceType = typeof(TestResources),
#endif
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal("prompt from resources", context.DisplayMetadata.Placeholder());
}
// This is IMPORTANT. Product code needs to use GetPrompt() instead of .Prompt. It's easy to regress.
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_PromptFromResources_NullLocalizer()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
#if USE_REAL_RESOURCES
Prompt = nameof(Test.Resources.DisplayAttribute_Prompt),
ResourceType = typeof(Test.Resources),
#else
Prompt = nameof(DataAnnotations.Test.Resources.DisplayAttribute_Prompt),
ResourceType = typeof(TestResources),
#endif
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal("prompt from resources", context.DisplayMetadata.Placeholder());
}
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_LocalizeProperties()
{
// Arrange
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
.Setup(s => s["Model_Name"])
.Returns(new LocalizedString("Model_Name", "name from localizer"));
stringLocalizer
.Setup(s => s["Model_Description"])
.Returns(new LocalizedString("Model_Description", "description from localizer"));
stringLocalizer
.Setup(s => s["Model_Prompt"])
.Returns(new LocalizedString("Model_Prompt", "prompt from localizer"));
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactory
.Setup(f => f.Create(It.IsAny<Type>()))
.Returns(stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var display = new DisplayAttribute()
{
Name = "Model_Name",
Description = "Model_Description",
Prompt = "Model_Prompt"
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(DataAnnotationsMetadataProviderTest));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal("name from localizer", context.DisplayMetadata.DisplayName());
Assert.Equal("description from localizer", context.DisplayMetadata.Description());
Assert.Equal("prompt from localizer", context.DisplayMetadata.Placeholder());
}
[Theory] [Theory]
[InlineData(typeof(EmptyClass), false)] [InlineData(typeof(EmptyClass), false)]
[InlineData(typeof(ClassWithFields), false)] [InlineData(typeof(ClassWithFields), false)]
@ -232,7 +409,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_IsEnum_ReflectsModelType(Type type, bool expectedIsEnum) public void CreateDisplayMetadata_IsEnum_ReflectsModelType(Type type, bool expectedIsEnum)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type); var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0]; var attributes = new object[0];
@ -266,7 +443,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_IsFlagsEnum_ReflectsModelType(Type type, bool expectedIsFlagsEnum) public void CreateDisplayMetadata_IsFlagsEnum_ReflectsModelType(Type type, bool expectedIsFlagsEnum)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type); var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0]; var attributes = new object[0];
@ -398,7 +575,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IReadOnlyDictionary<string, string> expectedDictionary) IReadOnlyDictionary<string, string> expectedDictionary)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type); var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0]; var attributes = new object[0];
@ -532,7 +709,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IEnumerable<KeyValuePair<EnumGroupAndName, string>> expectedKeyValuePairs) IEnumerable<KeyValuePair<EnumGroupAndName, string>> expectedKeyValuePairs)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type); var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0]; var attributes = new object[0];
@ -554,7 +731,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationMetadata_RequiredAttribute_SetsIsRequiredToTrue() public void CreateValidationMetadata_RequiredAttribute_SetsIsRequiredToTrue()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var required = new RequiredAttribute(); var required = new RequiredAttribute();
@ -576,7 +753,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationMetadata_NoRequiredAttribute_IsRequiredLeftAlone(bool? initialValue) public void CreateValidationMetadata_NoRequiredAttribute_IsRequiredLeftAlone(bool? initialValue)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var attributes = new Attribute[] { }; var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -597,7 +774,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_RequiredAttribute_IsBindingRequiredLeftAlone(bool initialValue) public void CreateBindingMetadata_RequiredAttribute_IsBindingRequiredLeftAlone(bool initialValue)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var attributes = new Attribute[] { new RequiredAttribute() }; var attributes = new Attribute[] { new RequiredAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -618,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingDetails_NoEditableAttribute_IsReadOnlyLeftAlone(bool? initialValue) public void CreateBindingDetails_NoEditableAttribute_IsReadOnlyLeftAlone(bool? initialValue)
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var attributes = new Attribute[] { }; var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string)); var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -636,7 +813,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationDetails_ValidatableObject_ReturnsObject() public void CreateValidationDetails_ValidatableObject_ReturnsObject()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var attribute = new TestValidationAttribute(); var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };
@ -655,7 +832,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationDetails_ValidatableObject_AlreadyInContext_Ignores() public void CreateValidationDetails_ValidatableObject_AlreadyInContext_Ignores()
{ {
// Arrange // Arrange
var provider = new DataAnnotationsMetadataProvider(); var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var attribute = new TestValidationAttribute(); var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute }; var attributes = new Attribute[] { attribute };

View File

@ -1050,7 +1050,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[] new DefaultCompositeMetadataDetailsProvider(new IMetadataDetailsProvider[]
{ {
new DefaultBindingMetadataProvider(), new DefaultBindingMetadataProvider(),
new DataAnnotationsMetadataProvider(), new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
}), }),
new TestOptionsManager<MvcOptions>()) new TestOptionsManager<MvcOptions>())
{ {

View File

@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public static string DisplayAttribute_Name { get; } = Resources.DisplayAttribute_Name; public static string DisplayAttribute_Name { get; } = Resources.DisplayAttribute_Name;
public static string DisplayAttribute_Prompt { get; } = Resources.DisplayAttribute_Prompt;
public static string DisplayAttribute_CultureSensitiveName => public static string DisplayAttribute_CultureSensitiveName =>
Resources.DisplayAttribute_Name + CultureInfo.CurrentUICulture; Resources.DisplayAttribute_Name + CultureInfo.CurrentUICulture;

View File

@ -58,6 +58,22 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Test
return GetString("DisplayAttribute_Name"); return GetString("DisplayAttribute_Name");
} }
/// <summary>
/// prompt from resources
/// </summary>
internal static string DisplayAttribute_Prompt
{
get { return GetString("DisplayAttribute_Prompt"); }
}
/// <summary>
/// prompt from resources
/// </summary>
internal static string FormatDisplayAttribute_Prompt()
{
return GetString("DisplayAttribute_Prompt");
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -126,4 +126,7 @@
<data name="DisplayAttribute_Name" xml:space="preserve"> <data name="DisplayAttribute_Name" xml:space="preserve">
<value>name from resources</value> <value>name from resources</value>
</data> </data>
<data name="DisplayAttribute_Prompt" xml:space="preserve">
<value>prompt from resources</value>
</data>
</root> </root>

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
new DefaultBindingMetadataProvider(), new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(), new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(), new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
new DataMemberRequiredBindingMetadataProvider(), new DataMemberRequiredBindingMetadataProvider(),
}; };
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
new DefaultBindingMetadataProvider(), new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(), new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(), new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
new DataMemberRequiredBindingMetadataProvider(), new DataMemberRequiredBindingMetadataProvider(),
}; };
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
new DefaultBindingMetadataProvider(), new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(), new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(), new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
detailsProvider detailsProvider
}), }),
new TestOptionsManager<MvcOptions>()) new TestOptionsManager<MvcOptions>())