Tag Helpers — HTML-Friendly ASP.NET Core Helpers

Tag Helpers are C# classes that enhance HTML elements with server-side functionality. They look like HTML attributes (asp-controller, asp-action, asp-for) and are processed at render time to generate correct HTML. Tag Helpers replaced the older Html.ActionLink(), Html.BeginForm(), Html.TextBoxFor() and similar HTML Helpers because they are less noisy in templates (they look like HTML, not C# method calls), are more discoverable via IntelliSense, and integrate naturally with the surrounding HTML.

Anchor and Form Tag Helpers

@* ── Anchor Tag Helper — generates correct href from route values ────── *@
@* Old HTML Helper: @Html.ActionLink("View", "Details", "Posts", new { id = post.Id }, null) *@

<a asp-controller="Posts" asp-action="Details" asp-route-id="@post.Id">
    View Post
</a>
@* Generates: <a href="/posts/details/42">View Post</a> *@

@* With named route *@
<a asp-route="blog" asp-route-year="2025" asp-route-slug="@post.Slug">Read More</a>

@* ── Form Tag Helper ───────────────────────────────────────────────────── *@
<form asp-controller="Posts" asp-action="Create" method="post">
    @* Automatically adds anti-forgery token hidden field! *@
    <!-- form fields -->
    <button type="submit">Create Post</button>
</form>
@* Generates:
   <form action="/posts/create" method="post">
     <input name="__RequestVerificationToken" type="hidden" value="..." />
     ... *@

@* ── Input Tag Helper — binds to model property ───────────────────────── *@
@model CreatePostViewModel

<div class="mb-3">
    <label asp-for="Title" class="form-label"></label>
    <input asp-for="Title" class="form-control" />
    <span asp-validation-for="Title" class="text-danger"></span>
</div>
@* asp-for="Title" generates:
   - label: <label for="Title">Title</label>
   - input: <input type="text" id="Title" name="Title" value="@Model.Title" />
   - span:  displays ModelState["Title"] error message *@
Note: The asp-for Tag Helper does several things at once: sets the id and name attributes to the property name (enabling model binding on POST), sets the value attribute to the current model value (pre-populating the field on form redisplay), sets the correct type attribute for the property’s type (datetime-local for DateTime, number for int, checkbox for bool), and adds data-val attributes for client-side validation. A single asp-for replaces multiple attributes you would otherwise set manually.
Tip: Use asp-append-version="true" on <link> and <script> tags to enable cache-busting. It appends a query string hash of the file’s content: <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> generates /css/site.css?v=abc123. When you update the file, the hash changes, forcing browsers to download the new version instead of using their cached copy. Without this, users may see stale CSS or JS after a deployment.
Warning: Tag Helpers only work if @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers is in _ViewImports.cshtml. Without this registration, asp-for, asp-controller, and other Tag Helper attributes are silently ignored — the HTML element is rendered without them, often producing broken links or forms without anti-forgery tokens. If Tag Helpers suddenly stop working after moving views to a new folder, check that _ViewImports.cshtml exists in that folder or a parent folder.

Validation Tag Helpers

@* ── Validation Tag Helpers — display ModelState errors ─────────────────── *@
@model CreatePostViewModel

@* Summary: shows all validation errors at the top of the form *@
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@* ModelOnly = only errors not associated with a specific field *@
@* All = show all errors including field-specific ones in the summary *@

<div class="mb-3">
    <label asp-for="Title"></label>
    <input asp-for="Title" class="form-control" />
    @* Field-specific error — shows ModelState["Title"].Errors *@
    <span asp-validation-for="Title" class="text-danger small"></span>
</div>

@* Client-side validation (optional — requires jquery.validate) *@
@section Scripts {
    @* Enables unobtrusive client-side validation using data-val attributes *@
    <partial name="_ValidationScriptsPartial" />
}

Common Mistakes

Mistake 1 — Forgetting @addTagHelper in _ViewImports (Tag Helpers ignored silently)

❌ Wrong — asp-for, asp-controller etc. are rendered as plain HTML attributes with no effect.

✅ Correct — ensure @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers is in _ViewImports.cshtml.

Mistake 2 — Using asp-action with a typo (incorrect URL, 404 at runtime)

❌ Wrong — asp-action="Detals" (typo) generates /posts/detals — 404, no compile error.

✅ Correct — use asp-action="@nameof(PostsController.Details)" for compile-time safety.

🧠 Test Yourself

A form uses <form asp-controller="Posts" asp-action="Create" method="post">. What extra hidden field does the Form Tag Helper automatically include?