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

16,504

Solution 1

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)

Solution 2

An answer has been selected, but look at how I did it. Below is code how I normally do it when populating a drop down. It is very simplistic, I suggest you use it as a base to build your drop downs.

At the top of my view I specify my view model:

@model MyProject.ViewModels.MyViewModel

On my view I have a drop down list that displays all the banks that a user can select from:

<table>
     <tr>
          <td><b>Bank:</b></td>
          <td>
               @Html.DropDownListFor(
                    x => x.BankId,
                    new SelectList(Model.Banks, "Id", "Name", Model.BankId),
                    "-- Select --"
               )
               @Html.ValidationMessageFor(x => x.BankId)
          </td>
     </tr>
</table>

I always have a view model for a view, I never pass a domain object directly to the view. In this case my view model will contain a list of banks that will be populated from the database:

public class MyViewModel
{
     // Other properties

     public int BankId { get; set; }
     public IEnumerable<Bank> Banks { get; set; }
}

My bank domain model:

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

Then in my action method I create an instance of my view model and populate the banks list from the database. Once this is done then I return the view model to the view:

public ActionResult MyActionMethod()
{
     MyViewModel viewModel = new ViewModel
     {
          // Database call to get all the banks
          // GetAll returns a list of Bank objects
          Banks = bankService.GetAll()
     };

     return View(viewModel);
}

[HttpPost]
public ActionResult MyActionMethod(MyViewModel viewModel)
{
    // If you have selected an item then BankId would have a value in it
}

I hope this helps.

Share:
16,504
Old Geezer
Author by

Old Geezer

Don't shoot the messenger. An expert, or teacher, is a person who, after reading your question, knows what you know, what you don't know, what you are trying to know, and what else you need to know in order to achieve what you are trying to know.

Updated on June 23, 2022

Comments

  • Old Geezer
    Old Geezer almost 2 years

    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.