Mert Özen Further With Every Line
Backend .NET Core Web API — #06

Model Binding: Binding Request Data to Your Method (Route, Query, Body, Header)

Mert Özen Jul 26, 2026 11 min 64 views
Model Binding: Binding Request Data to Your Method (Route, Query, Body, Header)

We cover how .NET automatically binds request data to your method parameters: reading from the route, query string, body and headers, attributes like [FromBody], and common mistakes.

In the previous articles we wrote endpoints and learned to return the right status codes. But we always took one thing for granted: the data coming into the methods. When we wrote GetById(int id), how did that id value pass from the address into the method? The name of this magic is model binding. Today we understand from the ground up how .NET automatically carries the data from all around an incoming request into your method parameters.

What Does Model Binding Actually Do?

When an HTTP request arrives, data can be hidden in many places: inside the address, after the question mark at the end of the address, in the body of the request, or in its headers. All of this data, in its raw form, is text. Model binding is the bridge that takes this raw text and places it into your C# method parameters, converting it to the right type.

Think of it like a customs officer: it opens the packages coming from outside and places their contents on the right shelves. When you write int id in your method, model binding takes the text from the address, tries to convert it into a number, and puts it into the id parameter. You never deal with this conversion; you just declare what you want in the method signature, and .NET handles the rest.

Where Can the Data Come From?

There are four main places where data can be found in an incoming request. Most of the time .NET figures out where to look on its own, but knowing what's going on saves you a lot of time when things get tangled.

From the route (inside the address)

Data that is a part of the address. This is the example you already know:

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    return Ok($"Requested user: {id}");
}

The id here comes from the {id} placeholder in the address. For values that uniquely identify a resource (usually ids), the route is the most natural place.

From the query string (from the end of the address)

Data that comes after the question mark at the end of the address, usually used for filtering and sorting. For example, in an address like /users?page=2&size=10, page and size are query parameters:

[HttpGet]
public IActionResult GetAll(int page, int size)
{
    return Ok($"Page {page}, size {size}");
}

When the method parameter's name is the same as the key in the query, .NET matches the two on its own. For optional, non-mandatory information, the query string is a perfect fit.

From the body (request body)

The main data, usually in JSON format, that you send while creating a new record or updating one. When you want to send a user object, you write it like this:

[HttpPost]
public IActionResult Create([FromBody] User user)
{
    return Ok($"Created user: {user.Name}");
}

The [FromBody] here tells .NET "fill this parameter from the JSON in the request's body." The client sends JSON, and model binding converts it into your User class. The body is the standard way to carry complex objects (data with multiple fields).

From the header

Data carried in the request's headers. Used less often, but it's the right place for certain information (for example, an API key or a special tracking id):

[HttpGet("profile")]
public IActionResult GetProfile([FromHeader(Name = "X-Client-Id")] string clientId)
{
    return Ok($"Requesting client: {clientId}");
}

Here we explicitly say which header to read from with [FromHeader]. Headers are usually used not for the data itself, but for side information related to the request.

How Does .NET Know Where to Look?

Most of the time you don't even need to write an attribute, because .NET makes reasonable assumptions. For simple types (like numbers and text) it looks at the route first, then the query string. For complex types (the classes you write) it assumes the body. So even if you don't write attributes like [FromBody] and [FromQuery], it usually works.

So why do we write them? Because clarity always wins. When someone reading your code sees [FromBody], they understand at a glance where the data comes from; they don't have to guess. Also, in cases where the assumptions aren't enough (for example, when you want to read from the query a value that would normally come from the route), these attributes are your only way out. In short, trust .NET's intelligence but declare your intent clearly in the code.

A Common Mistake

The place beginners get stuck the most is this: the object arriving empty even though they expect data from the body. The reason is usually one of two things. Either the client isn't sending the data as JSON with the right content type, or the properties of the User class don't match the field names in the sent JSON. Model binding does the matching by name; if the JSON has name while your class says FullName, that field stays empty and no one gives you an error. When your object arrives unexpectedly empty, the first place to look is whether the field names match exactly.

A Small Experiment

Add two new endpoints to your UsersController. The first, a GetAll that takes page and size from the query string; the second, a Create that takes a User from the body with [FromBody]. Then send requests to both with an API testing tool (or the browser's developer tools). Change the query parameters, send different JSON to the body, and observe how the method parameters get filled. Also, deliberately misspell a field name in the JSON; see with your own eyes how that field of the object stays empty. This small experiment settles the logic of model binding better than any explanation.

In the next article we'll move on to the DTO concept: we'll talk about why, instead of directly using the data coming from the body or the database objects, we place special carrier classes in between. The model binding you learned today is exactly the foundation you need to understand where in the request those DTOs get filled from and how.