Controllers & Actions
Mapping every route inline in Program.cs works for a handful of endpoints, but real APIs organize related endpoints into controllers — classes where each method is an action handling one route.
Defining a controller
A controller is a class inheriting from ControllerBase, decorated with attributes that set its base route and mark it as an API controller:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
var products = new[] { "Keyboard", "Mouse", "Monitor" };
return Ok(products);
}
}
$ curl http://localhost:5225/api/products ["Keyboard","Mouse","Monitor"]
[Route("api/[controller]")] uses the controller's name (minus "Controller") to build the base path — ProductsController becomes api/products automatically. [HttpGet] on the method maps it to GET requests on that base route. Ok(products) returns a 200 response with the value serialized to JSON.
Action methods with parameters
A route parameter in the method's own [HttpGet] attribute appends to the controller's base route:
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
if (id > 100)
return NotFound();
return Ok($"Product #{id}");
}
$ curl -i http://localhost:5225/api/products/5 HTTP/1.1 200 OK "Product #5" $ curl -i http://localhost:5225/api/products/999 HTTP/1.1 404 Not Found
NotFound() and Ok(...) are both helper methods on ControllerBase that build a proper IActionResult with the right status code — you're never manually setting Response.StatusCode for common cases like these.
[ApiController] isn't just decoration — it turns on automatic model-validation responses (a 400 with error details when binding fails, covered in the next lesson) and a few other API-specific conventions. Leaving it off a controller that's meant to be an API silently disables that behavior.