How Model Binding Works
Model binding maps data from an incoming HTTP request onto action method parameters or a bound model object. Binding source attributes, [FromBody], [FromQuery], [FromRoute], [FromForm], and [FromHeader], control exactly where a value is read from; when no attribute is present, ASP.NET Core infers a source based on the parameter's type and context, complex types default to the body in controllers and default to the body in minimal APIs too, while simple types default to route values (if a matching template segment exists) or the query string otherwise.
Cricket analogy: Model binding inferring where a value comes from is like an umpire automatically knowing whether an appeal is for LBW or a run-out based on the fielder's gesture: [FromRoute] pulls an id from the URL path the way a caught-behind appeal is read from the wicketkeeper's specific gesture, no extra explanation needed.
public class CreateOrderRequest
{
[Required, StringLength(100, MinimumLength = 3)]
public string CustomerName { get; set; } = default!;
[Range(1, 1000)]
public int Quantity { get; set; }
}
[HttpPost]
public IActionResult Create(
[FromBody] CreateOrderRequest request,
[FromHeader(Name = "X-Client-Id")] string clientId)
{
if (!ModelState.IsValid)
return ValidationProblem(ModelState);
return Ok(new { request.CustomerName, request.Quantity, clientId });
}Data Annotations Validation
Data annotation attributes, [Required], [StringLength], [Range], [EmailAddress], and others, are applied directly to model properties and are evaluated automatically during model binding, populating ModelState with any validation errors before the action method body runs. On a controller decorated with [ApiController], the framework short-circuits the action entirely and returns a 400 Bad Request with a ProblemDetails body the moment ModelState.IsValid is false, without the developer needing to check it manually.
Cricket analogy: Data annotations are like a bowling-action review that checks an arm doesn't bend beyond 15 degrees before a delivery is legal: [Range(0,100)] on a Score property rejects an invalid value the way ICC's biomechanics check rejects an illegal bowling action before the ball is bowled.
When a controller is decorated with [ApiController], ASP.NET Core automatically short-circuits the action and returns a 400 Bad Request with a ProblemDetails body the moment ModelState.IsValid is false; you don't need to write the if (!ModelState.IsValid) return BadRequest(...) check yourself in most cases.
Custom Validation
Cross-property validation rules, ones that depend on the relationship between two or more fields, can't be expressed by a single data annotation attribute on one property. Implementing IValidatableObject on a model, or writing a custom ValidationAttribute subclass, lets you inspect the whole object and add validation errors that reflect relationships, such as requiring a CheckOutDate to fall after a CheckInDate.
Cricket analogy: A custom validator implementing IValidatableObject is like a third umpire applying a nuanced rule that depends on multiple factors at once, such as checking both foot placement and bat position for a stumping: it can compare EndDate against StartDate across the whole object, something a single [Range] attribute on one property can't do.
FluentValidation as an Alternative
FluentValidation is a popular third-party library that externalizes validation rules into dedicated validator classes, subclasses of AbstractValidator<T>, using a fluent chain like RuleFor(x => x.Email).NotEmpty().EmailAddress(), instead of decorating the model with attributes. Because a validator is just a class registered in DI, it can inject dependencies like a repository to perform asynchronous checks (uniqueness, for example), and it's straightforward to unit test in isolation from ASP.NET Core's request pipeline.
Cricket analogy: FluentValidation is like a specialist fielding coach brought in separately from the head coach to focus purely on catching drills: instead of annotating the Player class itself with attributes, you write a dedicated PlayerValidator class, keeping validation logic cleanly separated the way a specialist coach's drills are separate from match tactics.
Binding a request body directly onto an EF Core entity (rather than a dedicated DTO) opens the door to overposting: a client can include extra JSON properties like IsAdmin or AccountBalance that map onto entity properties never intended to be client-settable. Always bind to a purpose-built request DTO and map explicitly onto the entity.
- Binding source attributes like [FromBody], [FromQuery], [FromRoute], [FromForm], and [FromHeader] control exactly where a parameter's value is read from.
- When a source isn't specified, ASP.NET Core infers it: complex types default to the body, simple types default to the query string.
- Data annotations like [Required], [StringLength], and [Range] populate ModelState, and [ApiController] auto-returns 400 when it's invalid.
- IValidatableObject and custom ValidationAttribute subclasses support cross-property rules a single per-field attribute can't express.
- FluentValidation externalizes validation rules into dedicated, independently testable validator classes instead of attributes on the model.
- Binding a request body directly onto an EF Core entity risks overposting; always bind to a dedicated DTO.
Practice what you learned
1. Which attribute would you use to bind a parameter from the JSON request body?
2. What happens automatically when a controller has [ApiController] applied and ModelState.IsValid is false?
3. Why would you use IValidatableObject instead of a set of individual data annotation attributes?
4. What is 'overposting' in the context of model binding?
5. What's a key benefit of using FluentValidation over data annotation attributes?
Was this page helpful?
You May Also Like
Minimal APIs
A lightweight, low-ceremony way to build HTTP APIs in ASP.NET Core using top-level route handlers instead of controller classes.
Controllers and Attribute Routing
How ControllerBase classes and [Route]/[Http*] attributes map incoming requests to action methods in ASP.NET Core Web API.
Action Results and Status Codes
How ASP.NET Core action results map to HTTP status codes, and how to choose the right response for every outcome.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics