Html DropDownList set selected value in view

There are many times when well need to have HTML Select elements in our forms. ASP.NET gives us lots of options with theDropDownList and theDropDownListFor HTML helpers. This can make things easy for us, but the variety of options can make the process confusing for an MVC beginner. Lets demystify this process a bit with some simple, practical examples of how to bind DropDownList in MVC, both in ASP.NET and ASP.NET Core.

Lets pretend that we have a web application where were trying to track employees and their worksites. We can represent these two entities with some simple models, like this:

public class Employee { public int ID { get; set; } public string Name { get; set; }

\[ForeignKey["Worksite"]\] public int WorksiteID { get; set; } public Worksite Worksite { get; set; } }

public class Worksite { public int ID { get; set; } public string Name { get; set; } }

When we create or update an Employee, well want the WorksiteID to be set from a select element. The basic form will look like the following:

For simplicitys sake, Ive removed a lot of things that really should be in your final markup, like the AntiForgeryToken and form validation messages. However, this is the bare minimum that we need: a form that will POST to the Create action in the Employees controller, which will then insert a new employee into the database.

Lets start with how we would accomplish this in .NET Standard.

DropDownList Examples in .NET Standard

When Im learning something new in .NET, I like to take a look at how the .NET devs would have me do things. That doesnt mean Ill always take their advice, but, hey, they built the thing, so I might as well examine their examples.

To that end, Im going to scaffold a new controller with views, using the Entity framework. If youre new to MVC, do this:

Right click on the Controllers folder in your project, then select Add, followed by Controller

From there, choose MVC 5 Controller with views, using Entity Framework from the dialog:

Finally, select the appropriate Model class [Employee in our case] and Data context class. You can leave the view options and controller name alone, as the defaults are what we want anyway:

Click on Add. Visual Studio will create our Controller class in the Controllers folder and CRUD views in a nested Employees folder under Views. Awesome.

DropDownList Razor Syntax

Lets check out what Visual Studio automatically generated for the Create view, so we can see an example of DropDownList in action.

@model DropdownlistStandard.Models.Employee

@{ ViewBag.Title = Create; }

Create

@using [Html.BeginForm[]] { @Html.AntiForgeryToken[]

Employee

@Html.ValidationSummary[true, "", new { @class = "text-danger" }]
@Html.LabelFor[model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }]

@Html.EditorFor[model => model.Name, new { htmlAttributes = new { @class = "form-control" } }] @Html.ValidationMessageFor[model => model.Name, "", new { @class = "text-danger" }]

@Html.LabelFor[model => model.WorksiteID, "WorksiteID", htmlAttributes: new { @class = "control-label col-md-2" }]

@Html.DropDownList["WorksiteID", null, htmlAttributes: new { @class = "form-control" }] @Html.ValidationMessageFor[model => model.WorksiteID, "", new { @class = "text-danger" }]

}

@Html.ActionLink["Back to List", "Index"]

@section Scripts { @Scripts.Render[~/bundles/jqueryval] }

Okay, then! Theres a lot going on here, but what were most concerned with is this line, the one that generates our select element:

@Html.DropDownList["WorksiteID", null, htmlAttributes: new { @class = "form-control" }]

And if we hover over DropDownList we get this signature:

DropDownList[string name, IEnumerableselectList, Object htmlAttributes]

The first parameter will set the name and id attributes of the HTML select element. If the second parameter is null, then Razor will also look for an IEnumerablein the ViewData with a key matching that name. In other words, if the name parameter is WorksiteID, Razor is expecting an IEnumerableinViewData["WorksiteID"]or ViewBag.WorksiteID [which are two different ways of expressing the same thing]. Well discuss how to set this when we get to the controller.

The second parameter becomes useful when we want to bind our DropDownList with data from a Model or ViewModel. More on that later.

The final parameter accepts a generic object that we can use to set HTML attributes. In this example, were using the parameter to set the class attribute to a Bootstrap CSS class. You could add properties to the object to set other attributes as well.

So thats one way of specifying a DropDownList using Razor syntax. You should also know that, if we dont care about the Bootstrap class, we can omit the second and third parameters altogether, like this:

@Html.DropDownList["WorksiteID"]

This will give us the exact same select element, but without the class attribute.

ASP.NET Controller Code for DropDownList

Now lets switch our focus to the back end, and take a look at our Controller file, EmployeesController.cs in the Controllers folder. Here are the relevant methods for creating a new Employee:

public class EmployeesController : Controller { private EmployeeContext db = new EmployeeContext[];

// GET: Employees/Create public ActionResult Create[] { ViewBag.WorksiteID = new SelectList[db.Worksites, ID, Name]; return View[]; }

// POST: Employees/Create // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see //go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create[[Bind[Include = ID,Name,WorksiteID]] Employee employee] { if [ModelState.IsValid] { db.Employees.Add[employee]; db.SaveChanges[]; return RedirectToAction[Index]; }

ViewBag.WorksiteID = new SelectList[db.Worksites, "ID", "Name", employee.WorksiteID]; return View[employee]; }

protected override void Dispose[bool disposing] { if [disposing] { db.Dispose[]; } base.Dispose[disposing]; } }

Ive taken out the rest of the controller methods like Edit and Delete so we can just focus on the Create process. In the GET action, theres only one line thats interesting, so it should jump right out at you:

ViewBag.WorksiteID = new SelectList[db.Worksites, "ID", "Name"];

We need an IEnumerable in our Razor view, and creating a new instance of the SelectList class does exactly this. The first parameter uses Entity Framework to get every row in the Worksites table. We use the second parameter to specify which database field to use for the SelectListItem Value, and the third parameter to specify which field to use for the SelectListItem Text. This will then render as thevalue andtext attributes on the select element.

The only counter-intuitive part here is the naming of the ViewBag key. If we were striving for clarity, we would probably name this something like ViewBag.Worksites, and not ViewBag.WorksiteID, since the variable type is anIEnumerable and not an int. However, we need the select element to have a name attribute of WorksiteID so that our model will bind properly when we POST.

Since the DropDownList name parameter is used to render the name of the HTML element and to specify the ViewData key were looking for, were stuck with this weird naming convention for now.

Heres something else to notice that can be a sticking point. Take a look at the POST method. If the Model is valid, well add the Employee to the database, then redirect to the Index action. However, if the Model isnt valid, we need to rebind the DropDownList by settingViewBag.WorksiteID again. This is because the ViewData values dont persist across requests. If you dont grab the Worksite values again, your users will get an error instead of being able to see their form.

DropDownListFor and a Strongly-Typed ViewModel

The approach so far will certainly work, and you shouldnt feel too bad for using it, but it does have some shortcomings. Despite the way it looks, the ViewBag isnt strongly typed. Go ahead, try getting Intellisense to autocomplete your ViewBag keys in the Razor view. It wont work. ViewBag.WorksiteID is the exact same thing as ViewData["WorksiteID"].

WorksiteID is just a magic string. Thus, you open yourself to typing mistakes, and if you ever have to refactor, youll need to hunt down every instance instead of having Visual Studio assist you.

Having a strongly-typed ViewModel makes your code more robust. Lets take a look at how we would do that. Heres our ViewModel class:

public class EmployeeViewModel { public int ID { get; set; } public string Name { get; set; } public int WorksiteID { get; set; }

public IEnumerableWorksites { get; set; } }

In the controller, well want to populate the Worksites property with the Worksites from the database.

// GET: Employees/Create public ActionResult Create[] { var viewModel = new EmployeeViewModel[]; viewModel.Worksites = db.Worksites;

return View[viewModel]; }

Finally, in the view, well want to change the model type fromEmployee to EmployeeViewModel at the top of the file. Then, well change the line with @Html.DropDownList to this:

@Html.DropDownListFor[model => model.WorksiteID, new SelectList[Model.Worksites, "ID", "Name"]]

The first parameter is a lambda expression that identifies which property in the model were going to bind to. This will set the name and id attributes to WorksiteID. The second parameter is an IEnumerable that comes from our ViewModel.

Setting the Selected Value of the Dropdown

When we started looking at our controller methods, we began with Create methods. In the Edit methods, youll need a way to set the selected value, so the users previous choice can show up in the form as expected.

When you create the IEnumerable, just add the selected value as an extra parameter, like this:

public ActionResult Edit[int? id] { if [id == null] { return new HttpStatusCodeResult[HttpStatusCode.BadRequest]; } Employee employee = db.Employees.Find[id]; if [employee == null] { return HttpNotFound[]; } ViewBag.WorksiteID = new SelectList[db.Worksites, ID, Name, employee.WorksiteID]; return View[employee]; }

If youre using a ViewModel, you would use the same exact same syntax to set the selected value when you create the SelectList, like this:

@Html.DropDownListFor[model => model.WorksiteID, new SelectList[Model.Worksites, "ID", "Name", Model.WorksiteID]]

Lets say we wanted to add an extra option to the select, maybe a blank item or a helpful message, like Please choose a worksite With a ViewModel and DropDownListFor, we would just add the text of the message as a third parameter:

@Html.DropDownListFor[model => model.WorksiteID, new SelectList[Model.Worksites, "ID", "Name"], "Please select a worksite..."]

If youre using DropDownList instead, the parameter for the optionLabel comes third:

@Html.DropDownList["WorksiteID", null, "Please select a worksite...", htmlAttributes: new { @class = "form-control" }]

Bind DropDownList in ASP.NET Core

Now we switch gears to .NET Core. Here, Im using Razor Pages, so theres a bit of a difference in the controller syntax. However, as far as binding the dropdown data, there isnt much difference at all, except that ViewBag is no longer available. Heres the PageModel for creating a new Employee:

public class CreateModel : PageModel { private readonly Dropdownlist.DAL.ProjectContext _context;

public CreateModel[Dropdownlist.DAL.ProjectContext context] { _context = context; }

public IActionResult OnGet[] { ViewData[WorksiteID] = new SelectList[_context.Worksites, ID, Name]; return Page[]; }

[BindProperty] public Employee Employee { get; set; }

public async TaskOnPostAsync[] { if [!ModelState.IsValid] { ViewData\["WorksiteID"\] = new SelectList[\_context.Worksites, "ID", "Name"];

return Page[]; } \_context.Employees.Add[Employee]; await \_context.SaveChangesAsync[]; return RedirectToPage["./Index"]; } }

Like before, were grabbing the Worksites from the database, creating an IEnumerable, and then stuffing that into the ViewData.

You could, alternatively, create a ViewModel and assign the IEnumerableinto a property, in the same way that we did with .NET Standard earlier.

One glitch that I ran across when automatically scaffolding was that, in the POST method, line 23 was missing. This is the line that re-binds the dropdown if theres a model error, and we need to reload the page. Without that line, my select element no longer had any options. Bear in mind that .NET Core is newer than .NET Standard, and there are still issues to be solved. Ive actually found a number of other [small] glitches in the scaffolding, like inconsistent alignment, so keep an eye out.

Using Tag Helpers in the .NET Core View

The view in .NET Core gets a little more interesting. Tag Helpers are an alternative to HTML Helpers, and keep the markup a bit closer to HTML. Heres the select element in the view, using Tag Helpers:

The asp-for tag sets the name and id attributes to WorksiteID. The asp-items tag specifies the IEnumerablewith our worksite data.

Personally, I find that this makes the code a bit easier to read in the context of the HTML document. You can see Microsofts own documentation about the difference between Tag Helpers and HTML Helpers, but overall, I like the Tag Helpers better.

If you dont agree, however, you can still use theDropDownList and DropDownListFor HTML helpers, since this is still a Razor document.

One last curiosity: even though ViewBagis no longer supported in the controller, you can still use ViewBag in the Razor view. In fact, even though you can useViewData for the asp-items tag_,_ the ViewBag syntax is cleaner. With ViewData , youd have to do some uglier casting, like this:

asp-items="[SelectList]@ViewData["CourseID"]"

How to Bind DropDownList in MVC: A Summary

Heres a quick cheat sheet of the strategies used in this guide:

Using DropDownList and ViewData in ASP.NET MVC

In the Controller:

ViewBag.WorksiteID = new SelectList[db.Worksites, "ID", "Name"];

In the Razor view:

@Html.DropDownList["WorksiteID", null, htmlAttributes: new { @class = "form-control" }]

Using DropDownListFor and a ViewModel in ASP.NET MVC

In the Controller, assuming we have a ViewModel with an IEnumerable of Worksite objects:

varviewModel=newEmployeeViewModel[]; viewModel.Worksites=db.Worksites;

In the Razor view:

@Html.DropDownListFor[model => model.WorksiteID, new SelectList[Model.Worksites, "ID", "Name"]]

Using Tag Helpers in ASP.NET Core

In the controller:

ViewData["WorksiteID"] = new SelectList[db.Worksites, "ID", "Name"];

In the Razor view:

Alternatively, you could make a ViewModel and use the Tag Helper in the same way, just assign the appropriate ViewModel property to asp-items.

Hopefully youve found this guide helpful and binding your DropDownLists will be a bit easier in the future.

Video liên quan

Chủ Đề