HTML Helpers and Forms

This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2, and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the System.Web-hosted MVC framework, its routing, Razor views, HTML helpers, model binding, filters, and the OWIN-based authentication/Identity stack — as described by the official documentation at Microsoft Learn (plus Web API, Web Pages, SignalR, and Identity), which are the reference these pages are written and verified against.

This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, System.Web-hosted MVC framework; it is functionally frozen and receives only security fixes. For the current, cross-platform MVC framework see MVC Controllers and Views under ASP.NET Core (Blazor).

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page documents ASP.NET MVC 5.3.x HTML helpers on .NET Framework 4.8.1 — not ASP.NET Core’s Tag Helpers, which replace most of this API surface (see the cross-link at the end of this page).

Four kinds of helper

  • Standard — untyped, string-keyed: Html.TextBox("Name", value).

  • Strongly typed — a lambda expression over Model, giving compile-time checking and correct name/id attributes for model binding: Html.TextBoxFor(m ⇒ m.Name).

  • Templated — resolved by type/metadata rather than naming a specific input: Html.EditorFor(m ⇒ m.Price) (see Views, Layouts, and Partials).

  • Inline (@helper) — a markup-producing function scoped to one view (see Razor Syntax).

Strongly typed helpers are preferred in current MVC 5 code: they survive a property rename (compile error instead of a silent broken form) and their generated name attribute is exactly what the default model binder expects (see Model Binding and Validation).

Html.BeginForm / Html.BeginRouteForm

Both return an IDisposable (MvcForm) that writes the closing </form> tag when disposed — almost always used with a using block:

@using (Html.BeginForm("Edit", "Products", FormMethod.Post, new { @class = "form-horizontal" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    @* fields *@
    <button type="submit">Save</button>
}

@using (Html.BeginRouteForm("ProductDetails", new { id = Model.Id }, FormMethod.Post))
{
    @* posts to a specific named route instead of an action/controller pair *@
}

The *For field helpers

@model MyApp.Models.ProductEditViewModel

@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Name)

@Html.PasswordFor(m => m.NewPassword)
@Html.TextAreaFor(m => m.Description, 5, 40, null)
@Html.CheckBoxFor(m => m.IsActive)
@Html.RadioButtonFor(m => m.Size, "Small")
@Html.RadioButtonFor(m => m.Size, "Large")

@Html.DropDownListFor(m => m.CategoryId,
    new SelectList(Model.Categories, "Id", "Name", Model.CategoryId), "-- choose --")
@Html.ListBoxFor(m => m.SelectedTagIds, new MultiSelectList(Model.AllTags, "Id", "Name"))

@Html.HiddenFor(m => m.Id)
@Html.EditorFor(m => m.Price)
@Html.DisplayFor(m => m.CreatedOn)

@Html.EnumDropDownListFor(m => m.Status)          <!-- MVC 5.1+: builds a SelectList from an enum's values -->

SelectList/MultiSelectList (System.Web.Mvc) wrap an IEnumerable, a value-field name, a text-field name, and the currently selected value(s) — constructed in the controller or the view model, not hand-built as <option> tags.

Validation helpers

@Html.ValidationSummary(excludePropertyErrors: true, message: "Please correct the errors below.")
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })

These render the error messages ModelState accumulated during model binding/validation (see Model Binding and Validation) and carry the data-val-* attributes that unobtrusive client validation reads.

Html.AntiForgeryToken()

Emits a hidden input carrying a per-user, per-session token that the matching [ValidateAntiForgeryToken] action filter checks on submit — MVC 5’s CSRF defense; see Security Hardening for the full mechanism and the AJAX variant.

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
}

HtmlHelper extension methods and MvcHtmlString

Every helper above is an extension method on HtmlHelper/HtmlHelper<TModel>, which is how custom helpers are added without subclassing anything:

public static class CustomHtmlHelpers
{
    public static MvcHtmlString StarRating(this HtmlHelper html, int rating)
    {
        var stars = string.Concat(Enumerable.Range(0, 5)
            .Select(i => i < rating ? "&#9733;" : "&#9734;"));
        return new MvcHtmlString(stars);          // marks the string as "already safe", bypassing auto-encoding
    }
}
@Html.StarRating(Model.Rating)

MvcHtmlString (implementing IHtmlString) is what tells Razor a value is already-encoded markup rather than plain text to be HTML-encoded — returning a bare string from a helper would get double-escaped when rendered.

Why ASP.NET Core replaced most of this with Tag Helpers

Two problems with HTML helpers motivated Tag Helpers: they read as C# method calls even though they produce HTML (@Html.TextBoxFor(…​) gives no visual cue in the rendered markup, and HTML/CSS tooling can’t see through it), and building one requires C#, not markup. ASP.NET Core’s Tag Helpers (<input asp-for="Name" />) attach to ordinary HTML attributes, stay readable as HTML in the source, and are processed server-side without changing the tag a designer would otherwise write. See Razor Syntax and Tag Helpers for the ASP.NET Core equivalent of every helper on this page, and Creating Custom HTML Helpers (C#) for more on the MVC 5 model.

Next: Model Binding and Validation covers how a submitted form comes back into an action’s parameters.