15 Oct 2014
It’s surprising how many subtle, but frustrating traps one can fall into when building sites with ASP.NET MVC. Creating forms for the web is one of them. It’s common to spend hours on something trivial, such as displaying a selected value in a DropDownList on postback, or getting that selected value in a controller. Quite often it happens when you just start learning ASP.NET MVC or upgrade from an older tech. And boy, is this frustrating as hell – instead of building an actual web app, you spend hours wrestling with the framework.
This article is one of the ‘DropDownList series’ articles that should help you in dealing with DropDownList / SelectList / SelectListItem related problems. Check out other DropDownList articles here:
I want to show you how to build a simple form with a drop down list that’s got “Please select” text as the first option and is based on the list of strings supplied by the controller. I’ll show you how to display that list on a form, how to get user’s selection in the controller, check that user has selected something and render the list back to the user with the value selected.
Sounds deceptively simple, right? Hold that thought for now and have a look at the Microsoft’s own
documentation for the @Html.DropDownListFor
function. It has 6 different overloads – which
one of those do you really need? And what are those mysterious <TModel, TProperty>
or
optionLabel
? Now throw into the mix various ways you can pass the data into the view: ViewBag,
ViewData or TempData? Or maybe Model? So you are naturally in the perfect spot to start making
mistakes.
We need to clear this up once and for all. In this example I will take you through building a simplistic “Sign Up” form that consists of two fields: Name and State. Both of these fields are required – this way we can test rendering of selected dropdown list value on the postback.
The following bits and pieces are needed:
Here’s the complete code of the solution used in this article. You can also browse the code online or clone the git repository. Now let’s dive right into the details.
public class SignUpModel
{
[Required]
[Display(Name="Name")]
public string Name { get; set; }
// This property will hold a state, selected by user
[Required]
[Display(Name="State")]
public string State { get; set; }
// This property will hold all available states for selection
public IEnumerable<SelectListItem> States { get; set; }
}
As you can see model is pretty simple and reflects the form’s fields except for one property –
States
. It works together with the State
property – while the the State
receives user’s
selection, States
hold a list of all possible selections.
Controller’s a bit more complex - it consists of 3 action methods and a couple of utility functions.
public class SignUpController : Controller
{
//
// 1. Action method for displaying 'Sign Up' page
//
public ActionResult SignUp()
{
// Let's get all states that we need for a DropDownList
var states = GetAllStates();
var model = new SignUpModel();
// Create a list of SelectListItems so these can be rendered on the page
model.States = GetSelectListItems(states);
return View(model);
}
//
// 2. Action method for handling user-entered data when 'Sign Up' button is pressed.
//
[HttpPost]
public ActionResult SignUp(SignUpModel model)
{
// Get all states again
var states = GetAllStates();
// Set these states on the model. We need to do this because
// only the selected value from the DropDownList is posted back, not the whole
// list of states.
model.States = GetSelectListItems(states);
// In case everything is fine - i.e. both "Name" and "State" are entered/selected,
// redirect user to the "Done" page, and pass the user object along via Session
if (ModelState.IsValid)
{
Session["SignUpModel"] = model;
return RedirectToAction("Done");
}
// Something is not right - so render the registration page again,
// keeping the data user has entered by supplying the model.
return View("SignUp", model);
}
//
// 3. Action method for displaying 'Done' page
//
public ActionResult Done()
{
// Get Sign Up information from the session
var model = Session["SignUpModel"] as SignUpModel;
// Display Done.html page that shows Name and selected state.
return View(model);
}
// Just return a list of states - in a real-world application this would call
// into data access layer to retrieve states from a database.
private IEnumerable<string> GetAllStates()
{
return new List<string>
{
"ACT",
"New South Wales",
"Northern Territories",
"Queensland",
"South Australia",
"Victoria",
"Western Australia",
};
}
// This is one of the most important parts in the whole example.
// This function takes a list of strings and returns a list of SelectListItem objects.
// These objects are going to be used later in the SignUp.html template to render the
// DropDownList.
private IEnumerable<SelectListItem> GetSelectListItems(IEnumerable<string> elements)
{
// Create an empty list to hold result of the operation
var selectList = new List<SelectListItem>();
// For each string in the 'elements' variable, create a new SelectListItem object
// that has both its Value and Text properties set to a particular value.
// This will result in MVC rendering each item as:
// <option value="State Name">State Name</option>
foreach (var element in elements)
{
selectList.Add(new SelectListItem
{
Value = element,
Text = element
});
}
return selectList;
}
}
The most important piece in the controller is the following code (and it’s repeated in both SignUp
methods):
model.States = GetSelectListItems(states);
As said above, this code runs twice – first when user loads the ‘Sign Up’ page in the browser and the form is displayed, and second time when user submits the form.
Why does it need to happen twice? The nature of browser forms is such that only selected values are posted back, and if you want to display the form after a postback (in case there’s a validation error in one of the form’s controls, for example), you need to populate all the supplementary data again, otherwise controls such as DropDownLists will be just rendered empty.
And View is the final destination where it all comes together with the help of
Html.DropDownListFor()
function.
@model Dropdowns.Models.SignUpModel
@{ ViewBag.Title = "Sign up"; }
<div class="row">
<div class="col-sm-4 col-sm-offset-4">
<h1>Sign up</h1>
<div class="panel panel-default">
<div class="panel-body">
@using (Html.BeginForm("SignUp", "SignUp", FormMethod.Post, new { role = "form" })) {
@* Name textbox *@
<div class="form-group">
@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
</div>
@* State selection dropdown *@
<div class="form-group">
@Html.LabelFor(m => m.State)
@Html.DropDownListFor(m => m.State, // 1. Store selected value in Model.State;
// when page is rendered after postback,
// take selected value from Model.State.
// 2. Take list of values from Model.States
Model.States,
// 3. Text for the first 'default' option
"- Please select a state -",
//4. A class name to assign to <select> tag
new { @class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Sign up</button>
}
</div>
</div>
</div>
</div>
Again, the most important point to note here is the call of DropDownListFor()
function. It does
all the heavy lifting when rendering a <select>
tag with a bunch of <option>
tags.
As you can tell from the comments, its first argument m => m.State
is used to store and retrieve
selected value, and its second argument Model.States
is used to supply all possible selections for
the dropdown. In the end you get something like this sent to user’s browser:
<select class="form-control" id="State" name="State">
<option value="">- Please select a state -</option>
<option value="ACT">ACT</option>
<option value="New South Wales">New South Wales</option>
<option value="Northern Territories">Northern Territories</option>
<option value="Queensland">Queensland</option>
<option value="South Australia">South Australia</option>
<option value="Victoria">Victoria</option>
<option value="Western Australia">Western Australia</option>
</select>
Oh, and remember that cryptic optionLabel
argument of DropDownListFor
function? It’s actually used to render
the ‘prompt’ option of the drop down list. I’d never be able to tell that from the name alone!
Also check out my next article on how to use DropDownListFor
with enums - you’ll learn how to
show enums in readable form in drop down lists and heaps more.
Download fully tested and 100% working Visual Studio solution with the source code used in this article for FREE – just enter your name and email in the form below, and I’ll send you the download link right away.
You will also get access to all the source code for all existing and new articles on the site, and access to my mailing list, which receives handy timesaving tips on .NET Core programming.