Wednesday, January 9, 2013

DropDownListFor with Enums

When creating a dropdown list using the DropDownListFor method off of the html helper, you usually have to manually include all of the list items:

@{
    List<selectlistitem> items = new List<selectlistitem>
    {
        new SelectListItem {Text = "Red", Value = "Red", Selected = Model.FavoriteColor == "Red"},
        new SelectListItem {Text = "Green", Value = "Green", Selected = Model.FavoriteColor == "Green"},
        new SelectListItem {Text = "Blue", Value = "Blue", Selected = Model.FavoriteColor == "Blue"}
    };
}

@Html.DropDownListFor(model => model.FavoriteColor, items)

In this case, FavoriteColor is a string property that has some options on a list for convenience. But what if it was an Enum?
public enum Colors
{
    Red,
    Green,
    Blue
}

We can use some fancy maneuvering and LINQ to create a dropdown list in one command:
@Html.DropDownListFor(model => model.FavoriteColor, Enum.GetValues(typeof(Colors)).Cast<colors>().Select(color => new SelectListItem { Text = Colors.ToString(), Value = Colors.ToString(), Selected = model.FavoriteColor == color }))

This LINQ query uses the static method GetValues to get a list of all entries in an enum. They get casted to their appropriate type and then a new SelectListItem is selected out of them. This works great, but it's a bit too much work. Let's try an HtmlHelper extension method:
public static class HtmlHelperExtensions
{
    public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {

    }
}

By creating a new static class and adding it to the same namespace as the view (or adding a "using" statement to the top of the view), we will now see a new overload for the DropDownListFor method which takes only one parameter, the property expression.
@Html.DropDownListFor(model => model.FavoriteColor)

In populating the method, it has to do 5 things:
  1. It must establish the name of the property
  2. It must establish the type of the property and ensure it is an enum
  3. It must get a list of all possible options for the enum
  4. It must create a list of SelectListItems
  5. Finally, it must return a dropdown list
Here is the complete method in action:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
    var propertyName = ExpressionHelper.GetExpressionText(expression);
    var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

    var propertyInfo = metadata.ContainerType.GetProperty(propertyName);

    if (!propertyInfo.PropertyType.IsEnum)
        throw new InvalidParameterException("Must use with a property of an Enum type.");

    var options = Enum.GetValues(propertyInfo.PropertyType).Cast<object>().Select(o => o.ToString()).ToList();
    var descriptions = propertyInfo.PropertyType.GetEnumDescriptions();

    var selectedOption = metadata.Model.ToString();
    var selectList = options.Select(option => new SelectListItem {Text = descriptions[option], Value = option, Selected = option == selectedOption});

    return htmlHelper.DropDownListFor(expression, selectList);
}
For more information, check out our website.

1 comment:

  1. This blog entry provides an excellent and clear explanation of how to create an HTML Extension to the DropDownListFor helper. C# should provide this functionality to convert an enum to a DropDownListFor. So this is a good solution to add the functionality to your project.

    ReplyDelete