> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neuro-tech.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Controllers and routes

> Build endpoints with controller classes, route attributes, and route templates

## Start with a controller

Use `[Controller]` when you want a class to be discovered by `RegisterControllers(...)`, and use `[Route]` to define its base path.

```csharp theme={null}
[Controller]
[Route("/users")]
public sealed class UsersController : ControllerBase
{
    [HttpGet("")]
    public Task<List<UserSummary>> List()
    {
        return Task.FromResult(new List<UserSummary>
        {
            new UserSummary { UserId = "1", DisplayName = "Ada" },
            new UserSummary { UserId = "2", DisplayName = "Linus" }
        });
    }

    [HttpGet("/me")]
    public Task<UserSummary> GetCurrent()
    {
        return Task.FromResult(new UserSummary
        {
            UserId = "me",
            DisplayName = "Current user"
        });
    }

    [HttpGet("/[userId]")]
    public Task<UserSummary> Get(string UserId)
    {
        return Task.FromResult(new UserSummary
        {
            UserId = UserId,
            DisplayName = "Ada"
        });
    }
}

public sealed class UserSummary
{
    public string UserId { get; set; } = string.Empty;
    public string DisplayName { get; set; } = string.Empty;
}
```

The full routes become:

* `GET /api/users`
* `GET /api/users/me`
* `GET /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.

```csharp theme={null}
[Controller]
[Route("/parking-areas")]
public sealed class ParkingAreasController : ControllerBase
{
    [HttpGet("/")]
    public Task<List<ParkingAreaResponse>> List()
    {
        return Task.FromResult(new List<ParkingAreaResponse>());
    }
}

[Controller(Parent = typeof(ParkingAreasController))]
[Route("/[areaId]")]
public sealed class ParkingAreaController : ControllerBase
{
    [HttpGet("/")]
    public Task<ParkingAreaResponse> Get(Guid AreaId)
    {
        return Task.FromResult(new ParkingAreaResponse { Id = AreaId });
    }
}

[Controller(Parent = typeof(ParkingAreaController))]
[Route("/squares")]
public sealed class ParkingSquaresController : ControllerBase
{
    [HttpGet("/[squareId]")]
    public Task<ParkingSquareResponse> Get(Guid AreaId, Guid SquareId)
    {
        return Task.FromResult(new ParkingSquareResponse
        {
            Id = SquareId,
            ParkingAreaId = AreaId
        });
    }
}

public sealed class ParkingAreaResponse
{
    public Guid Id { get; set; }
}

public sealed class ParkingSquareResponse
{
    public Guid Id { get; set; }
    public Guid ParkingAreaId { get; set; }
}
```

Those routes become:

* `GET /api/parking-areas`
* `GET /api/parking-areas/[areaId]`
* `GET /api/parking-areas/[areaId]/squares/[squareId]`

Parent route parameters are part of the final route, so child actions can bind them like any other route parameter. In the example above, `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.

<Note>
  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.
</Note>

## HTTP method attributes

Use one of these on controller methods:

* `[HttpGet("...")]`
* `[HttpPost("...")]`
* `[HttpPut("...")]`
* `[HttpPatch("...")]`
* `[HttpDelete("...")]`

The controller route and method route are concatenated.

<Note>
  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(...)`.
</Note>

## Route template segments

* `users` — literal segment
* `[userId]` — named route parameter
* `*` — any single segment

```csharp theme={null}
[Controller]
[Route("/projects")]
public sealed class ProjectsController : ControllerBase
{
    [HttpGet("/[projectId]/files/*")]
    public Task<ProjectFileResponse> GetFile(string ProjectId)
    {
        return Task.FromResult(new ProjectFileResponse
        {
            ProjectId = ProjectId
        });
    }
}

public sealed class ProjectFileResponse
{
    public string ProjectId { get; set; } = string.Empty;
}
```

## 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.

```csharp theme={null}
string ProjectsRoute = ControllerBase.GetRoute<ProjectsController>();
```

That is useful when you want to compose links or manual subroutes around an attributed controller without hard-coding the same prefix twice.

## Controller lifecycle

The router creates a new controller instance per request:

* Request-specific state does not leak between requests
* `ControllerBase.Context` always belongs to the current request
* Do not access `ControllerBase.Context` in 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.

## What to read next

[Binding and responses](/neuron-development/first-party-packages/neuron-http-router/binding-and-responses) shows how route values, query strings, and JSON bodies are turned into controller method arguments.
