From 575fe68b2bcd5adf9aaa29cdf58f1e53a28379a1 Mon Sep 17 00:00:00 2001 From: Derek Gray Date: Wed, 22 Mar 2017 10:15:01 -0500 Subject: [PATCH] Order enum values by DisplayAttribute.Order Fixes #4297 --- .../DataAnnotationsMetadataProvider.cs | 17 ++++--- .../DataAnnotationsMetadataProviderTest.cs | 44 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs index 394520027f..16cd00814f 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -176,15 +176,20 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal // EnumDisplayNamesAndValues and EnumNamesAndValues // - // Order EnumDisplayNamesAndValues to match Enum.GetNames(). That method orders by absolute value, - // then its behavior is undefined (but hopefully stable). Add to EnumNamesAndValues in same order but - // Dictionary does not guarantee order will be preserved. + // Order EnumDisplayNamesAndValues by DisplayAttribute.Order, then by the order of Enum.GetNames(). + // That method orders by absolute value, then its behavior is undefined (but hopefully stable). + // Add to EnumNamesAndValues in same order but Dictionary does not guarantee order will be preserved. + var groupedDisplayNamesAndValues = new List>(); var namesAndValues = new Dictionary(); var enumLocalizer = _stringLocalizerFactory?.Create(underlyingType); - foreach (var name in Enum.GetNames(underlyingType)) + + var enumFields = Enum.GetNames(underlyingType) + .Select(name => underlyingType.GetField(name)) + .OrderBy(field => field.GetCustomAttribute(inherit: false)?.GetOrder() ?? 1000); + + foreach (var field in enumFields) { - var field = underlyingType.GetField(name); var groupName = GetDisplayGroup(field); var value = ((Enum)field.GetValue(obj: null)).ToString("d"); @@ -193,7 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal groupName, () => GetDisplayName(field, enumLocalizer)), value)); - namesAndValues.Add(name, value); + namesAndValues.Add(field.Name, value); } displayMetadata.EnumGroupedDisplayNamesAndValues = groupedDisplayNamesAndValues; diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index 1aed4aaaa0..4b551655f9 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -976,6 +976,36 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal KVPEnumGroupAndNameComparer.Instance); } + [Fact] + public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_ReflectsDisplayAttributeOrder() + { + // Arrange + var expectedKeyValuePairs = new List> + { + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayOrder.Three)), "2"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayOrder.Two)), "1"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayOrder.One)), "0"), + new KeyValuePair(new EnumGroupAndName(string.Empty, nameof(EnumWithDisplayOrder.Null)), "3"), + }; + + var provider = new DataAnnotationsMetadataProvider( + new TestOptionsManager(), + stringLocalizerFactory: null); + + var key = ModelMetadataIdentity.ForType(typeof(EnumWithDisplayOrder)); + var attributes = new object[0]; + var context = new DisplayMetadataProviderContext(key, new ModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Equal( + expectedKeyValuePairs, + context.DisplayMetadata.EnumGroupedDisplayNamesAndValues, + KVPEnumGroupAndNameComparer.Instance); + } + [Fact] public void CreateDisplayMetadata_EnumGroupedDisplayNamesAndValues_NameWithNoIStringLocalizerAndNoResourceType() { @@ -1318,6 +1348,20 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal MinusTwo = -2, } + private enum EnumWithDisplayOrder + { + [Display(Order = 3)] + One, + + [Display(Order = 2)] + Two, + + [Display(Order = 1)] + Three, + + Null, + } + private enum EnumWithDuplicates { Zero = 0,