An action method typically returns IActionResult, ActionResult<T>, or a bare value — each with different implications:
[HttpGet("{id}")]
public async Task<ActionResult<Order>> GetById(int id)
{
var order = await _orderService.GetByIdAsync(id);
if (order is null) return NotFound(); // 404, no body
return Ok(order); // 200, JSON body
}
ActionResult<T> lets a single method return either a concrete T (implicitly wrapped as a 200 with that body) or any IActionResult (like NotFound()) for the non-happy paths — cleaner than committing to one or the other across every branch.
| Helper | Status | Typical use |
|---|---|---|
Ok(value) | 200 | Successful GET/PUT with a body to return |
CreatedAtAction(...) | 201 | Successful POST — includes a Location header pointing at the new resource |
NoContent() | 204 | Successful action with nothing meaningful to return (a DELETE, or a PUT that doesn't echo the resource back) |
BadRequest(...) | 400 | The request itself is malformed or fails validation |
Unauthorized() | 401 | No valid credentials were presented at all |
Forbid() | 403 | Valid credentials, but insufficient permission for this action |
NotFound() | 404 | The requested resource doesn't exist |
Conflict(...) | 409 | The request conflicts with the current state of the resource (e.g. a duplicate key) |
CreatedAtAction(nameof(GetById), new { id = order.Id }, order) is worth understanding precisely: it generates the Location header by resolving the URL for the named action (GetById) with the given route values, rather than hardcoding a URL string — so it stays correct even if the route template for GetById later changes.