Tyrel Van Niekerk
Tyrel Van Niekerk

Reputation: 1712

How to create an MVC helper method to populate a drop down from an enum

The MVC HtmlHelper.DropDownFor method can be downright frustrating to use. More often than not your selection does not remain or your control is not bound correctly. How would you write a custom HTML helper to populate a dropdown from an enum?

Upvotes: 1

Views: 329

Answers (1)

Tyrel Van Niekerk
Tyrel Van Niekerk

Reputation: 1712

I spent the last few hours trying to figure this one out, so might as well share what I found. After trying all sorts of permutations, creating a test app to try out various options and searching many articles, I got something that works for me.

First point to mention. The SelectList class takes four parameters (last three are optional). If you don't specify the selected value (last param), it will clear out any selected values you had set in your SelectListItem objects (assuming you created a list of those). This frustrated me for a while because I was setting one of the items Selected property to true, but once I create the SelectList object it was always set to false.

Here's the MVC source for SelectList for reference:

public class SelectList : MultiSelectList
{
    public SelectList(IEnumerable items)
        : this(items, null /* selectedValue */)
    {
    }

    public SelectList(IEnumerable items, object selectedValue)
        : this(items, null /* dataValuefield */, null /* dataTextField */, selectedValue)
    {
    }

    public SelectList(IEnumerable items, string dataValueField, string dataTextField)
        : this(items, dataValueField, dataTextField, null /* selectedValue */)
    {
    }

    public SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue)
        : base(items, dataValueField, dataTextField, ToEnumerable(selectedValue))
    {
        SelectedValue = selectedValue;
    }

    public object SelectedValue { get; private set; }

    private static IEnumerable ToEnumerable(object selectedValue)
    {
        return (selectedValue != null) ? new object[] { selectedValue } : null;
    }
}

Once I got past that little point I got my helper to correctly select the item from the list and correctly bind the value back. So here's the helper method I created (The initial method was from another post, but that did nor work correctly for me):

public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) where TProperty : struct, IConvertible
{
    if (!typeof(TProperty).IsEnum)
        throw new ArgumentException("TProperty must be an enumerated type");

    var selectedValue = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model.ToString();
    var selectList = new SelectList(from value in EnumHelper.GetValues<TProperty>()
                                    select new SelectListItem
                                                {
                                                    Text = value.ToDescriptionString(),
                                                    Value = value.ToString()
                                                }, "Value", "Text", selectedValue);

    return htmlHelper.DropDownListFor(expression, selectList, htmlAttributes);
}

(The EnumHelper.GetValues and ToDescriptionString are my helper methods to return a list of enum values of a specified type and to get the EnumMember value property for the description for the enum) I can post that code if anyone wants it.

The trick in that above code was telling SelectList what the value and text properties are as well as the selected value.

Upvotes: 1

Related Questions