Tag Helpers in forms in ASP.NET Core

This document demonstrates working with Forms and the HTML elements commonly used on a Form. The HTML Form element provides the primary mechanism web apps use to post back data to the server. Most of this document describes Tag Helpers and how they can help you productively create robust HTML forms. We recommend you read Introduction to Tag Helpers before you read this document.

In many cases, HTML Helpers provide an alternative approach to a specific Tag Helper, but it's important to recognize that Tag Helpers don't replace HTML Helpers and there's not a Tag Helper for each HTML Helper. When an HTML Helper alternative exists, it's mentioned.

The Form Tag Helper

The Form Tag Helper above generates the following HTML:

The MVC runtime generates the action attribute value from the Form Tag Helper attributes asp-controller and asp-action . The Form Tag Helper also generates a hidden Request Verification Token to prevent cross-site request forgery (when used with the [ValidateAntiForgeryToken] attribute in the HTTP Post action method). Protecting a pure HTML Form from cross-site request forgery is difficult, the Form Tag Helper provides this service for you.

Using a named route

The asp-route Tag Helper attribute can also generate markup for the HTML action attribute. An app with a route named register could use the following markup for the registration page:

Many of the views in the Views/Account folder (generated when you create a new web app with Individual User Accounts) contain the asp-route-returnurl attribute:

With the built in templates, returnUrl is only populated automatically when you try to access an authorized resource but are not authenticated or authorized. When you attempt an unauthorized access, the security middleware redirects you to the login page with the returnUrl set.

The Form Action Tag Helper

The Form Action Tag Helper generates the formaction attribute on the generated or tag. The formaction attribute controls where a form submits its data. It binds to elements of type image and elements. The Form Action Tag Helper enables the usage of several AnchorTagHelper asp- attributes to control what formaction link is generated for the corresponding element.

Supported AnchorTagHelper attributes to control the value of formaction :

Attribute Description
asp-controller The name of the controller.
asp-action The name of the action method.
asp-area The name of the area.
asp-page The name of the Razor page.
asp-page-handler The name of the Razor page handler.
asp-route The name of the route.
asp-route- A single URL route value. For example, asp-route-id="1234" .
asp-all-route-data All route values.
asp-fragment The URL fragment.

Submit to controller example

The following markup submits the form to the Index action of HomeController when the input or button are selected:

  

The previous markup generates following HTML:

 

Submit to page example

The following markup submits the form to the About Razor Page:

  

The previous markup generates following HTML:

 

Submit to route example

Consider the /Home/Test endpoint:

public class HomeController : Controller < [Route("/Home/Test", Name = "Custom")] public string Test() < return "This is the test page"; >> 

The following markup submits the form to the /Home/Test endpoint.

  

The previous markup generates following HTML:

 

The Input Tag Helper

The Input Tag Helper:

An error occurred during the compilation of a resource required to process this request. Please review the following specific error details and modify your source code appropriately. Type expected 'RegisterViewModel' does not contain a definition for 'Email' and no extension method 'Email' accepting a first argument of type 'RegisterViewModel' could be found (are you missing a using directive or an assembly reference?) 

The Input Tag Helper sets the HTML type attribute based on the .NET type. The following table lists some common .NET types and generated HTML type (not every .NET type is listed).

.NET type Input Type
Bool type="checkbox"
String type="text"
DateTime type="datetime-local"
Byte type="number"
Int type="number"
Single, Double type="number"

The following table shows some common data annotations attributes that the input tag helper will map to specific input types (not every validation attribute is listed):

Attribute Input Type
[EmailAddress] type="email"
[Url] type="url"
[HiddenInput] type="hidden"
[Phone] type="tel"
[DataType(DataType.Password)] type="password"
[DataType(DataType.Date)] type="date"
[DataType(DataType.Time)] type="time"
using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public class RegisterViewModel < [Required] [EmailAddress] [Display(Name = "Email Address")] public string Email < get; set; >[Required] [DataType(DataType.Password)] public string Password < get; set; >> > 
@model RegisterViewModel 
Email:
Password:

The code above generates the following HTML:

 
Email:
Password:
">

The data annotations applied to the Email and Password properties generate metadata on the model. The Input Tag Helper consumes the model metadata and produces HTML5 data-val-* attributes (see Model Validation). These attributes describe the validators to attach to the input fields. This provides unobtrusive HTML5 and jQuery validation. The unobtrusive attributes have the format data-val-rule="Error Message" , where rule is the name of the validation rule (such as data-val-required , data-val-email , data-val-maxlength , etc.) If an error message is provided in the attribute, it's displayed as the value for the data-val-rule attribute. There are also attributes of the form data-val-ruleName-argumentName="argumentValue" that provide additional details about the rule, for example, data-val-maxlength-max="1024" .

When binding multiple input controls to the same property, the generated controls share the same id , which makes the generated mark-up invalid. To prevent duplicates, specify the id attribute for each control explicitly.

Checkbox hidden input rendering

Checkboxes in HTML5 don't submit a value when they're unchecked. To enable a default value to be sent for an unchecked checkbox, the Input Tag Helper generates an additional hidden input for checkboxes.

For example, consider the following Razor markup that uses the Input Tag Helper for a boolean model property IsChecked :

 

The preceding Razor markup generates HTML markup similar to the following:

 

The preceding HTML markup shows an additional hidden input with a name of IsChecked and a value of false . By default, this hidden input is rendered at the end of the form. When the form is submitted:

The ASP.NET Core model-binding process reads only the first value when binding to a bool value, which results in true for checked checkboxes and false for unchecked checkboxes.

To configure the behavior of the hidden input rendering, set the CheckBoxHiddenInputRenderMode property on MvcViewOptions.HtmlHelperOptions. For example:

services.Configure(options => options.HtmlHelperOptions.CheckBoxHiddenInputRenderMode = CheckBoxHiddenInputRenderMode.None); 

The preceding code disables hidden input rendering for checkboxes by setting CheckBoxHiddenInputRenderMode to CheckBoxHiddenInputRenderMode.None. For all available rendering modes, see the CheckBoxHiddenInputRenderMode enum.

HTML Helper alternatives to Input Tag Helper

Html.TextBox , Html.TextBoxFor , Html.Editor and Html.EditorFor have overlapping features with the Input Tag Helper. The Input Tag Helper will automatically set the type attribute; Html.TextBox and Html.TextBoxFor won't. Html.Editor and Html.EditorFor handle collections, complex objects and templates; the Input Tag Helper doesn't. The Input Tag Helper, Html.EditorFor and Html.TextBoxFor are strongly typed (they use lambda expressions); Html.TextBox and Html.Editor are not (they use expression names).

HtmlAttributes

@Html.Editor() and @Html.EditorFor() use a special ViewDataDictionary entry named htmlAttributes when executing their default templates. This behavior is optionally augmented using additionalViewData parameters. The key "htmlAttributes" is case-insensitive. The key "htmlAttributes" is handled similarly to the htmlAttributes object passed to input helpers like @Html.TextBox() .

@Html.EditorFor(model => model.YourProperty, new < htmlAttributes = new < @class="myCssClass", style="Width:100px" >>) 

Expression names

The asp-for attribute value is a ModelExpression and the right hand side of a lambda expression. Therefore, asp-for="Property1" becomes m => m.Property1 in the generated code which is why you don't need to prefix with Model . You can use the "@" character to start an inline expression and move before the m. :

Generates the following:

With collection properties, asp-for="CollectionProperty[23].Member" generates the same name as asp-for="CollectionProperty[i].Member" when i has the value 23 .

When ASP.NET Core MVC calculates the value of ModelExpression , it inspects several sources, including ModelState . Consider . The calculated value attribute is the first non-null value from:

Navigating child properties

You can also navigate to child properties using the property path of the view model. Consider a more complex model class that contains a child Address property.

public class AddressViewModel < public string AddressLine1 < get; set; >> 
public class RegisterAddressViewModel < public string Email < get; set; >[DataType(DataType.Password)] public string Password < get; set; >public AddressViewModel Address < get; set; >> 

In the view, we bind to Address.AddressLine1 :

@model RegisterAddressViewModel 
Email:
Password:
Address:

The following HTML is generated for Address.AddressLine1 :

Expression names and Collections

Sample, a model containing an array of Colors :

public class Person < public ListColors < get; set; >public int Age < get; set; >> 

The action method:

public IActionResult Edit(int id, int colorIndex)

The following Razor shows how you access a specific Color element:

@model Person @ < var index = (int)ViewData["index"]; > 
@Html.EditorFor(m => m.Colors[index])

The Views/Shared/EditorTemplates/String.cshtml template:

@model string  

Sample using List :

public class ToDoItem < public string Name < get; set; >public bool IsDone < get; set; >> 

The following Razor shows how to iterate over a collection:

@model List  
@for (int i = 0; i < Model.Count; i++) < @Html.EditorFor(model => model[i]) >
Name Is Done

The Views/Shared/EditorTemplates/ToDoItem.cshtml template:

@model ToDoItem @Html.DisplayFor(model => model.Name)   @* This template replaces the following Razor which evaluates the indexer three times. @Html.DisplayFor(model => model[i].Name)   *@ 

foreach should be used if possible when the value is going to be used in an asp-for or Html.DisplayFor equivalent context. In general, for is better than foreach (if the scenario allows it) because it doesn't need to allocate an enumerator; however, evaluating an indexer in a LINQ expression can be expensive and should be minimized.

The commented sample code above shows how you would replace the lambda expression with the @ operator to access each ToDoItem in the list.

The Textarea Tag Helper

The Textarea Tag Helper tag helper is similar to the Input Tag Helper.

using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public class DescriptionViewModel < [MinLength(5)] [MaxLength(1024)] public string Description < get; set; >> > 
@model DescriptionViewModel 

The following HTML is generated:

 
">

The Label Tag Helper

The Label Tag Helper provides the following benefits over a pure HTML label element:

using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public class SimpleViewModel < [Required] [EmailAddress] [Display(Name = "Email Address")] public string Email < get; set; >> > 
@model SimpleViewModel 

The following HTML is generated for the element:

 

The Label Tag Helper generated the for attribute value of "Email", which is the ID associated with the element. The Tag Helpers generate consistent id and for elements so they can be correctly associated. The caption in this sample comes from the Display attribute. If the model didn't contain a Display attribute, the caption would be the property name of the expression. To override the default caption, add a caption inside the label tag.

The Validation Tag Helpers

There are two Validation Tag Helpers. The Validation Message Tag Helper (which displays a validation message for a single property on your model), and the Validation Summary Tag Helper (which displays a summary of validation errors). The Input Tag Helper adds HTML5 client side validation attributes to input elements based on data annotation attributes on your model classes. Validation is also performed on the server. The Validation Tag Helper displays these error messages when a validation error occurs.

The Validation Message Tag Helper

The Validation Message Tag Helper is used with the asp-validation-for attribute on an HTML span element.

The Validation Message Tag Helper will generate the following HTML:

You generally use the Validation Message Tag Helper after an Input Tag Helper for the same property. Doing so displays any validation error messages near the input that caused the error.

You must have a view with the correct JavaScript and jQuery script references in place for client side validation. See Model Validation for more information.

When a server side validation error occurs (for example when you have custom server side validation or client-side validation is disabled), MVC places that error message as the body of the element.

 The Email Address field is required. 

The Validation Summary Tag Helper

The Validation Summary Tag Helper is used to display a summary of validation messages. The asp-validation-summary attribute value can be any of the following:

asp-validation-summary Validation messages displayed
All Property and model level
ModelOnly Model
None None

Sample

In the following example, the data model has DataAnnotation attributes, which generates validation error messages on the element. When a validation error occurs, the Validation Tag Helper displays the error message:

using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public class RegisterViewModel < [Required] [EmailAddress] [Display(Name = "Email Address")] public string Email < get; set; >[Required] [DataType(DataType.Password)] public string Password < get; set; >> > 
@model RegisterViewModel 
Email:

Password:

The generated HTML (when the model is valid):

 
Email:

Password:

">

The Select Tag Helper

The Select Tag Helper asp-for specifies the model property name for the select element and asp-items specifies the option elements. For example:

using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace FormsTagHelper.ViewModels < public class CountryViewModel < public string Country < get; set; >public List Countries < get; >= new List < new SelectListItem < Value = "MX", Text = "Mexico" >, new SelectListItem < Value = "CA", Text = "Canada" >, new SelectListItem < Value = "US", Text = "USA" >, >; > > 

The Index method initializes the CountryViewModel , sets the selected country and passes it to the Index view.

public IActionResult Index()

The HTTP POST Index method displays the selection:

[HttpPost] [ValidateAntiForgeryToken] public IActionResult Index(CountryViewModel model) < if (ModelState.IsValid) < var msg = model.Country + " selected"; return RedirectToAction("IndexSuccess", new < message = msg >); > // If we got this far, something failed; redisplay form. return View(model); > 
@model CountryViewModel 

Which generates the following HTML (with "CA" selected):

 

">

We don't recommend using ViewBag or ViewData with the Select Tag Helper. A view model is more robust at providing MVC metadata and generally less problematic.

The asp-for attribute value is a special case and doesn't require a Model prefix, the other Tag Helper attributes do (such as asp-items )

Enum binding

It's often convenient to use with an enum property and generate the SelectListItem elements from the enum values.

public class CountryEnumViewModel < public CountryEnum EnumCountry < get; set; >> 
using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public enum CountryEnum < [Display(Name = "United Mexican States")] Mexico, [Display(Name = "United States of America")] USA, Canada, France, Germany, Spain >> 

The GetEnumSelectList method generates a SelectList object for an enum.

@model CountryEnumViewModel 

You can mark your enumerator list with the Display attribute to get a richer UI:

using System.ComponentModel.DataAnnotations; namespace FormsTagHelper.ViewModels < public enum CountryEnum < [Display(Name = "United Mexican States")] Mexico, [Display(Name = "United States of America")] USA, Canada, France, Germany, Spain >> 

The following HTML is generated:

 

">

Option Group

The CountryViewModelGroup groups the SelectListItem elements into the "North America" and "Europe" groups:

public class CountryViewModelGroup < public CountryViewModelGroup() < var NorthAmericaGroup = new SelectListGroup < Name = "North America" >; var EuropeGroup = new SelectListGroup < Name = "Europe" >; Countries = new List < new SelectListItem < Value = "MEX", Text = "Mexico", Group = NorthAmericaGroup >, new SelectListItem < Value = "CAN", Text = "Canada", Group = NorthAmericaGroup >, new SelectListItem < Value = "US", Text = "USA", Group = NorthAmericaGroup >, new SelectListItem < Value = "FR", Text = "France", Group = EuropeGroup >, new SelectListItem < Value = "ES", Text = "Spain", Group = EuropeGroup >, new SelectListItem < Value = "DE", Text = "Germany", Group = EuropeGroup >>; > public string Country < get; set; >public List Countries

The two groups are shown below:

option group example

The generated HTML:

  

">

Multiple select

The Select Tag Helper will automatically generate the multiple = "multiple" attribute if the property specified in the asp-for attribute is an IEnumerable . For example, given the following model:

using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace FormsTagHelper.ViewModels < public class CountryViewModelIEnumerable < public IEnumerableCountryCodes < get; set; >public List Countries < get; >= new List < new SelectListItem < Value = "MX", Text = "Mexico" >, new SelectListItem < Value = "CA", Text = "Canada" >, new SelectListItem < Value = "US", Text = "USA" >, new SelectListItem < Value = "FR", Text = "France" >, new SelectListItem < Value = "ES", Text = "Spain" >, new SelectListItem < Value = "DE", Text = "Germany">>; > > 

With the following view:

@model CountryViewModelIEnumerable 

Generates the following HTML:

 

">

No selection

If you find yourself using the "not specified" option in multiple pages, you can create a template to eliminate repeating the HTML:

@model CountryViewModel 
@Html.EditorForModel()

The Views/Shared/EditorTemplates/CountryViewModel.cshtml template:

@model CountryViewModel  
public IActionResult IndexNone() < var model = new CountryViewModel(); model.Countries.Insert(0, new SelectListItem("", "")); return View(model); > 
@model CountryViewModel 

The correct element will be selected ( contain the selected="selected" attribute) depending on the current Country value.

public IActionResult IndexOption(int id)
  

">

Additional resources

Collaborate with us on GitHub

The source for this content can be found on GitHub, where you can also create and review issues and pull requests. For more information, see our contributor guide.