diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs index c0f2ee588f..394520027f 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; @@ -66,6 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var displayAttribute = attributes.OfType().FirstOrDefault(); var displayColumnAttribute = attributes.OfType().FirstOrDefault(); var displayFormatAttribute = attributes.OfType().FirstOrDefault(); + var displayNameAttribute = attributes.OfType().FirstOrDefault(); var hiddenInputAttribute = attributes.OfType().FirstOrDefault(); var scaffoldColumnAttribute = attributes.OfType().FirstOrDefault(); var uiHintAttribute = attributes.OfType().FirstOrDefault(); @@ -127,7 +129,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal } // DisplayName - if (displayAttribute != null) + // DisplayAttribute has precendence over DisplayNameAttribute. + if (displayAttribute != null && displayAttribute.GetName() != null) { if (localizer != null && !string.IsNullOrEmpty(displayAttribute.Name) && @@ -140,6 +143,18 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal displayMetadata.DisplayName = () => displayAttribute.GetName(); } } + else if (displayNameAttribute != null) + { + if (localizer != null && + !string.IsNullOrEmpty(displayNameAttribute.DisplayName)) + { + displayMetadata.DisplayName = () => localizer[displayNameAttribute.DisplayName]; + } + else + { + displayMetadata.DisplayName = () => displayNameAttribute.DisplayName; + } + } // EditFormatString if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index c8ee22183d..c1705739d4 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Globalization; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal { new DisplayFormatAttribute() { HtmlEncode = false }, d => d.HtmlEncode, false }, { new DisplayFormatAttribute() { NullDisplayText = "(null)" }, d => d.NullDisplayText, "(null)" }, + { new DisplayNameAttribute("DisplayNameValue"), d => d.DisplayName(), "DisplayNameValue"}, { new HiddenInputAttribute() { DisplayValue = false }, d => d.HideSurroundingHtml, true }, { new ScaffoldColumnAttribute(scaffold: false), d => d.ShowForDisplay, false }, @@ -166,6 +168,124 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal Assert.False(context.BindingMetadata.IsReadOnly); } + [Fact] + public void CreateDisplayMetadata_DisplayAttribute_OverridesDisplayNameAttribute() + { + // Arrange + var localizationOptions = new TestOptionsManager(); + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: null); + + var displayName = new DisplayNameAttribute("DisplayNameAttributeValue"); + var display = new DisplayAttribute() + { + Name = "DisplayAttributeValue" + }; + + var attributes = new Attribute[] { display, displayName }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Equal("DisplayAttributeValue", context.DisplayMetadata.DisplayName()); + } + + [Fact] + public void CreateDisplayMetadata_DisplayAttribute_OverridesDisplayNameAttribute_IfNameEmpty() + { + // Arrange + var localizationOptions = new TestOptionsManager(); + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: null); + + var displayName = new DisplayNameAttribute("DisplayNameAttributeValue"); + var display = new DisplayAttribute() + { + Name = string.Empty + }; + + var attributes = new Attribute[] { display, displayName }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Equal(string.Empty, context.DisplayMetadata.DisplayName()); + } + + [Fact] + public void CreateDisplayMetadata_DisplayAttribute_DoesNotOverrideDisplayNameAttribute_IfNameNull() + { + // Arrange + var localizationOptions = new TestOptionsManager(); + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: null); + + var displayName = new DisplayNameAttribute("DisplayNameAttributeValue"); + var display = new DisplayAttribute() + { + Description = "This is a description" + }; + + var attributes = new Attribute[] { display, displayName }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Equal("DisplayNameAttributeValue", context.DisplayMetadata.DisplayName()); + } + + [Fact] + public void CreateDisplayMetadata_DisplayNameAttribute_LocalizesDisplayName() + { + // Arrange + var sharedLocalizer = new Mock(MockBehavior.Strict); + sharedLocalizer + .Setup(s => s["DisplayNameValue"]) + .Returns(new LocalizedString("DisplayNameValue", "Name from DisplayNameAttribute")); + + var stringLocalizerFactoryMock = new Mock(MockBehavior.Strict); + stringLocalizerFactoryMock + .Setup(s => s.Create(typeof(EmptyClass))) + .Returns(() => sharedLocalizer.Object); + + var localizationOptions = new TestOptionsManager(); + localizationOptions.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) => + { + return stringLocalizerFactory.Create(typeof(EmptyClass)); + }; + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: stringLocalizerFactoryMock.Object); + + var displayName = new DisplayNameAttribute("DisplayNameValue"); + + var attributes = new Attribute[] { displayName }; + var key = ModelMetadataIdentity.ForType(typeof(string)); + var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Equal("Name from DisplayNameAttribute", context.DisplayMetadata.DisplayName()); + } + [Fact] public void CreateDisplayMetadata_DisplayAttribute_NameFromResources_UsesDataAnnotationLocalizerProvider() {