In order for a drop-down list in an MVC View to be populated with data from the model, one method is to add the data to a model (or to the ViewBag) and populate it in the Controller so that the View can access it.
In this example, I'm using a ViewModel called PostViewModel
to contain a Post entity as well as a list of all possible Category values. The ViewModel will be populated in the controller, and the view will reference it. (This model uses Entity Framework.)
Model:
This is the Post model. Each Post has a CategoryId, which contains the integer value (foreign key) for the associated category. The property Public Virtual Category Category { get; set; }
will be populated with the actual Category object.
public class Post {
[Key]
public int Id { get; set; }
public String Title { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public String Content { get; set; }
}
public class PostViewModel {
// Contains the post object
public Post Post { get; set; }
// List of all possible categories
public IEnumerable<SelectListItem> Categories { get; set; }
}
Controller:
In addition to populating the model (Post) to be edited in the view, I am also adding a list of possible post categories to ViewBag.Categories
, which will be used to populate the select list. (See under "Possible Issues" for scenarios where you may have to bind the list in the POST version of the Edit function.)
// GET: Post/Edit/5
public ActionResult Edit(int Id) {
PostViewModel model = new PostViewModel();
// Get Post entity from database
Post post = context.Posts.Find(Id);
// Populate list of possible categories for post & add to model
List<Category> categories = context.Categories.ToList();
IEnumerable<SelectListItem> selcategories = from c in categories
select new SelectListItem {
Text = c.Name,
Value = c.Id.ToString()
};
// Add post & categories to ViewModel
model.Post = post;
model.Categories = selcategories;
// Populate the view's model
return View(model);
}
Edit View:
In the Edit View, I assign the model value "CategoryId"
so that any current value is selected and the new selection is sent back in the posted data, and I define where the select list values should be populated from: (IEnumerable<SelectListItem>)ViewBag.Categories
.
@Html.LabelFor(model => model.Post.CategoryId, htmlAttributes: new { @class = "control-label col-md-2" })
@Html.DropDownListFor(model => model.Post.CategoryId, Model.Categories, "Select Category", htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.Post.CategoryId, "", new { @class = "text-danger" })
The example above uses the control DropDownListFor
and references the list of categories stored in the ViewModel. If you prefer DropDownList
and the 'magic string' approach where you provide the name of the model property to be bound, the idea is the same:
@Html.DropDownList("CategoryId", Model.Categories, "Select Category", htmlAttributes: new { @class = "form-control" })
This technically won't work with the example above, as CategoryId
won't match up with any objects in our PostViewModel ViewModel. Changing it to Post.CategoryId
does work, although why would one prefer DropDownList over DropDownListFor?
Alternative Options:
The data can obviously be passed to the page in other ways, including adding a list of SelectListItem to the ViewBag, or simply adding it directly to the Posts model. (If you do that, use the [NotMapped]
annotation if you're doing code-first Entity Framework so that the framework knows to ignore it when creating the database.)
If you use ViewBag, you will also have to explicitly cast the item to IEnumerable<SelectListItem>
in the view otherwise you will get an error:
@Html.DropDownListFor(model => model.ParentId, (IEnumerable<SelectListItem>)ViewBag.Categories,
"Select Category", htmlAttributes: new { @class = "form-control" })
Controller Populating the ViewBag:
// Populate list of possible categories for post & add to ViewBag
List<Category> categories = context.Categories.ToList();
IEnumerable<SelectListItem> selcategories = from c in categories
select new SelectListItem {
Text = c.Name,
Value = c.Id.ToString()
};
ViewBag.Categories = selcategories;
Possible Issues:
If you encounter an input error when processing the user's input in the controller's [POST] Edit function, you may display an error message and keep the user on the same page by calling return View()
. When the view attempts to build the dropdown list, the property we bound in the controller's [GET] Edit function will be null, and you may see this error message:
Object reference not set to an instance of an object. System.Web.Mvc.WebViewPage
.Model.get returned null.
The message above will usually occur when you call the view without a model bound, and the View doesn't have anything to bind to.
The ViewData item that has the key 'ParentId' is of type 'System.Int32' but must be of type 'IEnumerable
'
The message above will usually occur when you call the view without a model bound, and the View doesn't have anything to bind to, and you're casting to IEnumerable
The ViewData item that has the key 'Post.CategoryId' is of type 'System.Int32' but must be of type 'IEnumerable
'.
The message above will usually occur when you call the view and pass it the same model that was passed back during the post. When the ViewModel in our example is posted back, it only includes the selected CategoryId, not a list of categories.
To solve the three errors above, use the copy of the model that was posted back to the [Post] Edit function (it will be the parameter, e.g. updatedPostViewModel
), add a list of categories to it just like you did in the [Get] Edit function (e.g. updatedPostViewModel.Categories
), and then call the view using the model: return View(updatedPostViewModel);
.