[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpGet("{id}")]
public async Task<ActionResult<Order>> GetById(int id)
{
var order = await _orderService.GetByIdAsync(id);
if (order is null) return NotFound();
return Ok(order);
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Order>>> GetAll([FromQuery] string? status)
{
var orders = await _orderService.GetAllAsync(status);
return Ok(orders);
}
[HttpPost]
public async Task<ActionResult<Order>> Create(CreateOrderRequest request)
{
var order = await _orderService.CreateAsync(request);
return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
}
}
[Route("api/[controller]")] maps this to api/orders (the [controller] token is replaced with the class name minus "Controller"). [HttpGet("{id}")] adds a route parameter, bound automatically to the id parameter by name; [HttpGet] with no template on GetAll matches the bare api/orders, and status arrives from a query string (?status=shipped) via [FromQuery].
Route templates support constraints, restricting what a segment is allowed to match before the framework even tries to invoke an action:
[HttpGet("{id:int}")] // only matches if {id} parses as an int — "abc" 404s instead of erroring inside the method
[HttpGet("{id:int:min(1)}")] // stacked constraints — int AND at least 1
[HttpGet("{slug:alpha}")] // only matches alphabetic characters
When two routes could both match the same URL, ASP.NET Core resolves ambiguity using route precedence — a literal segment beats a constrained parameter, which beats an unconstrained parameter, so api/orders/pending and api/orders/{id:int} don't conflict even though both could theoretically match a URL like api/orders/pending.