Tag Helpers customize the HTML generated by Razor. They usually look like attributes on ordinary elements—often beginning with asp-—but can also introduce new elements. Use them to connect a PageModel to a form without writing every label, input attribute, and validation placeholder by hand. Because the template still looks much like HTML, it remains easier to read and edit. Tag Helpers render HTML on the server; they do not run directly in frontend frameworks such as Angular or React. section 18.0 section 18.1
Bind a checkout field
Expose a binding model as an Input property on a checkout PageModel. In the Razor form, write <label asp-for="Input.FirstName"></label>, <input asp-for="Input.FirstName" />, and <span asp-validation-for="Input.FirstName"></span>. The helpers use the property and its DataAnnotations metadata to generate matching label and input attributes, a display caption, and a place for validation messages. For example, the generated input has a name of Input.FirstName and an id of Input_FirstName. You can still add ordinary HTML attributes, such as class, alongside asp-for. section 18.2 section 18.6
Listing 18.3 Razor template for binding to UserBindingModel on the checkout page
@page @model CheckoutModel #1 @{ ViewData["Title"] = "Checkout"; } <h1>@ViewData["Title"]</h1> <form asp-page="Checkout"> #2 <div class="form-group"> <label asp-for="Input.FirstName"></label> #3 <input class="form-control" asp-for="Input.FirstName" /> <span asp-validation-for="Input.FirstName"></span> </div> <div class="form-group"> <label asp-for="Input.LastName"></label> <input class="form-control" asp-for="Input.LastName" /> <span asp-validation-for="Input.LastName"></span> </div> <div class="form-group"> <label asp-for="Input.Email"></label> <input class="form-control" asp-for="Input.Email" /> #4 <span asp-validation-for="Input.Email"></span> </div> <div class="form-group"> <label asp-for="Input.PhoneNumber"></label> <input class="form-control" asp-for="Input.PhoneNumber" /> <span asp-validation-for="Input.PhoneNumber"></span> #5 </div> <button type="submit" class="btn btn-primary">Submit</button> </form> #1 The CheckoutModel is the PageModel, which exposes a UserBindingModel on the Input property. #2 Form Tag Helpers use routing to determine the URL the form will be posted to. #3 The Label Tag Helper uses DataAnnotations on a property to determine the caption to display. #4 The Input Tag Helper uses DataAnnotations to determine the type of input to generate. #5 The Validation Tag Helper displays error messages associated with the given property.
Compare the checkout template’s short asp-for declarations with the generated HTML to see which attributes the helpers supply. section 18.2
Choose the helper that matches the form element or task:
- Use the Form Tag Helper’s asp-page and asp-page-handler attributes to generate an action URL; asp-route-* supplies route values. It also adds a hidden field for CSRF protection. section 18.6
- Use asp-for on labels and inputs to draw on property names, types, and DataAnnotations metadata. Input helpers can produce data-val-* attributes, but client-side validation also requires jQuery validation and unobtrusive-validation scripts. section 18.6
- Use asp-for and asp-items with the Select Tag Helper to build a drop-down list or list box. Binding to an IEnumerable property enables a multiselect element. section 18.6
A form often exposes the same object for display and model binding. Keep sensitive values that users must not edit out of that binding model: otherwise, the arrangement can permit mass-assignment attacks. Tag Helpers reduce markup, but they do not remove the need to design the model safely. section 18.2
When built-in helpers do not fit, create a TagHelper class. A helper can target an element or an attribute, accept values through properties, and change its output in Process() or ProcessAsync(). For example, a custom if attribute can leave an element intact when a Boolean is true, or remove the element and its contents when false. This keeps a simple conditional close to the HTML rather than wrapping it in a Razor C# if block. For more complex page sections with their own business logic or data access, consider a view component instead. section 32.1.2 section 32.5

