Model Binding to a Dropdown
Sample #1: Binding to a List<string>
There’s a few ways to model bind a collection to a dropdown using the DropDown HTML helpers in ASP.NET MVC. Let’s first look at a simple scenario, where we have a dropdown and we want to bind to a List of states.
For this example, we’re going to construct a class called Globalization that has the list of states. (I use an this object to hold look-up data sets.)
public class Globalization { public List States { get { return new List() { "NY", "NJ", "IL", "TX", "FL" }; } }
Now let’s create our model:
public class Person { [Required(ErrorMessage="Please choose a state!")] public string State { get; set; } }
And follow up with our Controller:
public class HomeController : Controller { Globalization global = new Globalization(); public ActionResult Index() { return View(); } Globalization global = new Globalization(); public ActionResult Index() { return View(); } [HttpPost] public ActionResult Index(Person ba, FormCollection form) { // Let's check if the state form field exists in the Global State list... if (global.States.Exists(s => s == form["State"] ? true : false)) { // Clear the errors from the state property of the modelstate ModelState["State"].Errors.Clear(); } // Let's put it in the ViewBag so we can retain the user's form state when // the page is refreshed and the forms are repopulated with what the user // previously put in. ViewBag.selectedState = form["State"]; if (ModelState.IsValid) { // Run further server-side business logic from private methods. // DoFurtherStuff(); return (RedirectToAction("Success")); } return View(ba); } public ActionResult Success() { return View("Success"); } }
Now let’s do the View:
@model MvcApplication10.Models.Person @using MvcApplication10.Models; @{ ViewBag.Title = "BankAccount"; Html.EnableClientValidation(false); Html.EnableUnobtrusiveJavaScript(false); } @using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "mvcform" })) { BankAccount <div class="editor-field"> @{ SelectList slStates = new SelectList(new Globalization().States, ViewBag.selectedState); } @Html.DropDownList("State", slStates.OrderBy( x => x.Text ), "") @Html.ValidationMessageFor(model => model.State) </div> <p> </p> } <div> @Html.ActionLink("Back to List", "Index") </div>
So with this app, if we don’t pick a value from the dropdown, we get an error:
Now, let’s look at the HTML code generated:
FL IL NJ NY TX
Notice that the option elements don’t have explicit values, so they’ll be set to the text inside the option elements. So that’s equivalent to:
FL IL NJ NY TX
We leave the first one blank so the user has to select a value. So what if we want to bind the dropdown to a key/value pair collection like Dictionary? Like this for example:
FL IL
To do the above, refer to the next section…
Sample #2: Binding to a List<SelectListItem>
Now let’s visualize a different scenerio:
Where the HTML generated is:
Approval Code More Info Requested Rejected Deleted Approved
and we have to populate it with the data from the db (Status table):
Where the schema is:
CREATE TABLE [dbo].[Status]( [StatusID] [tinyint] IDENTITY(1,1) NOT NULL, [Name] [varchar](50) NOT NULL CONSTRAINT [PK_Status] PRIMARY KEY CLUSTERED ( [StatusID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
So we write the C# code a little different for this scenario. I’ve found that for this, it’s helpful to use the helper @Html.DropDownListFor(), which accepts an IEnumerable, which you’ll have to construct.
First let’s create our Model class:
public class Category { [Required(ErrorMessage = "Status is required.")] public Byte StatusID { get; set; } }
For convenience, let’s also create a ViewModel class that uses Category. We’re also using Dapper here, which makes db connection a snap. Check out the tutorial for more details.
/// <summary> /// We create his class so we can map data from Dapper. /// </summary> public class StateSelectListItem { public string Name { get; set; } public byte StatusID { get; set; } } /// <summary> /// This is the ViewModel for the Category Create Form /// </summary> public class CategoryViewModel { public Category CategoryModel { get; set; } public List ListStatusCodes { get { // We're putting an empty SelectListItem so that the first item in the drop down // is blank. List selectList = new List() { new SelectListItem() { Text = "", Value = "", Selected = false } }; using (SqlConnection conn = new SqlConnection("Data Source=NARUTO;Initial Catalog=GalaxyM33;Integrated Security=True")) { conn.Open(); // Let's map the results (using Dapper) to the list of StateSelectListItem IEnumerable listStatus = conn.Query("select StatusID, Name From [Status]"); // We're adding SelectList objects to the List... foreach (StateSelectListItem item in listStatus) { selectList.Add(new SelectListItem() { Text = item.Name, Value = item.StatusID.ToString() }); } conn.Close(); } return selectList; } } }
Here’s the controller:
public ActionResult Create() { CategoryViewModel viewModel = new CategoryViewModel(); return View(viewModel); } [HttpPost] public ActionResult Create(CategoryViewModel viewModel) { return View(viewModel); }
Now for the View:
@model ViewModels.CategoryViewModel @{ ViewBag.Title = "Create"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Create</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true) Category <div class="editor-field"> @Html.DropDownListFor(m => m.CategoryModel.StatusID, Model.ListStatusCodes) @Html.ValidationMessageFor(model => model.CategoryModel.StatusID) </div> <p> </p> } <div> @Html.ActionLink("Back to List", "Index") </div>
That should do it.
Categories