ASP.NET MVC Html.DropDownList SelectedValue

This is how I fixed this problem:

I had the following:

Controller:

ViewData["DealerTypes"] = Helper.SetSelectedValue(listOfValues, selectedValue) ;

View

<%=Html.DropDownList("DealerTypes", ViewData["DealerTypes"] as SelectList)%>

Changed by the following:

View

<%=Html.DropDownList("DealerTypesDD", ViewData["DealerTypes"] as SelectList)%>

It appears that the DropDown must not have the same name has the ViewData name :S weird but it worked.


Try this:

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

And then:

var list = new[] {   
    new Person { Id = 1, Name = "Name1" }, 
    new Person { Id = 2, Name = "Name2" }, 
    new Person { Id = 3, Name = "Name3" } 
};

var selectList = new SelectList(list, "Id", "Name", 2);
ViewData["People"] = selectList;

Html.DropDownList("PeopleClass", (SelectList)ViewData["People"])

With MVC RC2, I get:

<select id="PeopleClass" name="PeopleClass">
    <option value="1">Name1</option>
    <option selected="selected" value="2">Name2</option>
    <option value="3">Name3</option>
</select>

You can still name the DropDown as "UserId" and still have model binding working correctly for you.

The only requirement for this to work is that the ViewData key that contains the SelectList does not have the same name as the Model property that you want to bind. In your specific case this would be:

// in my controller
ViewData["Users"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["Users"])%>

This will produce a select element that is named UserId, which has the same name as the UserId property in your model and therefore the model binder will set it with the value selected in the html's select element generated by the Html.DropDownList helper.

I'm not sure why that particular Html.DropDownList constructor won't select the value specified in the SelectList when you put the select list in the ViewData with a key equal to the property name. I suspect it has something to do with how the DropDownList helper is used in other scenarios, where the convention is that you do have a SelectList in the ViewData with the same name as the property in your model. This will work correctly:

// in my controller
ViewData["UserId"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId")%>

The code in the previous MVC 3 post does not work but it is a good start. I will fix it. I have tested this code and it works in MVC 3 Razor C# This code uses the ViewModel pattern to populate a property that returns a List<SelectListItem>.

The Model class

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

The ViewModel class

using System.Web.Mvc;

public class ProductListviewModel
{
    public List<SelectListItem> Products { get; set; }
}

The Controller Method

public ViewResult List()
{
    var productList = new List<SelectListItem>();

    foreach (Product p in Products)
    {
        productList.Add(new SelectListItem
        {
            Value = p.ProductId.ToString(),
            Text = "Product: " + p.Name + " " + p.Price.ToString(),
            // To set the selected item use the following code 
            // Note: you should not set every item to selected
            Selected = true
        });
    }

    ProductListViewModel productListVM = new ProductListViewModeld();

    productListVM.Products = productList;

    return View(productListVM);
}

The view

@model MvcApp.ViewModels.ProductListViewModel

@using (Html.BeginForm())
{
    @Html.DropDownList("Products", Model.Products)
}

The HTML output will be something like

<select id="Products" name="Products">
    <option value="3">Product: Widget 10.00</option>
    <option value="4">Product: Gadget 5.95</option>
</select>

depending on how you format the output. I hope this helps. The code does work.


If we don't think this is a bug the team should fix, at lease MSDN should improve the document. The confusing really comes from the poor document of this. In MSDN, it explains the parameters name as,

Type: System.String
The name of the form field to return.

This just means the final html it generates will use that parameter as the name of the select input. But, it actually means more than that.

I guess the designer assumes that user will use a view model to display the dropdownlist, also will use post back to the same view model. But in a lot cases, we don't really follow that assumption.

Use the example above,

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

If we follow the assumption,we should define a view model for this dropdownlist related view

public class PersonsSelectViewModel{
    public string SelectedPersonId,
    public List<SelectListItem> Persons;
}

Because when post back, only the selected value will post back, so it assume it should post back to the model's property SelectedPersonId, which means Html.DropDownList's first parameter name should be 'SelectedPersonId'. So, the designer thinks that when display the model view in the view, the model's property SelectedPersonId should hold the default value of that dropdown list. Even thought your List<SelectListItem> Persons already set the Selected flag to indicate which one is selected/default, the tml.DropDownList will actually ignore that and rebuild it's own IEnumerable<SelectListItem> and set the default/selected item based on the name.

Here is the code from asp.net mvc

private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
            string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
            IDictionary<string, object> htmlAttributes)
{
    ...

    bool usedViewData = false;

    // If we got a null selectList, try to use ViewData to get the list of items.
    if (selectList == null)
    {
        selectList = htmlHelper.GetSelectData(name);
        usedViewData = true;
    }

    object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));

    // If we haven't already used ViewData to get the entire list of items then we need to
    // use the ViewData-supplied value before using the parameter-supplied value.
    if (defaultValue == null && !String.IsNullOrEmpty(name))
    {
        if (!usedViewData)
        {
            defaultValue = htmlHelper.ViewData.Eval(name);
        }
        else if (metadata != null)
        {
            defaultValue = metadata.Model;
        }
    }

    if (defaultValue != null)
    {
        selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
    }

    ...

    return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}

So, the code actually went further, it not only try to look up the name in the model, but also in the viewdata, as soon as it finds one, it will rebuild the selectList and ignore your original Selected.

The problem is, in a lot of cases, we don't really use it that way. we just want to throw in a selectList with one/multiple item(s) Selected set true.

Of course the solution is simple, use a name that not in the model nor in the viewdata. When it can not find a match, it will use the original selectList and the original Selected will take affect.

But i still think mvc should improve it by add one more condition

if ((defaultValue != null) && (!selectList.Any(i=>i.Selected)))
{
    selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}

Because, if the original selectList has already had one Selected, why would you ignore that?

Just my thoughts.