Search code examples
asp.net-mvcentity-frameworkviewbagdropdownlistfor

MVC DropDownListFor with ViewBag not passing value to controller


I'm having a real hard time trying to set up a drop-down list to a related model/table in my create view. My Guest model has a reference to the PersonPrefix model in the following way:

Guest model:

public virtual PersonPrefix Prefix { get; set; }

PersonPrefix model:

public class PersonPrefix
{

    [Key]
    public int PersonPrefixID { get; set; }
    [StringLength(6)]
    public string Abbreviation { get; set; }
    [StringLength(255)]
    public string Name { get; set; }
    public virtual ICollection<Guest> Guests { get; set; }

}

I have done the following to be able to get the data from the database and show it in a dropdown:

Controller:

public ActionResult Create()
{
    ViewBag.PersonPrefixes = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
    return View();
} 

and I've added the prefix object to the post

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "GuestID,FirstName,MiddleName,Surname,BirthDate,SelectedPrefix")] Guest guest)
{
    if (ModelState.IsValid)
    {
        db.Guests.Add(guest);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(guest);
}

This is the relevant code in the view so far but it is not passing the value to the controller:

  <div class="form-group">
        @Html.LabelFor(model => model.Prefix, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10" >
            @*@Html.DropDownListFor(m => m.Prefix,  new SelectList(ViewBag.PersonPrefixes, "Value", "Text", 1))*@
            @Html.DropDownListFor(m => m.Prefix,
            new SelectList(ViewBag.PersonPrefixes, "Value", "Text", 1))

        </div>
    </div>

Thanks for your help!!


Solution

  • You cannot bind a <select> element to a complex object. All html form controls post back a single value (or in the case of <select multiple> an array of value types). If you selected an option with a value of (say) 5, then the DefaultModelBinder would try to set guest.Prefix = 5; which of course fails.

    You need to bind to a value type (e.g. int, string etc). In your case you cannot even bind to the PersonPrefixID of PersonPrefix because validation would fail on the other properties of PersonPrefix. As always when editing, you should use a view model containing only those properties you need to edit.

    public class GuestVM
    {
      [Display(Name = "Prefix")]
      [Required(ErrorMessage = "Please select a prefix")]
      public int SelectedPrefix { get; set; }
      .... // other properties of Guest
      public SelectList PrefixList { get; set; }
    }
    

    Controller

    public ActionResult Create()
    {
      GuestVM model = new GuestVM();
      model.PrefixList = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
      .... // set other properties as required
      return View(model); // always return an instance of the model
    }
    

    View

    @Html.LabelFor(m => m.SelectedPrefix)
    @Html.DropDownListFor(m => m.SelectedPrefix, Model.PrefixList, "--please select--")
    @Html.ValidationMessageFor(m => m.SelectedPrefix)
    

    Then in the POST method, initialize a new instance of Guest data model and map its properties from the posted view model and finally save the data model.

    public ActionResult Create(GuestVM model)
    {
      if (!ModelSTate.IsValid)
      {
        model.PrefixList = new SelectList(db.PersonPrefixes, "PersonPrefixID", "Abbreviation");
        return View(model);
      }
      // Initialize a new instance of the data model and set its properties
      Guest guest = new Guest()
      {
        FirstName = model.FirstName,
        MiddleName = model.MiddleName,
        .... // other properties
        Prefix = db.PersonPrefixes.Find(model.SelectedPrefix)
      };
      db.Guests.Add(guest);
      db.SaveChanges();
      return RedirectToAction("Index");
    }
    

    Side note: You do not need to create another SelectList in the view (its already a SelectList) and the last parameter where you tried to set the selected value to 1 is ignored (its the value of the property your binding to which determines which option is selected) so if you want to pre-select an option with value="1", then set the value of SelectedPrefix = 1; in the controller before you pass the model to the view.