Start with a controller
Use[Controller] when you want a class to be discovered by RegisterControllers(...), and use [Route] to define its base path.
GET /api/usersGET /api/users/meGET /api/users/[userId]
Two registration paths
There are two ways to register attributed controllers:- Scan an assembly with
RegisterControllers(typeof(UsersController).Assembly) - Register a single controller type with
RegisterController<UsersController>()
[Controller] is required for assembly scanning. Direct RegisterController<T>() registration already names the controller type explicitly.
Parent controllers
Use[Controller(Parent = typeof(...))] when a controller should live under another controller’s route prefix.
Parent controllers are route containers. The router walks the parent chain, prepends each parent’s [Route], and then appends the child controller route and method route.
GET /api/parking-areasGET /api/parking-areas/[areaId]GET /api/parking-areas/[areaId]/squares/[squareId]
AreaId comes from the parent route segment and SquareId comes from the child method route.
Each type in the parent chain must be a controller with its own [Controller] and [Route]. Parent controllers can have actions of their own, or they can exist mainly to give nested resources a shared route prefix.
Controller-level middleware and authorization are registered on the route path for the controller that declares them. If a parent controller is registered with middleware on
/parking-areas, that middleware can run for deeper routes under /parking-areas/... because the request pipeline walks route-tree parents. Declare child-specific middleware or authorization on the child controller when it should only apply to that nested resource.HTTP method attributes
Use one of these on controller methods:[HttpGet("...")][HttpPost("...")][HttpPut("...")][HttpPatch("...")][HttpDelete("...")]
Attribute-routed controllers currently expose
GET, POST, PUT, PATCH, and DELETE. If you need a manual OPTIONS endpoint, register it directly on the router with RegisterOptions(...).Route template segments
users— literal segment[userId]— named route parameter*— any single segment
How parameter matching works
Route placeholder names are matched against method parameter names case-insensitively.[HttpGet("/[userId]")] can bind to string UserId.
Path matching is case-sensitive; route placeholder name matching is case-insensitive.
Reusing controller route prefixes
ControllerBase.GetRoute<T>() returns the route prefix declared on a controller type and caches it for reuse.
Controller lifecycle
The router creates a new controller instance per request:- Request-specific state does not leak between requests
ControllerBase.Contextalways belongs to the current request- Do not access
ControllerBase.Contextin the constructor
When to omit [Route]
If you omit [Route], the controller base path defaults to /. Most real applications are easier to read when each controller owns a clear path prefix.