.NET Core / Web API71 min total · 19 parts
Building REST APIs with ASP.NET Core: Routing, Middleware, and Dependency Injection
Part 5 of 19 · ~1 min
Controllers, [ApiController], and ControllerBase
ReservationsController above inherits from ControllerBase — not Controller, which pulls in view support for server-rendered pages that Bench, a pure JSON API, has no use for. ControllerBase is where Ok(), NotFound(), CreatedAtAction(), and access to HttpContext, User, and ModelState all come from.
[ApiController] is the other line doing real work on that class, and it's why ReservationsController above is shorter than it looks like it should be. It switches on four things at once:
- Automatic model validation — a
CreateReservationRequestthat fails a data-annotation rule (below) never reachesCreate's body at all; the framework hands back a400before your code runs. - Inferred binding sources —
toolIdandidcome from the route,requestfrom the body, with no[FromRoute]/[FromBody]spelled out anywhere (the exact inference rule is next). - Attribute routing is mandatory — Bench has no convention-based routing hiding in the background; every endpoint carries an attribute you can actually search for in the codebase.
- Automatic
ProblemDetailsresponses — a validation failure comes back as structuredapplication/problem+json, not a bare400with nothing explaining what went wrong.
Drop [ApiController] from a controller and none of the above is a compile error — it's just gone, silently, and you're back to writing the boilerplate it was doing for you. Theo actually did this once, copy-pasting a controller into a quick internal admin tool and forgetting the attribute; the giveaway wasn't a crash, it was validation that stopped firing and a [FromBody] that suddenly needed to be spelled out by hand.