There are a lot of examples out there on how to pass a SelectList or MultiSelectList object to an Html.DropDownList or Html.ListBox helper. However, I couldn’t find anything that was explaining the problem I was having.
In my app we have a Show object that represents a podcast. Each show may have multiple Guest objects, which are accessed by the Show.Guests member.
In the Admin controller I have the following Action method:
In the Edit View I display the fields for the Show and a ListBox for the Guests. You can select multiple Guests for a show. Ideally, when you edit a Show the Guests that are already associated with the Show are pre-selected. Makes sense, right?
Here is the code called in the View:
If you follow this you’ll notice that I am adding to the ViewData a MultiSelectList object, which contains an IEnumerable collection of all the Guests, that “Id” is the value field, “Name” is the text field, and an IEnumerable collection of the guest.Id values that are already associated with the show. However, this never caused the ListBox to select the values upon rendering the page.
I used the debugger and, sure enough, my MultiSelectList object was successfully populated with the properly selected items. What was the deal? The breakdown must be happening with the Html.ListBox helper. I spent a couple of hours scouring this myself and scouring the Internet. I went home determined to re-write the Html.ListBox helper myself and just be done with it.
However, the next morning I decided to download the MVC source. Here’s a great and simple article from Steve Sanderson on how to attach it to your project so that you can step through it with the Visual Studio debugger.
One nice thing about Html.ListBox and Html.DropDownList helpers is that you don’t have to provide a collection of SelectItems explicitly. If you have an item in the Model or ViewData that has the same name as the ListBox or DropDownList then it will find it automatically. If you do explicitly provide the SelectItem collection you can opt to not provide the default values explicitly. Again, as long as you have a object (or member of an object) that has the same name as the ListBox or DropDownList it will find it and use it.
Here’s the rub though. If you explicitly define the SelectList collection and the default values (as I did in my code) the Html helper looks to the Model and ViewData first before your default items. This normally never causes a problem, until you name your ListBox or DropDownBox the same name as one of your objects or members.
Notice that I called my DropDownList “Guests”? Notice how the Model is a Show object, which has a Guests member variable? It turns out that the Html helper was grabbing this collection, rather than my values. Technically these are the guests I want to default to, however, the Html helper was getting a collection of Guest objects not a collection of Guest Id values. When it compared each guest Id in the full list with the actual Guest objects in the default list it never found a match. Thus, nothing was ever selected.
What’s the fix? Simply change the name of your ListBox or DropDownList. When I changed the name value to “GuestListBox” all worked well, because, as you would expect there is no object or member in the Model or ViewData called “GuestListBox”. Once it couldn’t find one it dropped through that code and used the default values I provided.
To find exactly where MVC is doing this check out the SelectInternal member in the SelectExtensions.cs file. This is the internal method that both ListBox and DropDownList eventually call. You can see commented code that states:
In my opinion this is a bug. It shouldn’t even check the ViewData or Model if you have already explicitely provided the default values.
So, I added my vote to the bug that was already submitted for this on CodePlex. I haven’t check to see if this has been resolved in MVC v2.