2

There are some helpful extension methods for using displaying enums in dropdown lists. For example here and here.

But there is one problem that I encounter, which is that these helpers do not work if the enum is decorated with the Description attribute. The first example works perfectly with the Description attribute, but it doesn't set the selected value. The second example sets the selected value, but it doesn't use the description attribute. So I need to combine both methods into a working helper that does both correctly. I've a lot of variations to get it working but, no success so far. I've tried several ways to create a selectlist, but somehow it ignores the Selected property. In all my tests, the Selected property was set to true on one item, but this property is just ignored. So any ideas are most welcome!

This is the latest code that I've tried:

public static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
    {
        List<SelectListItem> items = new List<SelectListItem>();
        foreach (var item in Enum.GetValues(enumType))
        {
            FieldInfo fi = enumType.GetField(item.ToString());
            var attribute = fi.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault();
            var title = attribute == null ? item.ToString() : ((DescriptionAttribute)attribute).Description;
            var listItem = new SelectListItem
            {
                Value = ((int)item).ToString(),
                Text = title,
                Selected = selectedItem == item.ToString()
            };

            items.Add(listItem);
        }
        return items;

    }

public static HtmlString EnumDropDownList2For<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> modelExpression)
    {
        var typeOfProperty = modelExpression.ReturnType;
        if (!typeOfProperty.IsEnum)
            throw new ArgumentException(string.Format("Type {0} is not an enum", typeOfProperty));

        var value = htmlHelper.ViewData.Model == null
            ? default(TProperty)
            : modelExpression.Compile()(htmlHelper.ViewData.Model);

        return htmlHelper.DropDownListFor(modelExpression, ToSelectList(modelExpression.ReturnType, value.ToString()));
    }
Community
  • 1
  • 1
Hanno
  • 1,017
  • 11
  • 18

2 Answers2

3

I had the same issue with enums which actually had custom value set

public enum Occupation
{
    [Description("Lorry driver")] LorryDriver = 10,
    [Description("The big boss")] Director = 11,
    [Description("Assistant manager")] AssistantManager = 12
}

What I found is that when I use DropDownListFor(), it doesn't use the selected item from the SelectListItem collection to set the selected option. Instead it selects an option with a value which equals to the property I'm trying to bind to (m => m.Occupation) and for this it uses the enum's .ToString() not the enum's actual integer value. So what I ended up with is setting the SelectListItem's Value like so:

var listItem = new SelectListItem
{
    Value = item.ToString(), // use item.ToString() instead
    Text = title,
    Selected = selectedItem == item.ToString() // <- no need for this
};

The helper method:

public static class SelectListItemsForHelper
{
    public static IEnumerable<SelectListItem> SelectListItemsFor<T>(T selected) where T : struct
    {
        Type t = typeof(T);
        if (t.IsEnum)
        {
            return Enum.GetValues(t).Cast<Enum>().Select(e => new SelectListItem { Value = e.ToString(), Text = e.GetDescription() });
        }
        return null;
    }

    public static string GetDescription<TEnum>(this TEnum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());

        if (fi != null)
        {
            var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attributes.Length > 0)
                return attributes[0].Description;
        }

        return value.ToString();
    }
}

In the view:

@Html.DropDownListFor(m => m.Occupation, SelectListItemsForHelper.SelectListItemsFor(Model.Occupation), String.Empty)
Zeno
  • 63
  • 1
  • 4
  • Thank you for your answer. Even though you're code did not actually work for me, it did help me to solve the problem! I'm still not sure what finally did the trick.. I modified my ToSelectList function according to your comments and combined it with the GetDescription method. – Hanno Feb 27 '12 at 13:50
1

To summarize the solution that does work (at least for me):

I use the following set of helper methods

public static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
    {
        List<SelectListItem> items = new List<SelectListItem>();
        foreach (var item in Enum.GetValues(enumType))
        {
            var title = item.GetDescription();
            var listItem = new SelectListItem
            {
                Value = item.ToString(),
                Text = title,
                Selected = selectedItem == item.ToString()
            };

            items.Add(listItem);
        }
        return items;

    }

public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
    {
        string inputName = GetInputName(expression);
        var value = htmlHelper.ViewData.Model == null
            ? default(TProperty)
            : expression.Compile()(htmlHelper.ViewData.Model);

        return htmlHelper.DropDownList(inputName, ToSelectList(typeof(TProperty), value.ToString()));
    }

public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
    {
        if (expression.Body.NodeType == ExpressionType.Call)
        {
            MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body;
            string name = GetInputName(methodCallExpression);
            return name.Substring(expression.Parameters[0].Name.Length + 1);

        }
        return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
    }

    private static string GetInputName(MethodCallExpression expression)
    {
        MethodCallExpression methodCallExpression = expression.Object as MethodCallExpression;
        if (methodCallExpression != null)
        {
            return GetInputName(methodCallExpression);
        }
        return expression.Object.ToString();
    }

Usage:

@Html.EnumDropDownListFor(m => m.MyEnumType)

This works for enums with or without a description attribute, and sets the correct selected value.

Hanno
  • 1,017
  • 11
  • 18
  • You're missing GetDescription() (easily copied from Zeno above), but `htmlHelper.DropDownList` is undefined in my MVC3 project and VS has no ideas about where I might find it (I'm already `using System.Web.Mvc`) so I'm not sure what the problem there is. What's the namespace for that method? – Carl Bussema Mar 07 '12 at 22:07