Mvc: Easy access to Enum Display(Name)

Created on 3 Mar 2016  路  9Comments  路  Source: aspnet/Mvc

Let's consider the following enum type:

``` c#
public enum PaymentMethodType
{
[Display(Name = "przelew")]
BankTransfer,
[Display(Name = "gotowka")]
Cash,
[Display(Name = "inny")]
Other
}

If I use it in my model I can easily display conveinient drop down list based on Display Name attribute using the following statement:

However If I want to display the selected value in my details view using:

@Html.DisplayNameFor(model => model.PaymentMethod)
```

I get BankTransfer, Cash or Other instead of Display Name attribute.

Is it possiible to get easy access to Display Name attribute in views?

3 - Done enhancement

Most helpful comment

Ok, I dealt with that by using suggested extension method. Thanks. However still I think that this should be simplified and embbeded into razor sytax as displaying Enum Display Name is quite common.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Mvc.Rendering;
using System.Reflection;

namespace MvcExtensionsLibrary
{
    public static class HtmlExtensions
    {
        public static HtmlString EnumDisplayNameFor(this Enum item)
        {
            var type = item.GetType();
            var member = type.GetMember(item.ToString());
            DisplayAttribute displayName = (DisplayAttribute)member[0].GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault();

            if (displayName != null)
            {
                return new HtmlString(displayName.Name);
            }

            return new HtmlString(item.ToString());
        }
    }
}

All 9 comments

You need to create your own helper for getting the DisplayName attribute of an enum value.

Take a look at this:
https://github.com/Maetiz/mvc-extensions/blob/master/HtmlExtensions.cs#L11-L26

Ok, I dealt with that by using suggested extension method. Thanks. However still I think that this should be simplified and embbeded into razor sytax as displaying Enum Display Name is quite common.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Mvc.Rendering;
using System.Reflection;

namespace MvcExtensionsLibrary
{
    public static class HtmlExtensions
    {
        public static HtmlString EnumDisplayNameFor(this Enum item)
        {
            var type = item.GetType();
            var member = type.GetMember(item.ToString());
            DisplayAttribute displayName = (DisplayAttribute)member[0].GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault();

            if (displayName != null)
            {
                return new HtmlString(displayName.Name);
            }

            return new HtmlString(item.ToString());
        }
    }
}

I would suggest that's not the right way to go about it.

Add your own meta data provider to the ModelDatadataDetailsProviders

services.AddMvc(options =>
{
    options.ModelMetadataDetailsProviders.Add(new MyMetaDataProvider());
});

Here is an example of a MetaDataProvider that uses the (excellent) Humanizer package.

public class HumanizerMetaDataProvider : IDisplayMetadataProvider
    {
        public void GetDisplayMetadata(DisplayMetadataProviderContext context)
        {
            if (IsTransformRequired(context.DisplayMetadata, context.PropertyAttributes))
            {
                context.DisplayMetadata.DisplayName = () => context.Key.Name.Humanize();
            }
        }

        private static bool IsTransformRequired(DisplayMetadata modelMetadata, IReadOnlyList<object> propertyAttributes)
        {
            if (propertyAttributes == null)
                return false;

            if (propertyAttributes.OfType<DisplayNameAttribute>().Any())
                return false;

            if (propertyAttributes.OfType<DisplayAttribute>().Any())
                return false;

            return true;
        }
    }

Basically, we're using Humanizer to set the DisplayName except in the cases where we have a Display/DisplayName attribute already set.

Thanks for the awesome and helpful example @ctolkien.

@Eilon Is this really a bug instead of an enhancement?

Done?

Don't know where to post my question so I'm doing it here hoping someone will see it and be able to give a small explanation of why things are like they are.

I was trying to localize enum display attributes and it never worked even if using the recommended approach of setting a SharedResource class for all data annotations localization. I was wondering why it didn't work and tried to figure it out by debugging from source. From what I found, it seems like the enumLocalizer is not using the SharedResource for localization and I'd like to know why it is as such.

Especially, I was looking here and saw that it was using a localizer built using the underlyingType. Wouldn't it be a good option to use the same SharedResource localizer?

Even if it's an enum, the display attribute is still a display attribute and it is confusing (to me at least) when I read that I can make all data annotations localization check in a shared location for localized strings by doing so:
.AddDataAnnotationsLocalization(options => { options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResources)); })

Thanks in advance

I agree with the previous comment, it seems like the DataAnnotationsMetadataProvider uses the IStringLocalizerFactory.Create() directly rather the DataAnnotationLocalizerProvider specified in options. This looks like a bug to me, or at least there should be some kind of provider you can specify for enums so that you can use a shared resource for them as well.

As for a workaround, I created a custom IStringLocalizerFactory by extending the existing ResourceManagerStringLocalizerFactory, not sure if this is the best way to go about it, but it seems to work for now:

    public class SharedResourceLocalizationFactory :  ResourceManagerStringLocalizerFactory
    {
        public SharedResourceLocalizationFactory(IOptions<LocalizationOptions> localizationOptions, ILoggerFactory loggerFactory) : base(localizationOptions, loggerFactory)
        {
            _loggerFactory = loggerFactory;
        }

        private readonly ILoggerFactory _loggerFactory;
        private readonly IResourceNamesCache _resourceNamesCache = new ResourceNamesCache();

        protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
        {

            var typeInfo = typeof(SharedResource).GetTypeInfo();

            baseName = GetResourcePrefix(typeInfo);

            assembly = typeInfo.Assembly;

            return new ResourceManagerStringLocalizer(
                new ResourceManager(baseName, assembly),
                assembly,
                baseName,
                _resourceNamesCache,
                _loggerFactory.CreateLogger<ResourceManagerStringLocalizer>());
        }
    }

In Startup.ConfigureServices()

          services.AddSingleton<IStringLocalizerFactory, SharedResourceLocalizationFactory>();

@LentoMan It has been fixed, see #7812

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kspearrin picture kspearrin  路  4Comments

hikalkan picture hikalkan  路  4Comments

Lutando picture Lutando  路  4Comments

nefcanto picture nefcanto  路  3Comments

Mr-Smileys picture Mr-Smileys  路  3Comments