Model binding is the process of populating an action method's parameters from parts of the incoming request. Without [ApiController]'s inference, you'd write the source explicitly:
[HttpGet("{id}")]
public IActionResult GetById(
[FromRoute] int id, // from the URL path segment
[FromQuery] bool includeItems, // from the query string, e.g. ?includeItems=true
[FromHeader(Name = "X-Client-Id")] string? clientId) // from a request header
{
// ...
}
[HttpPost]
public IActionResult Create([FromBody] CreateOrderRequest request) // from the JSON request body
{
// ...
}
[HttpPost("with-file")]
public IActionResult Upload([FromForm] IFormFile file) // from multipart/form-data
{
// ...
}
[ApiController]'s inference rules follow a consistent pattern: a simple type (int, string, Guid) matching a route template parameter comes from the route; a complex type (a class with multiple properties) not matched by the route is assumed to come from the body; and only one parameter per action can be bound from the body — this is a real, easy-to-hit limitation. If an action genuinely needs two complex objects, wrap them in one request DTO rather than trying to accept two separate [FromBody] parameters.