Fixes #5197 GetEnumSelectList uses IStringLocalizer Fixes #4215 Html.DisplayFor now checks DisplayAttributes on enums
This commit is contained in:
parent
21236cc98e
commit
830983a477
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue