Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Enum to SelectList extension method

I need to create a SelectList from any Enum in my project.

I have the code below which I create a select list from a specific enum, but I'd like to make an extension method for ANY enum. This example retrieves the value of the DescriptionAttribute on each Enum value

var list = new SelectList(
            Enum.GetValues(typeof(eChargeType))
            .Cast<eChargeType>()
            .Select(n => new
                {
                    id = (int)n, 
                    label = n.ToString()
                }), "id", "label", charge.type_id);

Referencing this post, how do I proceed?

public static void ToSelectList(this Enum e)
{
    // code here
}
like image 365
Jimbo Avatar asked Aug 09 '13 10:08

Jimbo


2 Answers

What I think you are struggling with, is the retrieval of the description. I'm sure once you have those that you can define your final method which gives your exact result.

First, if you define an extension method, it works on a value of the enum, not on the enum type itself. And I think, for easy of usage, you would like to call the method on the type (like a static method). Unfortunately, you cannot define those.

What you can do is the following. First define a method which retrieves the description of the enum value, if it has one:

public static string GetDescription(this Enum value) {
    string description = value.ToString();
    FieldInfo fieldInfo = value.GetType().GetField(description);
    DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

    if (attributes != null && attributes.Length > 0) {
        description = attributes[0].Description;
    }
    return description;
}

Next, define a method which takes all values of an enum, and use the previous method to look up the value which we want to show, and return that list. The generic argument can be inferred.

public static List<KeyValuePair<TEnum, string>> ToEnumDescriptionsList<TEnum>(this TEnum value) {
    return Enum
        .GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .Select(x => new KeyValuePair<TEnum, string>(x, ((Enum)((object)x)).GetDescription()))
        .ToList();
}

And finally, for ease of usage, a method to call it directly without value. But then the generic argument is not optional.

public static List<KeyValuePair<TEnum, string>> ToEnumDescriptionsList<TEnum>() {
    return ToEnumDescriptionsList<TEnum>(default(TEnum));
}

Now we can use it like this:

enum TestEnum {
    [Description("My first value")]
    Value1,
    Value2,
    [Description("Last one")]
    Value99
}

var items = default(TestEnum).ToEnumDescriptionsList();
// or: TestEnum.Value1.ToEnumDescriptionsList();
// Alternative: EnumExtensions.ToEnumDescriptionsList<TestEnum>()
foreach (var item in items) {
    Console.WriteLine("{0} - {1}", item.Key, item.Value);
}
Console.ReadLine();

Which outputs:

Value1 - My first value
Value2 - Value2
Value99 - Last one
like image 86
Maarten Avatar answered Nov 02 '22 09:11

Maarten


Late to the party, but since there is no accepted answer and it might help others:

As @Maarten mentioned, an extension method works on the value of an enum, not the enum type itelf, so as with Maarteen's soultion you can create a dummy or default value to call the extension method on, however, you may find, as I did, that it is simpler to just use a static helper method like so:

public static class EnumHelper
{
    public static SelectList GetSelectList<T>(string selectedValue, bool useNumeric = false)
    {
        Type enumType = GetBaseType(typeof(T));

        if (enumType.IsEnum)
        {
            var list = new List<SelectListItem>();

            // Add empty option
            list.Add(new SelectListItem { Value = string.Empty, Text = string.Empty });

            foreach (Enum e in Enum.GetValues(enumType))
            {
                list.Add(new SelectListItem { Value = useNumeric ? Convert.ToInt32(e).ToString() : e.ToString(), Text = e.Description() });
            }

            return new SelectList(list, "Value", "Text", selectedValue);
        }

        return null;
    }

    private static bool IsTypeNullable(Type type)
    {
        return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
    }

    private static Type GetBaseType(Type type)
    {
        return IsTypeNullable(type) ? type.GetGenericArguments()[0] : type;
    } 

You would create the select list like this:

 viewModel.ProvinceSelect =  EnumHelper.GetSelectList<Province>(model.Province);

or using the optional numeric values instead of strings:

viewModel.MonthSelect =  EnumHelper.GetSelectList<Month>(model.Month,true);

The basic idea for this I got from here, though I changed it to suit my needs. One thing I added was the ability to optionally use ints for the value. I also added an enum extension to get the description attribute which is based on this blog post:

public static class EnumExtensions
{
    public static string Description(this Enum en)
    {
        Type type = en.GetType();

        MemberInfo[] memInfo = type.GetMember(en.ToString());

        if (memInfo != null && memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attrs != null && attrs.Length > 0)
            {
                return ((DescriptionAttribute)attrs[0]).Description;
            }
        }

        return en.ToString();
    }       
} 
like image 21
Louise Eggleton Avatar answered Nov 02 '22 08:11

Louise Eggleton