Drop-down Lists and ASP.NET MVC

Working with drop-down lists in ASP.NET MVC has some confusing aspects, so let’s look at an example.

Imagine the goal is to edit a song (not the music and lyrics of a song – just the boring data pieces). Each song is associated with an album, and each song has a title and track number. With this description, you can imagine an edit view using the following code:

<%= Html.DropDownList("AlbumId", Model.Albums)%>
  ...
<%= Html.TextBox("Title", Model.Title) %>
   ...
<%= Html.TextBox("TrackNumber", Model.TrackNumber) %>

The Html.DropDownList helper method likes to work with SelectListItem objects, so a view model you can pair with this view looks like the following:

public class EditSongViewModel
{        
    public string Title { get; set; }                
    public int TrackNumber { get; set; }
    public IEnumerable<SelectListItem> Albums { get; set; }
}

Some people don’t like to use SelectListItem types in their view models. Instead, they’ll convert to them in the view. I think it’s entirely reasonable to use SelectListItem in a view model, because the view model is supposed to make the view easier to write. Having the view model perfectly aligned with the needs of the view means you need to think less (and write less code) when creating the view.

Creating SelectListItems

There are a couple approaches you can take when creating a sequence of SelectListItem objects. I think the cleanest approach is to have an extension method that knows how to take a collection of objects in your software (like Album objects), and map them into SelectListItem objects.

public static IEnumerable<SelectListItem> ToSelectListItems(
              this IEnumerable<Album> albums, int selectedId)
{
    return 
        albums.OrderBy(album => album.Name)
              .Select(album => 
                  new SelectListItem
                  {
                    Selected = (album.ID == selectedId),
                    Text = album.Name,
                    Value = album.ID.ToString()
                   });
}

You can use the method like this:

model.Albums = _repository.FindAllAlbums().ToSelectItems(selectedId);

That code works, because Html.DropDownList will happily work with IEnumerable of SelectListItem.

The class you need to be careful with is the SelectList class. I’ve seen quite a few people make the mistake of wrapping their SelectListItem objects in a SelectList without setting the DataTextField and DataValueField properties. This does not work:

model.Albums = new SelectList(
                _repository.FindAllAlbums().ToSelectListItems(1)
                );

You’d think the SelectList class would know how to work with a collection of SelectListItem objects - but it doesn’t. The following doesn’t work either (the drop-down list will display “System.Web.Mvc.SelectListItem” for every entry):

model.Albums = 
    new SelectList(_repository.FindAllAlbums()
                              .ToSelectItems(selectedID));

The SelectList class is really designed to perform the conversion we did earlier (with the extension method), but it uses late binding reflection. The following would work, because we tell the SelectList where to find the text and value fields:

model.Albums = new SelectList(
                _repository.FindAllAlbums(), "ID", "Name"
                );

Reading the Selection

If you are using the same model to accept input from the edit view during a postback, you might think the default model binder will repopulate the Albums collection with all the album information and set the selected album. Unfortunately - the web doesn’t work this way and the Albums collection will be empty.

The only album related information the browser will post is the value of the selected item. If you want this value bound to a model, you’ll need to provide an AlbumId property (to match the name we gave the DropDownList in the view - “AlbumId”).

public class EditSongViewModel
{
    public int AlbumId { get; set; }
    public string Title { get; set; }                
    public int TrackNumber { get; set; }
    public IEnumerable<SelectListItem> Albums { get; set; }
}

Some people will create two separate view models in this case. One view model is designed to carry information to the view, and will have an Albums property (but no AlbumId property). The second view model is designed to accept user input during postback and will have an AlbumId propery (but no Albums property). This approach adds the overhead of an extra class, but the view models are perfectly aligned with their duties and no properties go unused. You’ll have to decide which approach is best for you.

Summary

  • Don’t use a SelectList without telling it the DataTextField and DataValueField properties to use.
  • Don’t expect to see a collection for a drop-down list repopulated on a postback.
  • Extension methods make it easy to create a sequence of SelectListItem objects in strongly-typed code.
  • Html.DropDownList doesn’t require a SelectList – it’s happy working with any sequence of SelectListItem objects.

Print | posted @ Monday, January 18, 2010 9:12 AM

Comments on this entry:

Gravatar # re: Drop-down Lists and ASP.NET MVC
by Chris Missal at 1/18/2010 10:05 PM

Good stuff, have any good suggestions on how to include a blank option at the top of the list? I haven't come up with a solution I like yet.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Scott Allen at 1/19/2010 9:40 AM

@Chris:

I have the extension method do a yield return with a blank SelectListItem, then return the rest of the items after a Select. I think it works out pretty well.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Geoff Weinhold at 1/19/2010 12:31 PM

Thanks for this Scott! This was great timing as I needed to implement something like this today.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Karl at 1/20/2010 4:04 PM

Where was this post last week when I needed it?!? Great writeup!

stackoverflow.com/...
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Matt at 1/22/2010 10:55 AM

Excellent post, and right on time for me as well. Thanks!
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Dan at 1/27/2010 8:34 AM

I'm pretty new to .NET MVC (and by no means a .NET veteran to begin with).

Would it be possible to change over your extension so that any DB Table with an "Id" and "Name" layout could use it?

Thinking:

public static IEnumerable<SelectListItem> ToSelectListItems(
this IEnumerable<T> items, int selectedId) { //not sure of the rest }

Thanks :)
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by jshah at 2/2/2010 12:30 PM

Hi Scott,

Nice article...

If you can create some sample code to download, would be great. I am new with MVC framework

Thanks
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Alec Whittington at 2/2/2010 3:35 PM

Why not just use the MVCContrib select helper and bind it to an entity instead having to create a list of SelectListItems?

Just seems like you re-invented a wheel that was solved MANY MANY months ago.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by http://ignou-student.blogspot.co at 2/2/2010 11:42 PM

Great article, i was use it on my current project.
  
Gravatar # asp.net using C#
by mohan at 2/2/2010 11:43 PM

if am selected a text in the dropdownlist1 means at the same time we should show few values in dropdownlist2 collections.i need sample program.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Scott Allen at 2/3/2010 7:44 AM

@Alec -

Because it only saves one line of code, and doesn't explain to people why the collection isn't recreated on post back.

  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by John at 2/3/2010 8:39 AM

I am one that would prefer that the model does not have the SelectItem collection there. In my opinion, the SelectItem class is only used for the view logic and nothing else. For example, if I wanted to have the same controller return the same data to the view, or as a restful web service which responds with xml, then the SelectItem class doesn't make sense. I believe the SelectItem is a class designed to represent the items in the drop down list, not my model.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by rajnish at 2/5/2010 1:32 AM

Great article, i was use it on my current project.

  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Wes at 2/5/2010 8:48 PM

I prefer populating these with aspect oriented filters instead of a controller action. This can be done with a bit of convention and reused across all actions that need it.

There is an example of this in my article on IFilterPriority with IJoinedFilter: geekswithblogs.net/...

I use this in conjunction with the MVCContrib fluent html syntax for creating a select or multi select list. This can be embedded in a template too, for reuse in the view.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by renantech at 2/7/2010 7:08 PM

asp.net 4 2010 improves many new features that beta 2 release are included. The 2010 version improve core asp.net as an outpout caching and session state storage.
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by E Rolnicki at 3/17/2010 11:11 AM

how about this....

public static class EnumerableHelpers
{
public static IList<SelectListItem> ToSelectItemList<T>(this IEnumerable<T> list, Func<T, string> textSelector, Func<T, string> valueSelector, IEnumerable<T> selected)
{
IList<SelectListItem> results = new List<SelectListItem>();
foreach (T item in list)
{
results.Add(new SelectListItem { Text = textSelector.Invoke(item), Value = valueSelector.Invoke(item), Selected = selected.Contains(item) });
}

return results;
}

public static IList<SelectListItem> ToSelectItemList<T>(this IEnumerable<T> list, Func<T, string> textSelector, Func<T, string> valueSelector)
{
return ToSelectItemList(list, textSelector, valueSelector, new List<T>());
}
}
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by E Rolnicki at 3/18/2010 10:47 AM

the code i posted above is to prevent the SelectListItems from bleeding into your model, as a select list is really a visual concern and not a part of the domain... if you have the ability in your views to quickly and easily convert a collection of items in your model into the UI-concerned select list then you maintain a seperation of concerns.

the useage would be

Model.ListOfFoo.ToSelectItemList(foo => foo.PropertyToDisplay, foo => foo.ValueToSubmit);
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by jim at 5/7/2010 7:41 AM

E Rolnicki - i like your take on this. I'm using a similar (tho less elegant method) on my current project. I'm looking at implementing (setting up test methods) using your approach as it's nice and clean - i love it.

scott - thanks for putting this article up in the 1st place - very timely.

cheers

jim

btw - here's the method that i have been using similar to your own:

public static List<SelectListItem> ToSelectList<T>(this IEnumerable<T> enumerable, Func<T, string> text, Func<T, string> value, string defaultOption)
{
var items = enumerable.Select(f => new SelectListItem() { Text = text(f), Value = value(f) }).ToList();
items.Insert(0, new SelectListItem() { Text = defaultOption, Value = "-1" });
return items;
}
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by antidepressants treatment at 8/4/2010 10:44 PM

Blogging for Haiti is one of the other way to help the victims via online. Other bloggers put a donation box for the Haiti's victimns
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by antidepressants treatment at 8/4/2010 10:45 PM

Blogging for Haiti is one of the other way to help the victims via online. Other bloggers put a donation box for the Haiti's victimns
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Won at 8/19/2010 6:46 PM

I am getting this error. What is wrong

"Extension methods must be defined in a non-generic static class"

public class ExperienceFormViewModel
{
private TigerDataContext db = new TigerDataContext();
ExperienceRepository expRepository = new ExperienceRepository();
// Properties
public EMP_EXPERIENCE empExperience { get; private set; }
public IEnumerable<SelectListItem> drpCountry { get; set; }

// Constructor
public ExperienceFormViewModel(EMP_EXPERIENCE eMP_EXPERIENCE)
{
empExperience = eMP_EXPERIENCE;
}

public static IEnumerable<SelectListItem> ToSelectListItems(this IEnumerable<MST_COUNTRY> mst_countrys, string selectedId)
{
return
mst_countrys.OrderBy(mst_country => mst_country.COUNTRY_NM)
.Select(mst_country =>
new SelectListItem
{
Selected = (mst_country.COUNTRY_ID == selectedId),
Text = mst_country.COUNTRY_NM,
Value = mst_country.COUNTRY_ID.ToString()
});
}
}
  
Gravatar # re: Drop-down Lists and ASP.NET MVC
by Goya at 9/16/2010 7:08 PM

Great post! thanks
  
Comments have been closed on this topic.
Scott Allen
Posts - 869
Comments - 4493
Stories - 14