diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs index 15588722a6..4baad584d7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DataAnnotationsMetadataProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// An implementation of and for /// the System.ComponentModel.DataAnnotations attribute classes. /// - public class DataAnnotationsMetadataProvider : + public class DataAnnotationsMetadataProvider : IBindingMetadataProvider, IDisplayMetadataProvider, IValidationMetadataProvider @@ -72,7 +72,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // Description if (displayAttribute != null) { - displayMetadata.Description = displayAttribute.GetDescription(); + displayMetadata.Description = () => displayAttribute.GetDescription(); } // DisplayFormat @@ -84,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata // DisplayName if (displayAttribute != null) { - displayMetadata.DisplayName = displayAttribute.GetName(); + displayMetadata.DisplayName = () => displayAttribute.GetName(); } if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) @@ -216,8 +216,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata foreach (var attribute in context.Attributes.OfType()) { - // If another provider has already added this attribute, do not repeat it. - // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and // IClientModelValidator) to be added to the ValidationMetadata twice. // This is to ensure we do not end up with duplication validation rules on the client side. if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index 85cd371b8a..fe0f44204a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -179,7 +179,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { get { - return DisplayMetadata.Description; + if (DisplayMetadata.Description == null) + { + return null; + } + + return DisplayMetadata.Description(); } } @@ -197,7 +202,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { get { - return DisplayMetadata.DisplayName; + if (DisplayMetadata.DisplayName == null) + { + return null; + } + + return DisplayMetadata.DisplayName(); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs index b4dc7253e1..d6ecfd9251 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata @@ -28,10 +29,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public string DataTypeName { get; set; } /// - /// Gets or sets a model description. - /// See + /// Gets or sets a delegate which is used to get a value for the + /// model description. See . /// - public string Description { get; set; } + public Func Description { get; set; } /// /// Gets or sets a display format string for the model. @@ -40,10 +41,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public string DisplayFormatString { get; set; } /// - /// Gets or sets a display name for the model. - /// See + /// Gets or sets a delegate delegate which is used to get a value for the + /// display name of the model. See . /// - public string DisplayName { get; set; } + public Func DisplayName { get; set; } /// /// Gets or sets an edit format string for the model. diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DataAnnotationsMetadataProviderTest.cs index 0d98e00894..e6152a0718 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/DataAnnotationsMetadataProviderTest.cs @@ -20,8 +20,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { { new DataTypeAttribute(DataType.Duration), d => d.DataTypeName, DataType.Duration.ToString() }, - { new DisplayAttribute() { Description = "d" }, d => d.Description, "d" }, - { new DisplayAttribute() { Name = "DN" }, d => d.DisplayName, "DN" }, + { new DisplayAttribute() { Description = "d" }, d => d.Description(), "d" }, + { new DisplayAttribute() { Name = "DN" }, d => d.DisplayName(), "DN" }, { new DisplayAttribute() { Order = 3 }, d => d.Order, 3 }, { new DisplayColumnAttribute("Property"), d => d.SimpleDisplayProperty, "Property" }, @@ -175,7 +175,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata provider.GetDisplayMetadata(context); // Assert - Assert.Equal("name from resources", context.DisplayMetadata.DisplayName); + Assert.Equal("name from resources", context.DisplayMetadata.DisplayName()); } // This is IMPORTANT. Product code needs to use GetDescription() instead of .Description. It's easy to regress. @@ -204,7 +204,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata provider.GetDisplayMetadata(context); // Assert - Assert.Equal("description from resources", context.DisplayMetadata.Description); + Assert.Equal("description from resources", context.DisplayMetadata.Description()); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderTest.cs index 42b397df92..5ff3946e60 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderTest.cs @@ -7,6 +7,8 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Threading; +using Microsoft.AspNet.Testing; using Microsoft.Framework.Internal; using Xunit; @@ -420,6 +422,58 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal("description", result); } + [Fact] + public void DisplayName_FromResources_GetsRecomputed() + { + // Arrange + var display = new DisplayAttribute() + { + Name = nameof(TestResources.DisplayAttribute_CultureSensitiveName), + ResourceType = typeof(TestResources), + }; + + var provider = CreateProvider(new[] { display }); + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act & Assert + var cultures = new[] { "fr-FR", "en-US", "en-GB" }; + foreach (var culture in cultures) + { + using (new CultureReplacer(uiCulture: culture)) + { + // Later iterations ensure value is recomputed. + var result = metadata.DisplayName; + Assert.Equal("name from resources" + culture, result); + } + } + } + + [Fact] + public void Description_FromResources_GetsRecomputed() + { + // Arrange + var display = new DisplayAttribute() + { + Description = nameof(TestResources.DisplayAttribute_CultureSensitiveDescription), + ResourceType = typeof(TestResources), + }; + + var provider = CreateProvider(new[] { display }); + var metadata = provider.GetMetadataForType(typeof(string)); + + // Act & Assert + var cultures = new[] { "fr-FR", "en-US", "en-GB" }; + foreach (var culture in cultures) + { + using (new CultureReplacer(uiCulture: culture)) + { + // Later iterations ensure value is recomputed. + var result = metadata.Description; + Assert.Equal("description from resources" + culture, result); + } + } + } + [Fact] public void DataTypeName_Null_IfHtmlEncodeTrue() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TestResources.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TestResources.cs index 3ce4cc280b..5ded5a2057 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TestResources.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/TestResources.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading; using Microsoft.AspNet.Mvc.Core.Test; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -12,5 +13,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public static string DisplayAttribute_Description { get; } = Resources.DisplayAttribute_Description; public static string DisplayAttribute_Name { get; } = Resources.DisplayAttribute_Name; + + public static string DisplayAttribute_CultureSensitiveName => + Resources.DisplayAttribute_Name + Thread.CurrentThread.CurrentUICulture; + + public static string DisplayAttribute_CultureSensitiveDescription => + Resources.DisplayAttribute_Description + Thread.CurrentThread.CurrentUICulture; } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs index ce39b0429e..b0b1c99b2a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForType() - .DisplayDetails(dd => dd.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = () => displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); var enumerableHelper = DefaultTemplatesUtilities.GetHtmlHelperForEnumerable(provider: provider); @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForProperty("Property1") - .DisplayDetails(dd => dd.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = () => displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); var enumerableHelper = DefaultTemplatesUtilities.GetHtmlHelperForEnumerable(provider: provider); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs index 428f1b29ee..456445a423 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperLabelExtensionsTest.cs @@ -110,7 +110,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForType() - .DisplayDetails(dd => dd.DisplayName = string.Empty); + .DisplayDetails(dd => dd.DisplayName = () => string.Empty); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); @@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForType() - .DisplayDetails(dd => dd.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = () => displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider); @@ -158,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForProperty("Property1") - .DisplayDetails(dd => dd.DisplayName = string.Empty); + .DisplayDetails(dd => dd.DisplayName = () => string.Empty); var modelExplorer = provider .GetModelExplorerForType(typeof(DefaultTemplatesUtilities.ObjectTemplateModel), model: null) @@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Core var provider = new TestModelMetadataProvider(); provider .ForProperty("Property1") - .DisplayDetails(dd => dd.DisplayName = displayName); + .DisplayDetails(dd => dd.DisplayName = () => displayName); var helper = DefaultTemplatesUtilities.GetHtmlHelper(provider: provider);