Use DataAnnotationLocalizerProvider for Display localization

This commit is contained in:
Ryan Brandenburg 2016-09-15 13:38:54 -07:00
parent 9c6c8410e2
commit 03b3d6bec8
5 changed files with 148 additions and 36 deletions

View File

@ -9,6 +9,7 @@ using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
{
@ -22,9 +23,18 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IValidationMetadataProvider
{
private readonly IStringLocalizerFactory _stringLocalizerFactory;
private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions;
public DataAnnotationsMetadataProvider(IStringLocalizerFactory stringLocalizerFactory)
public DataAnnotationsMetadataProvider(
IOptions<MvcDataAnnotationsLocalizationOptions> options,
IStringLocalizerFactory stringLocalizerFactory)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_localizationOptions = options.Value;
_stringLocalizerFactory = stringLocalizerFactory;
}
@ -88,9 +98,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
displayMetadata.DataTypeName = DataType.Html.ToString();
}
var containerType = context.Key.ContainerType ?? context.Key.ModelType;
var localizer = _stringLocalizerFactory?.Create(containerType);
IStringLocalizer localizer = null;
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory);
}
// Description
if (displayAttribute != null)

View File

@ -50,7 +50,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
throw new ArgumentNullException(nameof(options));
}
options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider(_stringLocalizerFactory));
options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider(
_dataAnnotationLocalizationOptions,
_stringLocalizerFactory));
options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider(
_validationAttributeAdapterProvider,

View File

@ -2,12 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -65,7 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
object expected)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(new object[] { attribute }));
@ -82,7 +81,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_FindsDisplayFormat_FromDataType()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var dataType = new DataTypeAttribute(DataType.Currency);
var displayFormat = dataType.DisplayFormat; // Non-null for DataType.Currency.
@ -102,7 +103,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_FindsDisplayFormat_OverridingDataType()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var dataType = new DataTypeAttribute(DataType.Time); // Has a non-null DisplayFormat.
var displayFormat = new DisplayFormatAttribute() // But these values override the values from DataType
@ -125,7 +128,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_EditableAttributeFalse_SetsReadOnlyTrue()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var editable = new EditableAttribute(allowEdit: false);
@ -144,7 +149,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_EditableAttributeTrue_SetsReadOnlyFalse()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var editable = new EditableAttribute(allowEdit: true);
@ -159,12 +166,52 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.False(context.BindingMetadata.IsReadOnly);
}
[Fact]
public void CreateDisplayMetadata_DisplayAttribute_NameFromResources_UsesDataAnnotationLocalizerProvider()
{
// Arrange
var sharedLocalizer = new Mock<IStringLocalizer>(MockBehavior.Loose);
var stringLocalizerFactoryMock = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactoryMock
.Setup(s => s.Create(typeof(EmptyClass)))
.Returns(() => sharedLocalizer.Object);
var options = new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>();
bool dataAnnotationLocalizerProviderWasUsed = false;
options.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) =>
{
dataAnnotationLocalizerProviderWasUsed = true;
return stringLocalizerFactory.Create(typeof(EmptyClass));
};
var provider = new DataAnnotationsMetadataProvider(options, stringLocalizerFactoryMock.Object);
var display = new DisplayAttribute()
{
Name = "DisplayName"
};
var attributes = new Attribute[] { display };
var key = ModelMetadataIdentity.ForType(typeof(string));
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
// Act
provider.CreateDisplayMetadata(context);
context.DisplayMetadata.DisplayName();
// Assert
Assert.True(dataAnnotationLocalizerProviderWasUsed, "DataAnnotationLocalizerProvider wasn't used by DisplayMetadata");
}
// 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 provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
@ -199,7 +246,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory.Object);
var display = new DisplayAttribute()
{
@ -234,7 +283,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory.Object);
var display = new DisplayAttribute()
{
@ -263,7 +314,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_DisplayAttribute_DescriptionFromResources_NullLocalizer()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
@ -298,7 +351,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(() => stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory.Object);
var display = new DisplayAttribute()
{
@ -327,7 +382,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_DisplayAttribute_PromptFromResources_NullLocalizer()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var display = new DisplayAttribute()
{
@ -366,12 +423,20 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
.Setup(s => s["Model_Prompt"])
.Returns(() => new LocalizedString("Model_Prompt", "prompt from localizer " + CultureInfo.CurrentCulture));
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactory
var stringLocalizerFactoryMock = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactoryMock
.Setup(f => f.Create(It.IsAny<Type>()))
.Returns(stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var options = new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>();
options.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) =>
{
return stringLocalizerFactory.Create(type);
};
var provider = new DataAnnotationsMetadataProvider(
options,
stringLocalizerFactoryMock.Object);
var display = new DisplayAttribute()
{
@ -423,7 +488,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_IsEnum_ReflectsModelType(Type type, bool expectedIsEnum)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
@ -457,7 +524,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateDisplayMetadata_IsFlagsEnum_ReflectsModelType(Type type, bool expectedIsFlagsEnum)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
@ -589,7 +658,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IReadOnlyDictionary<string, string> expectedDictionary)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
@ -633,7 +704,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
.Setup(f => f.Create(It.IsAny<Type>()))
.Returns(stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory.Object);
// Act
provider.CreateDisplayMetadata(context);
@ -765,7 +838,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
IEnumerable<KeyValuePair<EnumGroupAndName, string>> expectedKeyValuePairs)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var key = ModelMetadataIdentity.ForType(type);
var attributes = new object[0];
@ -865,7 +940,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationMetadata_RequiredAttribute_SetsIsRequiredToTrue()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var required = new RequiredAttribute();
@ -887,7 +964,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationMetadata_NoRequiredAttribute_IsRequiredLeftAlone(bool? initialValue)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -908,7 +987,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingMetadata_RequiredAttribute_IsBindingRequiredLeftAlone(bool initialValue)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var attributes = new Attribute[] { new RequiredAttribute() };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -929,7 +1010,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateBindingDetails_NoEditableAttribute_IsReadOnlyLeftAlone(bool? initialValue)
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var attributes = new Attribute[] { };
var key = ModelMetadataIdentity.ForProperty(typeof(int), "Length", typeof(string));
@ -947,7 +1030,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationDetails_ValidatableObject_ReturnsObject()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute };
@ -966,7 +1051,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public void CreateValidationDetails_ValidatableObject_AlreadyInContext_Ignores()
{
// Arrange
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory: null);
var provider = new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null);
var attribute = new TestValidationAttribute();
var attributes = new Attribute[] { attribute };
@ -1012,6 +1099,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
.Returns(stringLocalizer.Object);
return new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
useStringLocalizer ? stringLocalizerFactory.Object : null);
}

View File

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

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
@ -21,7 +22,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(stringLocalizerFactory),
new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory),
new DataMemberRequiredBindingMetadataProvider(),
};
@ -35,7 +38,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null),
new DataMemberRequiredBindingMetadataProvider(),
};
@ -70,7 +75,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
new DataAnnotationsMetadataProvider(
new TestOptionsManager<MvcDataAnnotationsLocalizationOptions>(),
stringLocalizerFactory: null),
detailsProvider
}),
new TestOptionsManager<MvcOptions>())