The ASP.NET Core pipeline
Model binding: from HTTP to C# objects
How route, query string and body become method parameters, and how over-posting is prevented.
Understand the concept
Model binding converts values from different parts of the request into method parameters. Route values and the query string bind to simple types; a JSON body binds to complex types. With [ApiController], complex types are read from the body by default; [FromRoute], [FromQuery], [FromBody] and [FromHeader] name the source explicitly.
Type conversion happens during binding. If ‘abc’ cannot bind to an int parameter, an error is added to ModelState and [ApiController] rejects the request with 400 automatically. The JSON body is read with System.Text.Json and its size can be limited.
The most important security rule: never make a database entity the binding target. A user can add unexpected fields to the body (IsAdmin, Price, OwnerId) and they all get written to the entity; this is over-posting or mass assignment. Use a request model (DTO or record) containing only the permitted fields instead.
Every value from the request is untrusted input; where it comes from and in what shape must be explicit.
Binding an EF Core entity with [FromBody] and calling SaveChanges directly.
A letter’s journey through the city
Think of a request as a letter and the server as a city. Pick a scenario, press play and watch which gates the letter passes and where it gets stopped.
The contact form is sent, but the e-mail field says ‘ayse-at-ornek’.
One topic. Three levels of depth.
// GET /api/posts?page=2&category=Backend
[HttpGet]
public IActionResult List([FromQuery] int page = 1, [FromQuery] string? category = null) => …Query string values bind to simple parameters.
This page connects to no real server or database. Code samples are illustrative for .NET 8, Next.js and SQL Server; timings are example values in the simulation, not measurements. Try samples only in your own development environment; never share real connection strings, tokens or passwords.
Try it yourself first.
The profile update endpoint currently binds the User entity. Users may change only DisplayName and Bio. Make it safe.
Show the solution and explanation
public sealed record UpdateProfile([MaxLength(60)] string DisplayName, [MaxLength(500)] string? Bio);
[HttpPut("me")]
public async Task<IActionResult> Update(UpdateProfile input, CancellationToken ct)
{
var user = await db.Users.SingleAsync(u => u.Id == CurrentUserId, ct);
user.DisplayName = input.DisplayName;
user.Bio = input.Bio;
await db.SaveChangesAsync(ct);
return NoContent();
}Only permitted fields exist in the model and are assigned one by one. The identity comes from the session, not the URL.