← Home
.NETWeb APIReported by candidates

What is model binding in ASP.NET Core ([FromBody], [FromQuery], [FromRoute]), and how do you validate the input (data annotations, FluentValidation)?

9 reports in the question index: Infosys (3), Hexaware, Saviant Consulting, Kale Logistics, Chetu, Wipro and Biyani Technologies, in first technical rounds.

Say this first · about 30 seconds
Make it yours · UDC Finance customer portal API (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next: a customer asks for day 31 with an empty reason. The last file holds the rules the controller just ran.

UDC.sln · where these 3 files sit
  • Contracts/
  • Controllers/
  • Validators/
Diagram · step 1The customer asks to move the payment day to the 31st, with no reason. Binding fills req.
ChangePaymentDay.cs · line 1 · 1 of 1The request class: the portal's JSON fills these two properties by name.

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • Model binding is how ASP.NET Core turns the raw request, meaning the route, the query string, headers and the JSON body, into the typed parameters of my action, so the action gets an int and a request object instead of strings.
  • With the ApiController attribute it works out the source for me: a complex type comes from the body, a simple type from the route when its name matches a route value, otherwise from the query string. I still write FromBody, FromRoute and FromQuery when it isn't obvious.
  • Validation runs right after binding. Data annotations like Required and Range on the request class fill the model state, and ApiController returns a 400 with a validation problem details body before my action runs, so every client gets errors in one shape. For rules across fields I call FluentValidation myself, since its old auto-validation package is no longer recommended. One production trap: Required on a plain decimal does nothing, because a missing field binds to zero, so such fields must be nullable.
Check your answer: tick what you said

You said 0 of 8

Then add your example: UDC Finance customer portal API (invented example)
  • Situation: UDC customers can move their monthly payment day in the portal. The FIS core only accepts days 1 to 28, so the day exists in every month. Our API passed 29, 30 and 31 straight through; the core rejected them in the overnight batch, and the service team phoned each customer the next day.
  • What I did: a FluentValidation validator (a small library: one class holds the rules for one request) for the change-payment-day request: day 1 to 28, and a reason that isn't empty. The controller calls it and puts its errors into , the object that holds the binding and validation errors, so the 400 has exactly the same ProblemDetails shape as every other error from our API.
  • Result: the customer sees "Day must be 1 to 28" next to the field at once. No more overnight rejections or phone calls.
  • What I'd say: the rule lives in one class with its own unit test, and I call it explicitly instead of using the old auto-validation package, which FluentValidation no longer recommends.

See it

1/9A BMW FS dealer app posts a . The data sits in three places in one request.

In plain words

The senior answer · about 2 minutes
What binding does
  • Model binding is the step between routing and my action that turns text into typed parameters. The route gives me dealerId, the query string gives dryRun, and the JSON body becomes a through System.Text.Json, .NET's built-in JSON reader. If a value can't be converted, say "abc" for an amount, that's recorded as an error rather than an exception, so it becomes a 400 and not a 500.
Where each value comes from
  • With [ApiController], complex types come from the body, parameters named like a route value come from the route, and the rest from the query. I write the attributes when a reader would have to guess, and I remember the body is one stream: only one parameter can come from it, and the client must send Content-Type: application/json or it gets a 415.
Validation that clients can rely on
  • Simple rules sit on the request class as data annotations. [ApiController] then answers every invalid request with a 400 before my code runs, so the portal handles one error shape everywhere. Cross-field rules, like a minimum down payment, go in or a FluentValidation validator that I call explicitly and copy into , so the shape stays the same.
  • Business checks that need data, like "this dealer is still active", belong in the service, not in an attribute: they need the database and they return their own errors.
What I watch for
  • Non-nullable numbers. A missing downPayment binds to 0 and [Required] is satisfied, which at Navistar sent applications to credit with no down payment: the incident in Deep. Our rule now: required numbers are nullable, and the API rejects unknown JSON fields, so a renamed field on the client fails loudly in testing instead of silently in production.

One request, every source and check

The real order on .NET 10 for one POST, from the raw request to the action or the 400.

1/6Content-Type first. JSON with application/json is read. A form post can't be, so: 415.

The story

  1. Before

    BMW FS dealers send from their dealer app to our API. In the first version, each action read the request by hand. (A controller is a class that handles requests; an action is one of its methods, answering one kind of request.) It cut the dealer id out of the address, read dryRun from the query string (the part of the address after ?), turned the JSON body into an object with , and then checked each value with its own if.

    1/4Before: every action parses the request and checks each value itself, copied by hand.

  2. Why it had to change

    Parsing and checking input is the same job in every action. Copied by hand, it gets forgotten or done slightly differently, and one missed check lets bad data reach the FIS core (the lender's main loan system, which our API feeds). Every action also answered errors in its own shape, so the dealer app needed special code for each.

  3. How it works now

    ASP.NET Core does both jobs before your action runs. Model binding reads the route, the query string, headers and the JSON body, and fills in the action's parameters, converting text to numbers and objects. Validation then checks the values against rules you put on the request class as attributes (labels in square brackets that add a rule or a behaviour), like [Range(12, 84)]. If anything fails, a controller marked [ApiController] answers 400 with a list of every error, and the action never runs. The main diagram follows one request through both steps.

  4. What we gained

    The actions only do their own work, every input is checked the same way, and every client gets errors in one shape. The rest of the card shows where each value comes from, how to write rules that go beyond one field, and the trap that let bad data through at Navistar.

In the lending API

The action's parameters say where each value comes from; the diagram above shows them being filled.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/BmwFs.Applications.Api/Controllers/
⋯ 6 lines in Deep
ApplicationsController.cs · line 9 · 1 of 4 is the return type: any kind of answer, such as 200 or 400.

The code, file by file

The BMW FS applications API: start-up, the controller and the request class. Tap Next to walk one request.

Lending.sln · where these 3 files sit
  • src/BmwFs.Applications.Api/
    • Controllers/
    • Contracts/
Diagram · step 1A BMW FS dealer app posts a . The data sits in three places in one request.
Program.cs · line 3 · 1 of 2Turns on controllers, and with them model binding and validation.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: Truck loan applications went to credit with no down payment

P2 · serious, has a workaroundNavistar Financialcame with a deployCredit applications API (ASP.NET Core on App Service)

Composed, not a real event at Navistar Financial. The mechanism is real ASP.NET Core, checked by a run on .NET 10: System.Text.Json ignores a JSON field it doesn't know, a missing field leaves a decimal property at 0, and [Required] on a non-nullable decimal never fails. decimal? makes [Required] fail, and UnmappedMemberHandling.Disallow turns an unknown field into a 400. Times, counts, names, ticket and PR numbers are composed.

The story in 5 lines
  1. On Thu 8 Oct Navistar's daily credit exceptions report showed 21 approved truck loan applications with a down payment of $0, which credit policy doesn't allow for these buyers.
  2. The applications table had 63 applications with a $0 down payment, all submitted since dealer portal release 7.2 on Tue 6 Oct. Before that: none.
  3. Release 7.2 renamed the portal's field downPayment to downPaymentAmount. The API ignored the unknown field, the decimal stayed at 0, and [Required] on a non-nullable decimal can't fail, so every application was accepted.
  4. The same day: the 21 approvals were put on hold, the portal sent the right name again, and an made the field decimal?, so a missing value is a 400. The fix also rejects unknown JSON fields, and a contract test runs on both sides.
  5. The rule since: required numbers in request classes are nullable, unknown fields are refused, and the portal and API test the same request samples.
Who's who (6 people)
You
.NET developer on the vendor team (L3). Owns the API fix.
Tech lead
Vendor senior. Runs the bridge call, reviews every PR.
Portal developer
Vendor Angular developer on the Navistar dealer portal.
L2 pod
Vendor support. Runs queries, deploys with approval.
Navistar credit operations analyst
Owns the daily exceptions report on credit decisions.
Navistar IT change manager
Approves the emergency change.

Tap a step. The map lights the place where it happens.

1/8 Detect

The exceptions report: 21 approved applications with a $0 down payment.

Thu 8 Oct, 09:00 CT, US Central (19:30 IST)Navistar credit operations analyst, then the L2 pod

  • What: the daily exceptions report, which lists credit decisions that break a policy, shows 21 truck loan applications approved with a down payment (the part of the price the buyer pays up front) of $0. Policy needs at least 10% for these buyers.
  • How we saw it: the analyst checks three of them with the dealers. All three buyers did agree a down payment; the dealers had typed it into the portal. opens a ticket with the vendor team.
  • Why it hid: the API answered 200 OK to every one. A $0 down payment is a valid number, so nothing in the logs looked wrong.
In theory

A request that leaves out a required value is refused.

In practice

The API accepted it and stored 0. Only a business report, two days later, noticed.

Try it yourself · 10 minutes, on your Mac
  1. dotnet new webapi -n BindLab --use-controllers. Add a request class with [Required] public decimal DownPayment { get; set; } and an [ApiController] action that returns it.
  2. POST {} with Content-Type: application/json: you get 200 and downPayment: 0. Then POST {"downPaymentAmount": 5}: the same, the unknown field is ignored.
  3. Set UnmappedMemberHandling = Disallow in and repeat the second POST: now a 400 that names $.downPaymentAmount (the JSON path of the unknown field).
  4. Change the property to decimal? and POST {} again: 400, "The DownPayment field is required."
You should see

A value type always has a value, so [Required] can't tell "missing" from 0. Nullable types and refusing unknown fields make a missing or misnamed value fail loudly.

The senior move

"Required numbers are nullable, unknown fields are refused, and the client's real requests run in the API's tests."

Sources Sourced: [Required] on non-nullable value types and the advice to use nullable types (ASP.NET Core docs, "Model validation": [Required] validation on the server); (.NET 8 and later, System.Text.Json docs, "Handle unmapped members"). Checked with a real run on .NET 10: a missing or misnamed field bound a decimal to 0 and passed [Required]; decimal? returned 400; returned 400 naming the field. Composed: Navistar's systems, dates, counts, names, ticket and numbers and the SQL.

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 10 minutes

Add validation to the create-application endpoint: customer name required, amount 1,000 to 500,000, term 12 to 84 months, down payment required. Invalid input must return 400 ProblemDetails listing every invalid field.

Biyani Technologies: server-side validation in ASP.NET
Stuck? Hints
  1. Which attributes check "present" and "between two numbers"?
  2. What makes the controller answer 400 by itself?
  3. What happens to a missing decimal field?
Show a solution
Lending.sln · where these 2 files sit
  • Controllers/
  • Contracts/
Diagram · step 8[ApiController] answers 400 with both errors. The action never runs.
ApplicationsController.cs · line 1 · 1 of 2Automatic 400 when validation fails.
What the interviewer checks
  • A request DTO with [Required] and [Range] on the right properties.
  • [ApiController] on the controller, so invalid input gets a 400 before the action runs.
  • decimal? for the down payment, so a missing value is caught.
  • registered; the body lists every invalid field with its message.
  • Can say what happens without [ApiController]: the action runs and must check .

Task 2

Question index: Mustmedium · 15 minutes

Add a rule across two fields without a library: the down payment must be at least 10% of the amount. It must come back in the same 400 shape as the other errors.

Saviant Consulting: ways to send data to a controller
Stuck? Hints
  1. Which built-in interface lets a class validate itself?
  2. How do you tie the error to the field?
  3. When does this method run if is already out of range?
Show a solution
Lending.sln · where this file sits
  • Contracts/
Diagram · step 2The attributes run first. Every field passes on its own.
CreateApplicationRequest.cs · line 2 · 1 of 5A built-in interface with one method, , that MVC calls after the attributes pass.
What the interviewer checks
  • on the request class, with returning a for .
  • The error appears under in the same 400 .
  • Knows runs only after every attribute passes, so it can trust is in range.
  • Can say when FluentValidation would be the better choice.
With AI tools

Interviewers now ask how AI tools change this. Assistants write request classes in seconds, and two mistakes keep showing up in what they generate: [Required] on non-nullable numbers, which never fails, and controllers built straight on the EF entity, which allows over-posting. I review every generated DTO for nullable required numbers and fields the client shouldn't set, and I ask the assistant for the invalid-input tests as well as the class.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked