On Post, a drop down list SelectList.SelectedValue is null

Old Geezer picture Old Geezer · Apr 12, 2012 · Viewed 16.4k times · Source

My model is as follows:

public class testCreateModel
{
    public string s1 { get; set; }
    public SelectList DL { get; set; }

    public testCreateModel()
    {
        Dictionary<string, string> items = new Dictionary<string, string>();
        items.Add("1", "Item 1");
        items.Add("2", "Item 2");
        DL = new SelectList(items, "Key", "Value");
    }
}

My initiating actions is:

    public ActionResult testCreate()
    {
        testCreateModel model = new testCreateModel();
        return View(model);
    }

My Razor view (irrelevant parts deleted) is:

@model Tasks.Models.testCreateModel

@using (Html.BeginForm()) {
<fieldset>
    <legend>testCreateModel</legend>

    <div class="editor-label">
        @Html.LabelFor(model => model.s1)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.s1)
    </div>

    <div class="editor-label">
        Select an item:
    </div>
    <div class="editor-field">
        @Html.DropDownList("dropdownlist", (SelectList)Model.DL)
    </div>

    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>
}

The post back action is:

    public ActionResult testCreate(testCreateModel model, FormCollection collection)
    {
        if (ModelState.IsValid)
        {
            Console.WriteLine("SelectedValue: ",model.DL.SelectedValue);
            Console.WriteLine("FormCollection:", collection["dropdownlist"]);
            // update database here...
        }
        return View(model);
    }

On post back, model.DL.SelectedValue is null. (However, the selected item can be obtained from FormCollection, but that is besides the point). The DL object is still properly populated otherwise, Immediate Window output as follows:

model.DL
{System.Web.Mvc.SelectList}
    base {System.Web.Mvc.MultiSelectList}: {System.Web.Mvc.SelectList}
    SelectedValue: null
model.DL.Items
Count = 2
    [0]: {[1, Item 1]}
    [1]: {[2, Item 2]}
model.DL.SelectedValue
null

Q1: How can I make use of the SelectedValue property instead?

Now, if in the Razor view I change the name of the Html SELECT tag to DL (ie same as the property name in the model):

@Html.DropDownList("DL", (SelectList)Model.DL)

I get an exception:

No parameterless constructor defined for this object. 
Stack Trace: 
[MissingMethodException: No parameterless constructor defined for this object.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache) +98
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache) +241
System.Activator.CreateInstance(Type type, Boolean nonPublic) +69
System.Web.Mvc.DefaultModelBinder.CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) +199
System.Web.Mvc.DefaultModelBinder.BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult 
...

Q2: Why?

Thanks.

Answer

Leonardo Garcia Crespo picture Leonardo Garcia Crespo · Apr 12, 2012

MVC will return just the value of the selected option in your POST, so you need a property to contain the single value that returns.

As a good advice, try setting SelectLists through ViewBag, that helps keep your ViewModels clean from data that needs to populate the form.

So your example could be solved like this:

public class testCreateModel
{
    public string s1 { get; set; }
    public int SelectedValue { get; set; }
}

and in your View just do this:

@Html.DropDownList("SelectedValue", (SelectList)ViewBag.DL)

prior to populating ViewBag.DL in your GET action.

As for your Q2, the default ModelBinder requires that all types to bind to have a default constructor (so that the ModelBinder can create them)