DropDownListFor with ASP.NET MVC

Monday, March 11, 2013

Building <select> tags generates a few questions and is something I've written about before (see Dropdown Lists and ASP.NET MVC). Here's a quick example to examine the scenario from different perspectives with the DropDownListFor HTML helper.

Let's say you have a class to represent different flavors of ice cream.

public class IceCreamFlavor
{
    public int Id { get; set; }
    public string Name { get; set; }
}

With DropDownListFor, you typically want a view model that will contain at least 2 properties:

- one property to hold a collection of SelectListItems to build the drop down

- one property to hold the value selected by a user

public class ViewModel
{
    private readonly List<IceCreamFlavor> _flavors;
    
    [Display(Name = "Favorite Flavor")]
    public int SelectedFlavorId { get; set; }
    
    public IEnumerable<SelectListItem> FlavorItems
    {
        get { return new SelectList(_flavors, "Id", "Name");}
    }
}

Assuming the _flavors field is populated with real ice cream flavors from a database or elsewhere, then the following Razor code:

@Html.LabelFor(m=>m.SelectedFlavorId)
@Html.DropDownListFor(m => m.SelectedFlavorId, Model.FlavorItems)
@Html.ValidationMessageFor(m=>m.SelectedFlavorId)
<input type="submit" value="Submit" />                           

.. will give you this in a browser:

DropDownListFor

Do I Have To Use String Literals?

The constructor for a SelectList wants you to identify the data field and text field using strings ("Id" and "Name"). If you want strong typing, use LINQ to project ice cream flavors into SelectListItems.

public IEnumerable<SelectListItem> FlavorItems
{            
    get
    {
        var allFlavors = _flavors.Select(f => new SelectListItem
                                               {
                                                   Value = f.Id.ToString(),
                                                   Text = f.Name           
                                              });
        return allFlavors;
        
    }
}

What About Adding A "Select A Flavor" Entry?

Perhaps you want to prepend a "Select Something Here" item into the option list for the drop down.

public IEnumerable<SelectListItem> FlavorItems
{            
    get
    {
        var allFlavors = _flavors.Select(f => new SelectListItem
                                               {
                                                   Value = f.Id.ToString(),
                                                   Text = f.Name                
                                              });
        return DefaultFlavorItem.Concat(allFlavors);                
    }
}

public IEnumerable<SelectListItem> DefaultFlavorItem
{
    get { return Enumerable.Repeat(new SelectListItem
                                       {
                                           Value = "-1", Text = "Select a flavor"
                                       }, count: 1); }
}

Select a Flavor

How Did It Remember That?

Something you might notice if validation fails is that the drop down list seems to remember the last value selected without you doing any extra work.

In other words, if the user selects "Chocolate" and clicks Submit to post the form to the server, your controller might detect a validation error and re-render the form with validation messages. The drop down list will also re-build the <select> from scratch, but somehow "Choclate" is selected instead of "Select A Flavor". You'll see this behavior because DropDownListFor will peek in ModelState to see the last value for SelectedFlavorId, and build the drop down list with the last selected flavor marked as selected. It's the same magic Web Form programmers see with ViewState.

Hope that helps!


Comments
gravatar Peter Monday, March 11, 2013
I prefer this version (replaced less/greater than with brackets...) public static class ModelExtensions { public static IEnumerable[SelectListItem] ToListItems[T](this List[T] items, Func[T, string] pickValue, Func[T, string] pickText, Func[T, bool] pickSelected = null, string defaultValue = "", string defaultText = "-- select --") { yield return new SelectListItem { Value = defaultValue, Text = defaultText }; if (items != null) { foreach (var item in items) yield return new SelectListItem { Value = pickValue(item), Text = pickText(item), Selected = pickSelected != null && pickSelected(item) }; } } } and you can call it like this: public IEnumerable[SelectListItem] StateItems { get { return States.ToListItems(p =] p.StateId.ToString(), p =] p.StateName); } }
gravatar Doeke Tuesday, March 19, 2013
Is there an advantage to Enumerable.Repeat with 1, instead of yield return? Or is it just a matter of preference?
gravatar Scott Allen Tuesday, March 19, 2013
@Doeke: Come to think of it, I'd prefer yield return! :)
gravatar Charles Vallance Thursday, March 21, 2013
For the "Select A Flavor" entry I'd also suggest @Html.DropDownListFor(m => m.SelectedFlavorId, Model.FlavorItems, "Select a falvor").
gravatar Randima Friday, March 29, 2013
How do I get the selected Item from the dropdown list ?
gravatar Scott Allen Saturday, March 30, 2013
@Randima: In this sample that's what SelectedFlavorId will do -
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!