Beginner 50 min.NET 10

Forms, model binding & validation

Turn browser input into trustworthy data with input models, validation, and antiforgery protection.

BY THE END OF THIS TUTORIAL

Here's what you'll be able to do.

  • Separate create input from a database entity
  • Handle invalid submissions without losing user input
  • Protect form posts and redirect after successful writes
Before you begin

Complete the Razor lesson. Run TaskBoard and sign in to a learning account.

Understand the idea

A form is a boundary between a user-controlled browser and your application. Model binding converts submitted values into a C# object. Validation checks whether that object meets your rules. Authorization decides whether the user can perform the operation. None of these steps replaces the others.

TaskBoard uses a dedicated input model to make the accepted fields explicit. The action redisplays invalid input, writes only after validation succeeds, and redirects after success. That small sequence is the foundation of most reliable MVC forms.

Choose your environment

Open TaskBoard.csproj from the example download. Install the ASP.NET and web development workload and a Visual Studio release supporting .NET 10. Select the TaskBoard launch profile and press F5. Use Solution Explorer to find the files named below.

Download the complete companion project
STEP 01

Define the input contract

Required rejects an absent or whitespace-only title. StringLength limits the range of accepted lengths. Notes is optional but bounded. A small input model keeps database-only properties out of the binding surface.

Keep messages useful to a person filling in the form. “Give your task a title” tells them what to do. A database exception about a constraint does not. Attributes provide common checks; use ModelState.AddModelError for business rules that need additional context.

Models/TaskInput.cs
using System.ComponentModel.DataAnnotations;

namespace TaskBoard.Models;

public class TaskInput
{
    [Required(ErrorMessage = "Give your task a title.")]
    [StringLength(100, MinimumLength = 3)]
    public string Title { get => _title; set => _title = value?.Trim() ?? ""; }
    private string _title = "";

    [StringLength(2000)]
    public string? Notes { get; set; }
}

public sealed record TaskListPage(IReadOnlyList<BoardTask> Tasks, string? Query, int Page, int TotalPages, int TotalCount);
STEP 02

Build an accessible form

The helpers create field names matching TaskInput and show validation errors beside the appropriate controls. The sample also displays an error summary. Antiforgery uses a token in the form and a companion cookie to establish that a submission belongs to the expected browser context.

Client-side validation can improve feedback speed, but it must never be the only check. This sample deliberately works with server-side validation alone, so disabling JavaScript does not bypass its rules.

Views/Tasks/Create.cshtml
@model TaskInput
@{ ViewData["Title"] = "Create task"; }
<a asp-action="Index">← My tasks</a><h1>Create task</h1>
<form method="post" class="editor">
    @Html.AntiForgeryToken()
    <div asp-validation-summary="All" role="alert"></div>
    <label asp-for="Title"></label><input asp-for="Title" />
    <span asp-validation-for="Title"></span>
    <label asp-for="Notes"></label><textarea asp-for="Notes" rows="5"></textarea>
    <span asp-validation-for="Notes"></span>
    <button class="button" type="submit">Save task</button>
</form>
STEP 03

Accept or redisplay the submission

MVC populates ModelState before the action runs. If it is invalid, returning the same view preserves the entered data and the validation errors. Only the valid path constructs a database entity and saves it. The owner comes from the authenticated principal, never from a hidden field.

After saving, the redirect changes the browser’s current page into a GET. Refreshing that result repeats the read, not the original create operation. TempData carries a one-time success message across the redirect.

Controller excerpt
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TaskInput input, CancellationToken ct)
{
    if (!ModelState.IsValid) return View(input);
    db.Tasks.Add(new BoardTask
    {
        Title = input.Title.Trim(),
        Notes = input.Notes,
        OwnerId = OwnerId
    });
    await db.SaveChangesAsync(ct);
    TempData["Message"] = "Task created.";
    return RedirectToAction(nameof(Index));
}
STEP 04

Test the boundary, not just the happy path

Submit an empty title, a two-character title, and a valid title. Confirm that invalid submissions do not add records. Then use the practice lab to send invalid values to a real server-side validation endpoint and inspect the returned errors.

A forged field that is absent from TaskInput should not affect the stored entity. A request with a missing antiforgery token should be rejected before the action saves anything. These are distinct tests: input shape, business rules, and request authenticity.

Expected behavior
Empty title       → validation error, no new record
Two-character title → validation error, no new record
Valid title       → save, redirect, success message
Missing CSRF token → HTTP 400 before the write
YOUR TURN

Make it your own.

Add a rule preventing the title “Untitled task”. Return a field-specific error and preserve the entered notes. Test the rule with both lowercase and uppercase input.

See the solution & reasoning

After binding, compare input.Title with the reserved title using StringComparison.OrdinalIgnoreCase. If it matches, call ModelState.AddModelError(nameof(input.Title), "Choose a more descriptive title."). Then keep the existing !ModelState.IsValid return View(input) branch. For whitespace-insensitive comparison, trim before comparing.

When something doesn't work

Returning RedirectToAction for invalid input discards ModelState. Return View(input) instead.

An antiforgery 400 can result from missing tokens, stale cookies, or posting to a different origin. Inspect the request rather than removing the protection.

If you normalize input in ways that change validity, validate the normalized form too; for example, three characters including leading spaces may become a one-character title after trimming.

CHECK YOUR UNDERSTANDING

What should an MVC action do when ModelState is invalid?

What should an MVC action do when ModelState is invalid?

ONE STEP FURTHER

That's another skill in your toolkit.

Your progress is saved in this browser. Sign in to save completion to your account.