Binding to a SelectList in MVC
Asked Answered
A

2

11

Once again I'm confronted with a "This shouldn't be this ?*!# hard" situation.

Problem: I want to use a form in MVC for creation of an object. One of the elements of the object is a set of limited choices - a perfect candidate for a drop down list.

But if I use a SelectList in my model, and a drop down list in my View, and then try to post the Model back to my Create method, I get the error "Missing Method Exception:No Parameterless constructor for this object". Exploring the MVC source code, it appears that in order to bind to a model, the Binder has to be able to create it first, and it can't create a SelectList because there is no default constructor for it.

Here's the simplified code: For the model:

public class DemoCreateViewModel
{
    public SelectList Choice { get; set; }
}

For the controller:

//
// GET: /Demo/Create

public ActionResult Create()
{
    DemoCreateViewModel data = new DemoCreateViewModel();
    data.Choice = new SelectList(new string[] { "Choice1", "Choice2", "Choice3" });
    ViewData.Model = data;
    return View();
}

//
// POST: /Demo/Create

[HttpPost]
public ActionResult Create(DemoCreateViewModel form)
{
    try
    {
        // TODO: Add insert logic here

        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}

And for the View:

<fieldset>
    <legend>Fields</legend>
    <%= Html.LabelFor(model => model.Choice) %>
    <%= Html.DropDownListFor(model => model.Choice, Model.Choice) %>
    <p>
        <input type="submit" value="Create" />
    </p>
</fieldset>

Now, I know I can MAKE this work by dropping back 10 yards and punting: bypass model binding and drop back to the FormCollection and validate and bind all the fields myself, but there's got to be a simpler way. I mean, this is about as simple a requirement as it gets. Is there a way to make this work within the MVC ModelBinding architecture? If so, what is it? And if not, how come?

Edit: Well, I have egg on my face, but maybe this will help someone else. I did some more experimenting and found a simple solution that seems to work.

Provide a simple value (string or integer, depending on what your select list value type is), and name that as the model element that you bind to. Then provide a second element as the select list of choices, and name it something else. So my model became:

public class DemoCreateViewModel
{
    public string Choice { get; set; }
    public SelectList Choices { get; set; }
}

And then the DropDownListFor statement in the View becomes:

<%= Html.DropDownListFor(model => model.Choice, Model.Choices) %>

When I do this, the submit button correctly binds the choice made in the form to the string Choice, and submits the model back to the second Create method.

Amboceptor answered 26/5, 2010 at 22:1 Comment(2)
Thanks for posting your fix, I had a similar issue and your edit helpedAcquisition
Can't you just bind it to a IEnumerable or List of SelectListItems instead?Hussein
R
5

Here is one approach:

@Html.DropDownListFor(model => model.Choice, 
                      ViewBag.Choices as SelectList, 
                      "-- Select an option--",
                      new { @class = "editor-textbox" })

Notice that I use ViewBag to contain my SelectList. This way when you post back, the client doesn't send the entire select list up to the server as part of the model.

In your controller code, you just need to set the view bag:

ViewBag.Choices = new SelectList(....
Rabbinism answered 18/2, 2011 at 19:22 Comment(1)
Notice that I use ViewBag to contain my SelectList. This way when you post back, the client doesn't send the entire select list up to the server as part of the model. (1) It wouldn't anyway; and (2) if it did, using ViewBag wouldn't stop it from being posted, it would just stop it from binding to the model.Cowfish
F
0

Consider creating a different view model for your post action without the SelectList property:

            public class DemoCreateViewModelForUpdate
            {
                public string Choice { get; set; }
            }

Then you can always map from the DemoCreateViewModelPost instance to an DemoCreateViewModel instance if the model state is invalid and you want to re-show the view. I tend to prefer everything needed by the view to be in my display view model class, so using a separate update only view model let's me keep things slim and trim for the trip back to the server.

In your view, you'd do:

            @Html.DropDownListFor(m => m.Choice, Model.Choices)

as in the previous answer, so no unnecessary data would round trip.

Fayina answered 9/4, 2013 at 1:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.