Web API
A Web API is the boundary of your service: the place where raw HTTP becomes a typed call, and an object becomes a status code and a response body again. ASP.NET Core offers two models here — Minimal APIs, where a delegate is mapped straight onto a route, and MVC controllers with their conventions, filters, and ModelState. Both run on the same routing, the same container, and the same middleware pipeline; choosing between them is choosing how much convention you want, not a different framework.
Every characteristic trap in this topic is about who decides, and when. Model binding picks a parameter's source in a strict order: an explicit [FromRoute]/[FromQuery]/[FromBody]/[FromHeader] attribute always wins, and without one [ApiController] infers the source itself — a complex type from the body, a simple type from the route values and then the query string. That same [ApiController] turns an invalid ModelState into an automatic 400 with a ProblemDetails body before the action is even entered — and that does not happen in a Minimal API, because there is no ModelState there. Authentication answers who, authorization answers may they: AddJwtBearer validates the signature, issuer, audience and lifetime against the configured values, puts a ClaimsPrincipal on HttpContext.User, and denies nobody; UseAuthorization denies. The layer-by-layer breakdown is below.
Topic map
- Minimal APIs — a delegate on a route,
TypedResults, the absence ofModelState, and what exactly you give up against controllers. - Action results —
TversusIActionResultversusActionResult<T>, status codes,ProblemDetails. - Model binding — the order of sources,
[ApiController]inference, the single body parameter, the automatic 400. - JWT authentication — what
AddJwtBeareractually validates and where in the pipeline that happens. - Authorization policies — requirements and handlers instead of group names, 401 versus 403.
- API versioning — version the contract, not the database; one explicit selector,
Sunset, and per-version metrics. - API configuration — typed options, the environment variable winning, and where the signing key lives.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
Expecting the automatic 400 of [ApiController] in a Minimal API | There is no ModelState in Minimal APIs — an invalid body reaches your handler; validate it yourself |
| Believing a Minimal API skips the pipeline or the container | It is just another endpoint: same routing, same DI, same middleware pipeline |
| Putting two complex parameters on one action | The request body is read exactly once — at most one parameter can bind from it |
Hand-checking ModelState.IsValid in every action under [ApiController] | Dead code: the 400 with ProblemDetails was already returned before the action ran |
Returning IActionResult from everywhere | The payload type disappears from the API description — OpenAPI and client generators see "something" |
| Thinking a concrete return type cannot express a 404 | ActionResult<T> can — a typed payload and a status result |
| Trusting the issuer and audience printed inside the token | The token would certify itself — they must be checked against the configured values or the check means nothing |
Assuming a JWT payload is encrypted | It is only signed and is readable by anyone holding the token — secrets do not belong in it |
Registering UseAuthorization before UseAuthentication | HttpContext.User is still empty — a valid token yields a permanent 401 |
Expecting UseAuthentication to reject an anonymous request | It only builds the principal; the access decision is made by UseAuthorization |
| Confusing 401 and 403 | 401 means not authenticated (who are you?); 403 means authenticated but not permitted |
| Encoding business rules in role names | Every org change becomes a code change and a release; a policy names the intent, not the group |
| Breaking the contract in place and hoping clients keep up | Third-party clients you do not control break silently — version the contract |
| Selecting the version implicitly — by API key or "latest by default" | The response starts depending on hidden state; one day an old client silently moves to the new contract |
Putting the JWT signing key in appsettings.json | The file is committed — anyone with repository access can mint tokens for your API |
Interview relevance
A Web API interview does not check whether you know the attributes — it checks whether you understand the boundary: where HTTP ends and your type begins. The most common format is "here is an action, tell me what happens to this request": where each parameter binds from, who returns the 400 on an invalid body, what status the client sees, and at what moment the token was handled.
Typical checks:
- What
[ApiController]gives you for free — the automatic 400 withProblemDetails, binding-source inference, required attribute routing. - The order of model-binding sources and why only one parameter can bind from the body.
- When to return
IActionResult, when a concrete type, and whenActionResult<T>. - What Minimal APIs give up against controllers (and what they do not — the pipeline and the container).
- What exactly
AddJwtBearervalidates and where those values come from. - The difference between authentication and authorization, a policy versus a role, and 401 versus 403.
- How to version a public API and how you learn that the old version can finally be removed.
Common wrong answer: "The token is valid, therefore access is granted." A valid signature only says who is calling: UseAuthentication built a ClaimsPrincipal and stopped there. Permission is a separate decision made by UseAuthorization from the endpoint's metadata — which is exactly why a 401 (we do not know who you are) and a 403 (we do, but you may not) are two different answers.