Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Easy access to Enum Display(Name) #4215

Closed
bartekkois opened this issue Mar 3, 2016 · 9 comments
Closed

Easy access to Enum Display(Name) #4215

bartekkois opened this issue Mar 3, 2016 · 9 comments

Comments

@bartekkois
Copy link

Let's consider the following enum type:

    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:

<select asp-for="PaymentMethod" asp-items="Html.GetEnumSelectList<PaymentMethodType>()" class="form-control"></select>

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?

@Maetiz
Copy link

Maetiz commented Mar 4, 2016

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

@bartekkois
Copy link
Author

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());
        }
    }
}

@ctolkien
Copy link

ctolkien commented Mar 6, 2016

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.

@Eilon Eilon added this to the Backlog milestone Mar 6, 2016
@tuespetre
Copy link
Contributor

Thanks for the awesome and helpful example @ctolkien.

@danroth27
Copy link
Member

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

@Eilon Eilon modified the milestones: 1.1.0, Backlog Aug 19, 2016
ryanbrandenburg added a commit that referenced this issue Sep 7, 2016
Fixes #5197 GetEnumSelectList uses IStringLocalizer
Fixes #4215 Html.DisplayFor now checks DisplayAttributes on enums
@Eilon
Copy link
Member

Eilon commented Sep 9, 2016

Done?

@lvmajor
Copy link

lvmajor commented May 4, 2018

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

@LentoMan
Copy link

LentoMan commented May 9, 2018

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>();

@lvmajor
Copy link

lvmajor commented May 31, 2018

@LentoMan It has been fixed, see #7812

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants