Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 355
Old Geezer Avatar asked Apr 12 '12 01:04

Old Geezer


2 Answers

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)

like image 104
Leonardo Garcia Crespo Avatar answered Nov 04 '22 08:11

Leonardo Garcia Crespo


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.

like image 33
Brendan Vogt Avatar answered Nov 04 '22 08:11

Brendan Vogt