Reputation: 99
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!!
Upvotes: 4
Views: 4418
Reputation: 93474
While it's better to use ViewModels, per Stephen's answer, what you're doing wrong specifically here is this:
First, your DropDownListFor is wrong. Your ViewBag entry already contains a SelectList, so you don't need to create another one... you just have to cast the dynamic type.
@Html.DropDownListFor(m => m.Prefix.PersonPrefixID,
(SelectList)ViewBag.PersonPrefixes, "Select...")
The first parameter is the selected id from the list of objects in the PersonPrefixes... So when the model is posted to the controller, it will contain the ID (but not the other data, so you will have to make sure to query the database to populate it).
Second, you will need to change your Post Action to this:
Create([Bind(Include = "GuestID,FirstName,MiddleName,Surname,BirthDate")] Guest guest)
And then on your PersonPrefix class use the Bind syntax:
[Bind(Include = "PersonPrefixID")]
public class PersonPrefix
{
[Key]
public int PersonPrefixID { get; set; }
.....
}
Now, when you post, the rest of PersonPrefix will be empty, because this data is not present in the post, so you have to retrieve it from the database using the posted PersonPrefixID.
However, as I said, it's better to use ViewModels. Even so, some of the same things I said here still apply. Your DropDownListFor needs to be correct, and work with a selected id property. And, if you're using a ViewModel, there is no reason to use the Bind attributes, since you're only binding the exact items you need.
Upvotes: 1
Reputation:
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.
Upvotes: 1