ASP.NET MVC DropDownLists - Multiple Selection and Enum Support

My original article about Drop Down Lists in ASP.NET MVC has proven to be among the most popular on this site, amassing over a third of a million views since it was published 5 years ago. This article builds on the original by looking at using drop down lists for multiple selections, and the helper that was introduced in MVC 5.1 to support enumerations as a source of select options.

Multiple Selections

Just to recap, an MVC Html.DropDownList helper is rendered to the browser as an HTML <select> element. They are mainly employed to provide the user with a means to select one of a number of options. Multiple selection select elements are useful when you want to provide your user with a means to choose more then one option but you do not want to use checkboxes, for example. You can enable multiple selection in one of two ways: you can add the multiple attribute to the Html.DropDownList helper via its htmlAttributes parameter, or you can use the Html.ListBox (or Html.ListBoxFor) helper. Options can be passed to the helper in a number of forms: as an Enumerable<SelectListItem>; as a SelectList object or as a MultiSelectList object:

using (var db = new NorthwindContext())
{
    var categories = db.Categories.Select(c => new { 
        CategoryID = c.CategoryID, 
        CategoryName = c.CategoryName 
    }).ToList();
    ViewBag.Categories = new MultiSelectList(categories, "CategoryID", "CategoryName");
    return View();
}

 

@Html.DropDownList("CategoryId", (MultiSelectList)ViewBag.Categories, new { multiple = "multiple"})
@Html.ListBox("CategoryId", (MultiSelectList)ViewBag.Categories)

Both helpers will result in identical HTML:

<select id="CategoryId" multiple="multiple" name="CategoryId">
    <option value="1">Beverages</option>
    <option value="2">Condiments</option>
    <option value="3">Confections</option>
    <option value="4">Dairy Products</option>
    <option value="5">Grains/Cereals</option>
    <option value="6">Meat/Poultry</option>
    <option value="7">Produce</option>
    <option value="8">Seafood</option>
</select>

MVC DropDownLists 2 

There aren't a lot of styling options for multiple select lists, but you can affect the number of list item initially visible by default (4) by setting a value for the size attribute:

@Html.ListBox("CategoryId", (MultiSelectList)ViewBag.Categories, new { size = 8 })

MVC DropDownLists 2 

When a multiple select control is posted to the server, the collection of selected values are processed by ASP.NET into a comma-separated list. The MVC model binding system quite happily sees this collection as an array. If the selected values can be parsed into ints, you can use an int array to represent the posted values. The following image shows a select list generated from the code above being posted back to the server. The selected items have values of 1, 3 and 7, which are captured in the categoryId parameter. The parameter name matches the name given to the DropDownList.

MVC DropDownLists 2

 

Setting Selected Values

You often need to set the selected values of a multiple select control, such as in editing scenarios or if you want to provide the user with a default selection. You can achieve this in a number of ways depending on preference and the helper that you use to generate the multiple select control. The SelectList class doesn't support setting multiple items as selected, so it is not an option, but you can use either the MultiSelectList or a collection of SelectListItem objects. The following code shows the selected values being set as 1, 3 and 7 when using the MultiSelectList approach:

using (var db = new NorthwindContext())
{
    var categories = db.Categories.Select(c => new { 
        CategoryID = c.CategoryID, 
        CategoryName = c.CategoryName 
    }).ToList();
    ViewBag.Categories = new MultiSelectList(categories, "CategoryID", "CategoryName", new[]{1,3,7});
    return View();
}

Here's the alternative approach that passes a List<SelectListItem> to the view:

using (var db = new NorthwindContext())
{
    var selected = new[] { 1, 3, 7 };
    var categories = db.Categories
        .AsEnumerable()
        .Select(c => new SelectListItem { 
            Value = c.CategoryID.ToString(), 
            Text = c.CategoryName,
            Selected = selected.Contains(c.CategoryID)
        }).ToList();
    ViewBag.Categories = categories;
    return View();
}

The MultiSelectList approach seems to win in terms of user-friendliness.

Html.ListBoxFor and view models

The Html.ListBoxFor helper is the strongly typed version designed for use with view models. Here's a simple view model to illustrate how to use the ListBoxFor helper:

public class CategoryViewModel
{
    public int[] CategoryId { get; set; }
    public MultiSelectList Categories { get; set; }
}

The view model class has two properties - an array of ints to represent the selected values and a MultiSelectList to hold the options. Here is a snippet showing the view code:

@Html.ListBoxFor(model => model.CategoryId, Model.Categories, new { size = 8 })

And here is the code to populate the options:

using (var db = new NorthwindContext())
{
    var model = new CategoryViewModel();
    var categories = db.Categories.Select(c => new
    {
        CategoryID = c.CategoryID,
        CategoryName = c.CategoryName
    }).ToList();
    model.Categories = new MultiSelectList(categories, "CategoryID", "CategoryName");
    model.CategoryId = new[] { 1, 3, 7 };
    return View(model);
}

In this example, I have explicitly set the view model's CategoryId property with the array that holds the selected values. The ListBoxFor helper will mark each of these options as selected in the rendered HTML. You can just as easily pass the integer array in to the MultiSelectList method as shown earlier:

using (var db = new NorthwindContext())
{
    var model = new CategoryViewModel();
    var categories = db.Categories.Select(c => new
    {
        CategoryID = c.CategoryID,
        CategoryName = c.CategoryName
    }).ToList();
    model.Categories = new MultiSelectList(categories, "CategoryID", "CategoryName", new[] { 1, 3, 7 });
    return View(model);
}

The resulting HTML will be the same.

Enumerations as a source for options

ASP.NET MVC 5.1 saw the introduction of a new helper: Html.EnumDropDownListFor. As a strongly typed helper, this works with enum properties in view models. It takes the members of the enum and produces a select list with them, assigning the value to the option's value attribute and the enumerator to the text. Here's an example of an enum:

public enum Power
{
    Coal = 1,
    Gas,
    Hydro,
    Nuclear,
    Solar,
    Wave,
    Wind
}

This might be used to represent a selection of power generation options. Here's how it might appear as part of a view model:

public class PowerViewModel
{
    public Power Power { get; set; }
}

And this is how the helper is used to render the options in the view:

@model PowerViewModel

<form method="post">
    @Html.EnumDropDownListFor(model => model.Power)
    <div>
        <input type="submit" />
    </div>
</form>

By default, the option with a value of 0 is selected. In this case, the enum values were intialised at 1 so the helper added an option with a value of 0 and empty text and made it selected:

MVC5 DropDownLists

If you want another option to be selected, you can set that in the view model:

var model = new PowerViewModel();
model.Power = Power.Hydro;
return View(model);

If you prefer a default option that says "Pick One" or similar, you can pass that value into the second argument for the helper method:

@Html.EnumDropDownListFor(model => model.Power, "Pick One")

If your enum starts at 0, this will result in an extra option with an empty value appearing in the list. However, the first enum (in my example, Coal) will still be selected by default. If you start your enum from 1, the "Pick One" option will be generated with a value of 0, which will be selected by default and will also form a valid selection if you make the property required via the DataAnnotation [Required] attribute. So what if you don't want the "Pick One" option being assigned a value and/or don't want the option with a value of 0 to be selected by default? You can solve both these problems by making the enum nullable in your view model:

public class PowerViewModel
{
    public Power? Power { get; set; }
}

And of course, you can still force the user to pick a valid value by adding the [Required] attribute to the property in the view model.

Finally, to retrieve the selected value, you ensure that the enum is included in the parameter list belonging to the action method that the form posts to, either on its own or as part of a view model. Here's the selected value being caught by the debugger when the form is posted back to the controller:

MVC5 DropDownLists 2

 

Summary

This article completes my look at dropdownlists in ASP.NET MVC. It looks at how to manage multiple selections and explores the new helper introduced in MVC 5.1 to support the use of enumerations in dropdownlists. If you would like to review the more common uses of the DropDownList helpers, please take some time to read the first article.