TOC

This article is currently in the process of being translated into Dutch (~10% done).

Tag Helpers:

The Select Tag Helper

De Select Tag Helper helpt je om makkelijk HTML SELECT elementen te kunnen maken gebaseerd op je data. Dit is een wat geavanceerdere Tag Helper, met meer functionaliteit dan bijvoorbeeld de Textarea Tag Helper. Toch ben je met minder toetsaanslagen klaar als je deze tag gebruikt in je Views! Ook zal je merken dat, omdat de Select Tag Helper iets meer voor je doet, de voorbeelden in dit artikel iets complexer zijn vergeleken met de eerdere artikelen.

Laten we eerst eens kijken naar wat een SELECT element doet. Hieronder volgt een HTML voorbeeld:

<select>
    <option value="1">The Godfather</option>
    <option value="2">Forrest Gump</option>
    <option value="3">Fight Club</option>
</select>

In de browser ziet het er zo uit:

Zoals je ziet creƫert een SELECT element een lijst met verschillende opties (standaard een "dropdown list"). De gebruiker kan dan een optie selecteren. De opties worden aangegeven met een OPTION element. Elk element kan naast een waarde (value) ook een tekstlabel hebben (in dit geval een film).

However, there's no need to hand code all those OPTION tags - we can have them automatically generated, based on a data source (usually a database) with the Select Tag Helper.

The for and items attributes

As with several of the other Tag Helpers, the Select Tag Helper has the asp-for attribute, which will allow you to bind its value to a specific property on the Model. In addition to that, an attribute called asp-items is available, which will allow you to provide a data source for the SELECT element.

The asp-for attributes needs a property on the Model to bind to and the asp-items needs a list of possible options. While both things CAN come from the same Model, it's often more practical to create a specific ViewModel for the purpose, which will combine the Model you are working with and the list of possible options. An example of this could be a FORM for editing a user, where you want to be able to edit basic stuff like name, e-mail etc., but also allow the user to select which country they live in. This list of countries would likely come from another source, e.g. a database or from a list of hand-picked countries. In this case, you could create a ViewModel like this:

public class EditUserViewModel
{
	public WebUser User { get; set; }
	
	public List<string> Countries { get; set; }
}

The WebUser class could look like this:

public class WebUser
{		
	public string FirstName { get; set; }

	public string LastName { get; set; }

	public string Country { get; set; }
}

Now when you want to show the Edit User View to the user, you would create the ViewModel and supply it with a WebUser object and a list of countries. Obviously, both things would normally come from somewhere else, e.g. a database, but to illustrate, I'm simply creating them on the fly. Here's how it looks in the Controller:

public IActionResult EditUser()
{
	EditUserViewModel viewModel = new EditUserViewModel();
	viewModel.User = new WebUser()
	{
		FirstName = "John",
		LastName = "Doe",
		Country = "USA"
	};
	viewModel.Countries = new List<string>()
	{
		"USA",
		"Great Britain",
		"Germany"
	};
	return View(viewModel);
}

With that in place, we're finally able to look at the View, where the Tag Helper is used:

@model HelloMVCWorld.ViewModels.EditUserViewModel

<form asp-action="UpdateUser" method="post">
    <input asp-for="User.FirstName" placeholder="First name" />
    <input asp-for="User.LastName" placeholder="Last name" />

    <select asp-items="@(new SelectList(Model.Countries))" asp-for="User.Country"></select>
    
    <input type="submit" value="Save" />
</form>

The important part here is of course the SELECT tag, where I use the Select Tag Helper to generate the dropdown list. Pay special attention to the asp-items attribute, where I create a new SelectList based on the list of countries. The reason is that the asp-items attribute expects the provided list to contain instances of the SelectItem class - this version of the SelectList constructor automatically creates these instances for us, based on the provided source. The generated HTML for this element will look like this:

<select id="User_Country" name="User.Country">
	<option selected="selected">USA</option>
	<option>Great Britain</option>
	<option>Germany</option>
</select>

Notice that USA has been pre-selected (with the selected-attribute) because the Model we generated in the Controller specified this as the current country - all of this is handled for us with just this one line of HTML/Razor, thanks to the Tag Helper!

Lists with key/values

One thing you may wonder about in the above example: No values are provided for the OPTION tags, meaning that the text of the selected item will also be used as the value when submitting the FORM. This is because we generate the option tags from a simple list of strings (in this case, name of countries), to keep the initial example a bit more simple. However, in most situations, you will likely be generating lists based on a combination of keys and values. For instance, the countries might come from a database, where they use a number as ID's and then a title/name field for the name of the country. Fortunately, this is also possible and very easy to accomplish.

Let's say you have a class called Country, consisting of a two properties: Id and Name. When creating the ViewModel, you could then load the countries from a database or another data source. In the View, simply use an alternative constructor for the SelectList, allowing you to specify which properties of the provided objects to use for the value and text, like this:

<select asp-items="@(new SelectList(Model.Countries, "Id", "Name"))" asp-for="User.CountryId"></select><br /><br />

Simple and flexible - just the way we like it!

Working with enums

Another very common usage scenario for the Select Tag Helper is in combination with enumerations (enums). An enumeration is a list of possible choices, defined in code, with each option corresponding to a number. This is very useful in a lot of situations and you will find lots of enumerations in the framework. An example of this is the DayOfWeek enumeration, used for the property with the same name on the DateTime struct. It's defined like this:

public enum DayOfWeek
{
	Sunday = 0,
	Monday = 1,
	Tuesday = 2,
	Wednesday = 3,
	Thursday = 4,
	Friday = 5,
	Saturday = 6
}

And you can easily define your own enumerations, e.g. like this:

public enum WebUserStatus
{
	Unknown,
	Active,
	Deleted
}

Unless you specifically assign a number, the first member of the enum will be 0, the next 1 and so on.

With the WebUserStatus enumeration defined, let's try using it for a SELECT element. Fortunately for us, there's a helper method on the HtmlHelper class that we can use for this purpose:

<select asp-items="@(Html.GetEnumSelectList<HelloMVCWorld.Models.WebUserStatus>())" asp-for="Status"></select>

The result will be a nice dropdown list with the options from the WebUserStatus enumeration!

The options of our enum are pretty self-explanatory, but because the language specification doesn't allow characters like whitespace and several special types of characters, you will often find or create enums with names that are not very easy on the human eye, like this one:

public enum WebUserValidationStatus
{
	Unknown,		
	NotVerifiedYet,
	VerifiedByMail,
	VerifiedByPhone
}

Sure, they still make sense, but they won't look right for non-programmers. Fortunately, you can easily add a human-readable version to all (or some) of them. If you use the Display data annotation, these values will be used instead of the enum label. Here's an example:

public enum WebUserValidationStatus
{
	Unknown,		
	[Display(Name = "Not verified yet")]
	NotVerifiedYet,
	[Display(Name = "Verified by e-mail")]
	VerifiedByMail,
	[Display(Name = "Verified by phone")]
	VerifiedByPhone
}

And here's how it will look:

Working with groups

The native HTML Select element support grouping of data, which basically just means that you can add elements which works like headers instead of actual elements, causing the following elements to appear as a part of this group. The Select Tag Helper supports this behavior as well, but it requires you to deliver a set of grouped items instead of just any kind of objects as we saw in previous examples. For this purpose, you would usually create a ViewModel to contain the grouped items:

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace HelloMVCWorld.ViewModels
{
	public class GroupedMoviesViewModel
	{
		public GroupedMoviesViewModel()
		{
			this.Movies = new List<SelectListItem>();
		}

		public List<SelectListItem> Movies { get; set; }
	}
}

Then, when creating the ViewModel, you should create instances of the SelectListItem class to hold the items, and create an instance of the SelectListGroup class for each group you want and assign it to SelectListItem's. It sounds more complicated than it is, as you can see:

public IActionResult SelectGroups()
{
	GroupedMoviesViewModel viewModel = new GroupedMoviesViewModel();

	SelectListGroup dramaMovies = new SelectListGroup() { Name = "Dramas" };
	viewModel.Movies.Add(new SelectListItem()
	{
		Group = dramaMovies,
		Text = "Forrest Gump"
	});
	viewModel.Movies.Add(new SelectListItem()
	{
		Group = dramaMovies,
		Text = "Fight Club"
	});

	SelectListGroup comedyMovies = new SelectListGroup() { Name = "Comedies" };
	viewModel.Movies.Add(new SelectListItem()
	{
		Group = comedyMovies,
		Text = "Anchorman: The Legend of Ron Burgundy"
	});
	viewModel.Movies.Add(new SelectListItem()
	{
		Group = comedyMovies,
		Text = "Step Brothers"
	});

	return View(viewModel);
}

The result will look like this:

As you can see from the resulting HTML, the groups are created with the OPTGROUP tag instead of the OPTION tag used for regular elements:

<select>
	<optgroup label="Dramas">
		<option>Forrest Gump</option>
		<option>Fight Club</option>
	</optgroup>
	<optgroup label="Comedies">
		<option>Anchorman: The Legend of Ron Burgundy</option>
		<option>Step Brothers</option>
	</optgroup>
</select>

Working with multiple selection lists

In some situations, you want the end-user to be able to select more than one item from the list at the same time. This is not possible in a dropdown list, which is the default rendering of the HTML SELECT element, but it is in fact supported. If you use the "multiple" attribute, your SELECT tag will be rendered as a listbox instead of a dropdown list, allowing you to select multiple items:

Fortunately for us, the Select Tag Helper supports this as well - in fact, it will automatically use a multiple-selection list if the property used in the asp-for attribute is of the type IEnumerable, e.g. a List. Here's an example of a ViewModel which will deliver a set of movies to the View, while accepting multiple selections in the FavoriteMovieIds property:

public class FavoriteMoviesViewModel
{
	public List<Movie> Movies { get; set; }

	public List<int> FavoriteMovieIds { get; set; }
}

Using it in the View is as simple as this:

<select asp-for="FavoriteMovieIds" asp-items="@(new SelectList(Model.Movies, "Id", "Title"))"></select>

This will result in the list you see in the screenshot above. The HTML looks much as it has before, with the addition of the multiple attribute:

<select id="FavoriteMovieIds" multiple="multiple" name="FavoriteMovieIds">
	<option value="1">The Godfather</option>
	<option value="2">Forrest Gump</option>
	<option value="3">Fight Club</option>
</select>

Summary

The HTML SELECT element is one of the more complex HTML elements, with lots of possibilities, which is why the Select Tag Helper is also a bit more complex to use, as seen in the examples above. However, that also means that it can save you even more time than usual!


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!