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 theHtml.ListBox (or Html.ListBoxFor) helper. Options can be passed to the helper
in a number of forms: as anEnumerable<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>
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 })
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.
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 ofSelectListItem objects. The following code shows the
selected values being set as 1, 3 and 7 when using theMultiSelectList 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 MultiSelectListto 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 anenum:
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:
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:
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)
<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>
@Html.ListBox("CategoryId", (MultiSelectList)ViewBag.Categories, new { size = 8 })
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();
}
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();
}
public class CategoryViewModel
{
public int[] CategoryId { get; set; }
public MultiSelectList Categories { get; set; }
}
@Html.ListBoxFor(model =>
model.CategoryId, Model.Categories, new { size = 8 })
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);
}
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);
}
public enum Power
{
Coal = 1,
Gas,
Hydro,
Nuclear,
Solar,
Wave,
Wind
}
public class PowerViewModel
{
public Power Power { get; set; }
}
@model PowerViewModel
<form method="post">
@Html.EnumDropDownListFor(model
=> model.Power)
<div>
<input type="submit" />
</div>
</form>
var model = new PowerViewModel();
model.Power = Power.Hydro;
return View(model);
@Html.EnumDropDownListFor(model
=> model.Power, "Pick One")
public class PowerViewModel
{
public Power? Power { get; set; }
}