Learn the HTML helper methods that came before tag helpers, such as Html.ActionLink, Html.TextBoxFor and Html.DisplayNameFor, understand where they still show up in scaffolded views, and know when to use each style.

Bishal Biswas
Self Learner
What HTML helpers are
Before tag helpers existed, Razor views generated links and form fields with HTML helpers: C# methods on the Html property that return a piece of markup.
@Html.ActionLink("Keyboard", "Details", "Products", new { id = 5 }, null)
<a href="/Products/Details/5">Keyboard</a>
That is the same link the anchor tag helper makes. The difference is style. A tag helper looks like HTML with extra attributes. An HTML helper looks like a C# method call.
Tag helpers are the better choice for new code, and this course uses them everywhere. But you still need to read HTML helpers, because scaffolded views, older projects and a great many tutorials use them. A few of them also have no tag helper equivalent.
Links
@Html.ActionLink("All products", "Index", "Products")
@Html.ActionLink("Keyboard", "Details", "Products", new { id = 5 }, new { @class = "btn" })
@Html.RouteLink("Keyboard", "ProductDetails", new { id = 5 })
The arguments are link text, action, controller, route values and then HTML attributes. Since class is a C# keyword, it is written as @class in the attributes object.
Form fields
The ...For helpers take a lambda that picks a model property, the same way asp-for takes a property name:
@model Product
@using (Html.BeginForm("Edit", "Products", FormMethod.Post))
{
@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Name)
@Html.LabelFor(m => m.Price)
@Html.TextBoxFor(m => m.Price, new { @class = "form-control" })
@Html.CheckBoxFor(m => m.InStock)
@Html.TextAreaFor(m => m.Description)
@Html.HiddenFor(m => m.Id)
@Html.DropDownListFor(m => m.CategoryId, (SelectList)ViewBag.Categories)
<button type="submit">Save</button>
}
Html.BeginForm writes the opening <form> tag, and the closing tag is written when the using block ends. That trick is why the form sits inside @using ( ... ) { }.
Here is the same form with tag helpers, so you can see the mapping:
<form asp-action="Edit" method="post">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name"></span>
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control" />
<input asp-for="InStock" />
<textarea asp-for="Description"></textarea>
<input type="hidden" asp-for="Id" />
<select asp-for="CategoryId" asp-items="ViewBag.Categories"></select>
<button type="submit">Save</button>
</form>
Both produce the same HTML, including the id, name, value and validation attributes.
Helpers that have no tag helper
These are the ones you will keep using even in a tag helper world.
Html.DisplayNameFor prints the display name of a property, which is the [Display(Name = "...")] text or the property name. Scaffolded list views use it for table headings:
<th>@Html.DisplayNameFor(model => model.Price)</th>
Html.DisplayFor prints a value using its display template, so a DateTime with [DataType(DataType.Date)] shows only the date and a bool shows a disabled checkbox:
<td>@Html.DisplayFor(modelItem => item.Price)</td>
Html.EditorFor picks the right input for a property’s type and attributes. It is what the display and editor templates topic builds on:
@Html.EditorFor(m => m.ReleaseDate)
Html.Raw outputs a string without HTML encoding. Use it only with content you trust:
@Html.Raw(Model.DescriptionHtml)
Html.AntiForgeryToken writes the hidden anti forgery field. The form tag helper does this automatically, but a hand written form or an AJAX call needs it:
@Html.AntiForgeryToken()
Url helpers build URLs without a tag:
<img src="@Url.Content("~/images/logo.png")" />
<script>const detailsUrl = '@Url.Action("Details", "Products")';</script>
A scaffolded view
When Visual Studio or the dotnet aspnet-codegenerator tool generates a list view, it mixes the two styles:
@model IEnumerable<Product>
<table class="table">
<thead>
<tr>
<th>@Html.DisplayNameFor(model => model.Name)</th>
<th>@Html.DisplayNameFor(model => model.Price)</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@Html.DisplayFor(modelItem => item.Name)</td>
<td>@Html.DisplayFor(modelItem => item.Price)</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
HTML helpers for display, tag helpers for links and forms. That is a sensible mix and there is nothing wrong with keeping it.
Choosing a style
- New forms and links: tag helpers. They read like HTML and get better editor support.
- Displaying values and names in tables:
DisplayForandDisplayNameFor. - Raw output, anti forgery tokens and URLs inside script blocks: HTML helpers, because there is no tag for them.
Do not rewrite working HTML helpers just to modernise them. Both styles produce the same HTML and both are fully supported.
Quick recap
- HTML helpers are C# methods on
Htmlthat return markup. Tag helpers do the same job with HTML attributes. Html.ActionLink,Html.BeginForm,Html.TextBoxForand friends map one to one ontoasp-action,<form asp-action>andasp-for.Html.DisplayNameFor,Html.DisplayFor,Html.EditorFor,Html.RawandHtml.AntiForgeryTokenhave no tag helper and remain useful.- Scaffolded views mix both styles, and that mix is fine.
- Prefer tag helpers in new code, and read HTML helpers comfortably in older code.
@* Views/Products/Edit.cshtml written with HTML helpers, then the same form with tag helpers *@
@model Product
@{
ViewData["Title"] = "Edit";
}
<h1>Edit @Html.DisplayFor(m => m.Name)</h1>
@* ---- HTML helper version ---- *@
@using (Html.BeginForm("Edit", "Products", new { id = Model.Id }, FormMethod.Post, true, new { @class = "mb-5" }))
{
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(m => m.Id)
<div class="mb-3">
@Html.LabelFor(m => m.Name, new { @class = "form-label" })
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Name, "", new { @class = "text-danger" })
</div>
<div class="mb-3">
@Html.LabelFor(m => m.Price, new { @class = "form-label" })
@Html.TextBoxFor(m => m.Price, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Price, "", new { @class = "text-danger" })
</div>
<div class="mb-3">
@Html.LabelFor(m => m.CategoryId, new { @class = "form-label" })
@Html.DropDownListFor(m => m.CategoryId, (SelectList)ViewBag.Categories, new { @class = "form-select" })
</div>
<div class="mb-3 form-check">
@Html.CheckBoxFor(m => m.InStock, new { @class = "form-check-input" })
@Html.LabelFor(m => m.InStock, new { @class = "form-check-label" })
</div>
<button type="submit" class="btn btn-primary">Save</button>
@Html.ActionLink("Cancel", "Index", "Products", null, new { @class = "btn btn-link" })
}
@* ---- The same form with tag helpers ---- *@
<form asp-action="Edit" asp-route-id="@Model.Id" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Id" />
<div class="mb-3">
<label asp-for="Name" class="form-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Price" class="form-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="CategoryId" class="form-label"></label>
<select asp-for="CategoryId" asp-items="ViewBag.Categories" class="form-select"></select>
</div>
<div class="mb-3 form-check">
<input asp-for="InStock" class="form-check-input" />
<label asp-for="InStock" class="form-check-label"></label>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="Index" class="btn btn-link">Cancel</a>
</form>
@* Helpers with no tag helper equivalent *@
<p>Field name: @Html.DisplayNameFor(m => m.Price)</p>
<div>@Html.Raw(Model.DescriptionHtml)</div>
<script>
const detailsUrl = '@Url.Action("Details", "Products")';
</script>
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready