Fixes #5198 Stops caching of Enum display values (#5185)

Fixes #5197 GetEnumSelectList uses IStringLocalizer
Fixes #4215 Html.DisplayFor now checks DisplayAttributes on enums
This commit is contained in:
Ryan Brandenburg 2016-09-07 16:00:17 -07:00 committed by GitHub
parent 21236cc98e
commit 830983a477
9 changed files with 415 additions and 29 deletions

View File

@ -10,8 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary>
public struct EnumGroupAndName
{
private Func<string> _name;
/// <summary>
/// Initializes a new instance of the EnumGroupAndName structure.
/// Initializes a new instance of the <see cref="EnumGroupAndName"/> structure. This constructor should
/// not be used in any site where localization is important.
/// </summary>
/// <param name="group">The group name.</param>
/// <param name="name">The name.</param>
@ -28,7 +31,30 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
}
Group = group;
Name = name;
_name = () => name;
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumGroupAndName"/> structure.
/// </summary>
/// <param name="group">The group name.</param>
/// <param name="name">A <see cref="Func{String}"/> which will return the name.</param>
public EnumGroupAndName(
string group,
Func<string> name)
{
if (group == null)
{
throw new ArgumentNullException(nameof(group));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
Group = group;
_name = name;
}
/// <summary>
@ -39,6 +65,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <summary>
/// Gets the name.
/// </summary>
public string Name { get; }
public string Name
{
get
{
return _name();
}
}
}
}

View File

@ -153,15 +153,17 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
// Dictionary does not guarantee order will be preserved.
var groupedDisplayNamesAndValues = new List<KeyValuePair<EnumGroupAndName, string>>();
var namesAndValues = new Dictionary<string, string>();
var enumLocalizer = _stringLocalizerFactory?.Create(underlyingType);
foreach (var name in Enum.GetNames(underlyingType))
{
var field = underlyingType.GetField(name);
var displayName = GetDisplayName(field);
var groupName = GetDisplayGroup(field);
var value = ((Enum)field.GetValue(obj: null)).ToString("d");
groupedDisplayNamesAndValues.Add(new KeyValuePair<EnumGroupAndName, string>(
new EnumGroupAndName(groupName, displayName),
new EnumGroupAndName(
groupName,
() => GetDisplayName(field, enumLocalizer)),
value));
namesAndValues.Add(name, value);
}
@ -291,18 +293,19 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
}
}
// Return non-empty name specified in a [Display] attribute for a field, if any; field.Name otherwise.
private static string GetDisplayName(FieldInfo field)
private static string GetDisplayName(FieldInfo field, IStringLocalizer stringLocalizer)
{
var display = field.GetCustomAttribute<DisplayAttribute>(inherit: false);
if (display != null)
{
// Note [Display(Name = "")] is allowed.
// Note [Display(Name = "")] is allowed but we will not attempt to localize the empty name.
var name = display.GetName();
if (name != null)
if (stringLocalizer != null && !string.IsNullOrEmpty(name) && display.ResourceType == null)
{
return name;
name = stringLocalizer[name];
}
return name ?? field.Name;
}
return field.Name;

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@ -522,6 +523,23 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
string templateName,
object additionalViewData)
{
var modelEnum = modelExplorer.Model as Enum;
if (modelExplorer.Metadata.IsEnum && modelEnum != null)
{
var value = modelEnum.ToString("d");
var enumGrouped = modelExplorer.Metadata.EnumGroupedDisplayNamesAndValues;
Debug.Assert(enumGrouped != null);
foreach (var kvp in enumGrouped)
{
if (kvp.Value == value)
{
// Creates a ModelExplorer with the same Metadata except that the Model is a string instead of an Enum
modelExplorer = modelExplorer.GetExplorerForModel(kvp.Key.Name);
break;
}
}
}
var templateBuilder = new TemplateBuilder(
_viewEngine,
_bufferScope,

View File

@ -2,12 +2,17 @@
// 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;
using Microsoft.AspNetCore.Testing;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.Extensions.Localization;
using Moq;
using Xunit;
@ -353,13 +358,13 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
.Setup(s => s["Model_Name"])
.Returns(new LocalizedString("Model_Name", "name from localizer"));
.Returns(() => new LocalizedString("Model_Name", "name from localizer " + CultureInfo.CurrentCulture));
stringLocalizer
.Setup(s => s["Model_Description"])
.Returns(new LocalizedString("Model_Description", "description from localizer"));
.Returns(() => new LocalizedString("Model_Description", "description from localizer " + CultureInfo.CurrentCulture));
stringLocalizer
.Setup(s => s["Model_Prompt"])
.Returns(new LocalizedString("Model_Prompt", "prompt from localizer"));
.Returns(() => new LocalizedString("Model_Prompt", "prompt from localizer " + CultureInfo.CurrentCulture));
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactory
@ -383,9 +388,18 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
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());
using (new CultureReplacer("en-US", "en-US"))
{
Assert.Equal("name from localizer en-US", context.DisplayMetadata.DisplayName());
Assert.Equal("description from localizer en-US", context.DisplayMetadata.Description());
Assert.Equal("prompt from localizer en-US", context.DisplayMetadata.Placeholder());
}
using (new CultureReplacer("fr-FR", "fr-FR"))
{
Assert.Equal("name from localizer fr-FR", context.DisplayMetadata.DisplayName());
Assert.Equal("description from localizer fr-FR", context.DisplayMetadata.Description());
Assert.Equal("prompt from localizer fr-FR", context.DisplayMetadata.Placeholder());
}
}
[Theory]
@ -589,6 +603,48 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Equal(expectedDictionary, context.DisplayMetadata.EnumNamesAndValues);
}
[Fact]
public void CreateDisplayMetadata_DisplayName_LocalizeWithStringLocalizer()
{
// Arrange
var expectedKeyValuePairs = new List<KeyValuePair<EnumGroupAndName, string>>
{
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName("Zero", string.Empty), "0"),
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayNames.One)), "1"),
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName(string.Empty, "dos value"), "2"),
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName(string.Empty, "tres value"), "3"),
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName(string.Empty, "name from resources"), "-2"),
new KeyValuePair<EnumGroupAndName, string>(new EnumGroupAndName("Negatives", "menos uno value"), "-1"),
};
var type = typeof(EnumWithDisplayNames);
var attributes = new object[0];
var key = ModelMetadataIdentity.ForType(type);
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
.Setup(s => s[It.IsAny<string>()])
.Returns<string>((index) => new LocalizedString(index, index + " value"));
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactory
.Setup(f => f.Create(It.IsAny<Type>()))
.Returns(stringLocalizer.Object);
var provider = new DataAnnotationsMetadataProvider(stringLocalizerFactory.Object);
// Act
provider.CreateDisplayMetadata(context);
// Assert
Assert.Equal(
expectedKeyValuePairs,
context.DisplayMetadata.EnumGroupedDisplayNamesAndValues,
KVPEnumGroupAndNameComparer.Instance);
}
// Type -> expected EnumDisplayNamesAndValues
public static TheoryData<Type, IEnumerable<KeyValuePair<EnumGroupAndName, string>>> EnumDisplayNamesData
{
@ -719,12 +775,90 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
provider.CreateDisplayMetadata(context);
// Assert
// OrderBy is used because the order of the results may very depending on the platform / client.
Assert.Equal(
expectedKeyValuePairs?.OrderBy(item => item.Key.Group, StringComparer.Ordinal)
.ThenBy(item => item.Key.Name, StringComparer.Ordinal),
context.DisplayMetadata.EnumGroupedDisplayNamesAndValues?.OrderBy(item => item.Key.Group, StringComparer.Ordinal)
.ThenBy(item => item.Key.Name, StringComparer.Ordinal));
expectedKeyValuePairs,
context.DisplayMetadata.EnumGroupedDisplayNamesAndValues,
KVPEnumGroupAndNameComparer.Instance);
}
[Fact]
public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_NameWithNoIStringLocalizerAndNoResourceType()
{
// Arrange & Act
var enumNameAndGroup = GetLocalizedEnumGroupedDisplayNamesAndValues(useStringLocalizer: false);
// Assert
var groupTwo = Assert.Single(enumNameAndGroup, e => e.Value.Equals("2", StringComparison.Ordinal));
using (new CultureReplacer("en-US", "en-US"))
{
Assert.Equal("Loc_Two_Name", groupTwo.Key.Name);
}
using (new CultureReplacer("fr-FR", "fr-FR"))
{
Assert.Equal("Loc_Two_Name", groupTwo.Key.Name);
}
}
[Fact]
public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_NameWithIStringLocalizerAndNoResourceType()
{
// Arrange & Act
var enumNameAndGroup = GetLocalizedEnumGroupedDisplayNamesAndValues(useStringLocalizer: true);
// Assert
var groupTwo = Assert.Single(enumNameAndGroup, e => e.Value.Equals("2", StringComparison.Ordinal));
using (new CultureReplacer("en-US", "en-US"))
{
Assert.Equal("Loc_Two_Name en-US", groupTwo.Key.Name);
}
using (new CultureReplacer("fr-FR", "fr-FR"))
{
Assert.Equal("Loc_Two_Name fr-FR", groupTwo.Key.Name);
}
}
[Fact]
public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_NameWithNoIStringLocalizerAndResourceType()
{
// Arrange & Act
var enumNameAndGroup = GetLocalizedEnumGroupedDisplayNamesAndValues(useStringLocalizer: false);
// Assert
var groupThree = Assert.Single(enumNameAndGroup, e => e.Value.Equals("3", StringComparison.Ordinal));
using (new CultureReplacer("en-US", "en-US"))
{
Assert.Equal("type three name en-US", groupThree.Key.Name);
}
using (new CultureReplacer("fr-FR", "fr-FR"))
{
Assert.Equal("type three name fr-FR", groupThree.Key.Name);
}
}
[Fact]
public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_NameWithIStringLocalizerAndResourceType()
{
// Arrange & Act
var enumNameAndGroup = GetLocalizedEnumGroupedDisplayNamesAndValues(useStringLocalizer: true);
var groupThree = Assert.Single(enumNameAndGroup, e => e.Value.Equals("3", StringComparison.Ordinal));
// Assert
using (new CultureReplacer("en-US", "en-US"))
{
Assert.Equal("type three name en-US", groupThree.Key.Name);
}
using (new CultureReplacer("fr-FR", "fr-FR"))
{
Assert.Equal("type three name fr-FR", groupThree.Key.Name);
}
}
[Fact]
@ -848,6 +982,70 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Same(attribute, validatorMetadata);
}
private IEnumerable<KeyValuePair<EnumGroupAndName, string>> GetLocalizedEnumGroupedDisplayNamesAndValues(
bool useStringLocalizer)
{
var provider = CreateIStringLocalizerProvider(useStringLocalizer);
var key = ModelMetadataIdentity.ForType(typeof(EnumWithLocalizedDisplayNames));
var attributes = new object[0];
var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes));
provider.CreateDisplayMetadata(context);
return context.DisplayMetadata.EnumGroupedDisplayNamesAndValues;
}
private DataAnnotationsMetadataProvider CreateIStringLocalizerProvider(bool useStringLocalizer)
{
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
.Setup(loc => loc[It.IsAny<string>()])
.Returns<string>((k =>
{
return new LocalizedString(k, $"{k} {CultureInfo.CurrentCulture}");
}));
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>(MockBehavior.Strict);
stringLocalizerFactory
.Setup(factory => factory.Create(typeof(EnumWithLocalizedDisplayNames)))
.Returns(stringLocalizer.Object);
return new DataAnnotationsMetadataProvider(
useStringLocalizer ? stringLocalizerFactory.Object : null);
}
private class KVPEnumGroupAndNameComparer : IEqualityComparer<KeyValuePair<EnumGroupAndName, string>>
{
public static readonly IEqualityComparer<KeyValuePair<EnumGroupAndName, string>> Instance = new KVPEnumGroupAndNameComparer();
private KVPEnumGroupAndNameComparer()
{
}
public bool Equals(KeyValuePair<EnumGroupAndName, string> x, KeyValuePair<EnumGroupAndName, string> y)
{
using (new CultureReplacer(string.Empty, string.Empty))
{
return x.Key.Name.Equals(y.Key.Name, StringComparison.Ordinal)
&& x.Key.Group.Equals(y.Key.Group, StringComparison.Ordinal);
}
}
public int GetHashCode(KeyValuePair<EnumGroupAndName, string> obj)
{
using (new CultureReplacer(string.Empty, string.Empty))
{
var hashcode = HashCodeCombiner.Start();
hashcode.Add(obj.Key.Name);
hashcode.Add(obj.Key.Group);
return hashcode.CombinedHash;
}
}
}
private class TestValidationAttribute : ValidationAttribute, IClientModelValidator
{
public void AddValidation(ClientModelValidationContext context)
@ -874,6 +1072,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
public string Name { get; set; }
}
private enum EnumWithLocalizedDisplayNames
{
[Display(Name = "Loc_Two_Name")]
Two = 2,
[Display(Name = nameof(TestResources.Type_Three_Name), ResourceType = typeof(TestResources))]
Three = 3
}
private enum EmptyEnum
{
}

View File

@ -10,11 +10,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// internal properties.
public static class TestResources
{
public static string DisplayAttribute_Description { get; } = Resources.DisplayAttribute_Description;
public static string Type_Three_Name => "type three name " + CultureInfo.CurrentCulture;
public static string DisplayAttribute_Name { get; } = Resources.DisplayAttribute_Name;
public static string DisplayAttribute_Description => Resources.DisplayAttribute_Description;
public static string DisplayAttribute_Prompt { get; } = Resources.DisplayAttribute_Prompt;
public static string DisplayAttribute_Name => Resources.DisplayAttribute_Name;
public static string DisplayAttribute_Prompt => Resources.DisplayAttribute_Prompt;
public static string DisplayAttribute_CultureSensitiveName =>
Resources.DisplayAttribute_Name + CultureInfo.CurrentUICulture;

View File

@ -7,6 +7,7 @@ using System.Reflection;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Localization;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding
@ -14,13 +15,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
internal class TestModelMetadataProvider : DefaultModelMetadataProvider
{
// Creates a provider with all the defaults - includes data annotations
public static IModelMetadataProvider CreateDefaultProvider()
public static IModelMetadataProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null)
{
var detailsProviders = new IMetadataDetailsProvider[]
{
new DefaultBindingMetadataProvider(),
new DefaultValidationMetadataProvider(),
new DataAnnotationsMetadataProvider(stringLocalizerFactory: null),
new DataAnnotationsMetadataProvider(stringLocalizerFactory),
new DataMemberRequiredBindingMetadataProvider(),
};

View File

@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
@ -161,9 +162,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(
TModel model,
ICompositeViewEngine viewEngine)
ICompositeViewEngine viewEngine,
IStringLocalizerFactory stringLocalizerFactory = null)
{
return GetHtmlHelper(model, CreateUrlHelper(), viewEngine, TestModelMetadataProvider.CreateDefaultProvider());
return GetHtmlHelper(
model,
CreateUrlHelper(),
viewEngine,
TestModelMetadataProvider.CreateDefaultProvider(stringLocalizerFactory));
}
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(

View File

@ -2,10 +2,12 @@
// 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 System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.Localization;
using Moq;
using Xunit;
@ -17,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
public void DisplayHelpers_FindsModel_WhenViewDataIsNotSet()
{
// Arrange
var expected = $"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[SomeProperty]]</div>{Environment.NewLine}" +
var expected = $"<div class=\"HtmlEncode[[display-label]]\">HtmlEncode[[SomeProperty]]</div>{Environment.NewLine}" +
$"<div class=\"HtmlEncode[[display-field]]\">HtmlEncode[[PropValue]]</div>{Environment.NewLine}";
var model = new SomeModel
{
@ -223,6 +225,75 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
Assert.Equal("ViewDataValue", HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Fact]
public void DisplayFor_EnumProperty_IStringLocalizedValue()
{
// Arrange
var model = new StatusModel
{
Status = Status.Created
};
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "DisplayTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("SomeView", view.Object));
var stringLocalizer = new Mock<IStringLocalizer>(MockBehavior.Strict);
stringLocalizer
.Setup(s => s["CreatedKey"])
.Returns<string>((key) =>
{
return new LocalizedString(key, "created from IStringLocalizer");
});
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>();
stringLocalizerFactory
.Setup(s => s.Create(typeof(Status)))
.Returns(stringLocalizer.Object);
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object, stringLocalizerFactory.Object);
// Act
var displayResult = helper.DisplayFor(m => m.Status);
// Assert
Assert.Equal("created from IStringLocalizer", HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Fact]
public void DisplayFor_EnumProperty_ResourceTypeLocalizedValue()
{
// Arrange
var model = new StatusModel
{
Status = Status.Faulted
};
var view = new Mock<IView>();
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
.Callback((ViewContext v) => v.Writer.WriteAsync(v.ViewData.TemplateInfo.FormattedModelValue.ToString()))
.Returns(Task.FromResult(0));
var viewEngine = new Mock<ICompositeViewEngine>(MockBehavior.Strict);
viewEngine
.Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny<string>(), /*isMainPage*/ false))
.Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty<string>()));
viewEngine
.Setup(v => v.FindView(It.IsAny<ActionContext>(), "DisplayTemplates/Status", /*isMainPage*/ false))
.Returns(ViewEngineResult.Found("SomeView", view.Object));
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object);
// Act
var displayResult = helper.DisplayFor(m => m.Status);
// Assert
Assert.Equal("Faulted from ResourceType", HtmlContentUtilities.HtmlContentToString(displayResult));
}
[Fact]
public void DisplayFor_UsesTemplateNameAndHtmlFieldName()
{
@ -356,5 +427,24 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
{
public string SomeProperty { get; set; }
}
private class StatusModel
{
public Status Status { get; set; }
}
public class StatusResource
{
public static string FaultedKey { get { return "Faulted from ResourceType"; } }
}
private enum Status : byte
{
[Display(Name = "CreatedKey")]
Created,
[Display(Name = "FaultedKey", ResourceType = typeof(StatusResource))]
Faulted,
Done
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -12,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Localization;
using Moq;
using Xunit;
@ -1298,6 +1300,32 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
$"The type '{ typeof(StructWithFields).FullName }' is not supported.");
}
[Fact]
[ReplaceCulture("en-US", "en-US")]
public void GetEnumSelectListTEnum_DisplayAttributeUsesIStringLocalizer()
{
// Arrange
var stringLocalizer = new Mock<IStringLocalizer>();
stringLocalizer
.Setup(s => s[It.IsAny<string>()])
.Returns<string>((s) => { return new LocalizedString(s, s + " " + CultureInfo.CurrentCulture); });
var stringLocalizerFactory = new Mock<IStringLocalizerFactory>();
stringLocalizerFactory
.Setup(s => s.Create(It.IsAny<Type>()))
.Returns(stringLocalizer.Object);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(stringLocalizerFactory.Object);
var metadata = metadataProvider.GetMetadataForType(typeof(EnumWithFields));
var htmlHelper = new TestHtmlHelper(metadataProvider);
// Act
var result = htmlHelper.GetEnumSelectList<EnumWithDisplayNames>();
// Assert
var zeroSelect = Assert.Single(result, s => s.Value.Equals("0", StringComparison.Ordinal));
Assert.Equal("cero en-US", zeroSelect.Text);
}
[Fact]
public void GetEnumSelectListTEnum_WrapsGetEnumSelectListModelMetadata()
{