Routing
Attribute Routing in Controllers
You can use attribute routing to map controller action methods to HTTP methods and routes.
A typical RESTful controller with all standard HTTP verbs looks like this:
[Route("api/products")]
[ApiController]
public class ProductsController : ControllerBase
{
// GET all: GET /api/products
[HttpGet]
public IActionResult GetAll() => Ok(repo.GetAll());
// GET by ID: GET /api/products/5
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
=> repo.Get(id) is { } prod ? Ok(prod) : NotFound();
// CREATE: POST /api/products
// Request body: JSON (sent as application/json)
// e.g. fetch('/api/products', {method:'POST',body:JSON.stringify(data),headers:{'Content-Type':'application/json'}})
[HttpPost]
public IActionResult Create([FromBody] ProductModel input)
{
var id = repo.Add(input);
return CreatedAtAction(nameof(GetById), new { id }, input);
}
// UPDATE: PUT /api/products/5
// Request body: JSON (full entity)
[HttpPut("{id:int}")]
public IActionResult Replace(int id, [FromBody] ProductModel input)
{
if (!repo.Exists(id)) return NotFound();
repo.Update(id, input);
return NoContent();
}
// PARTIAL UPDATE: PATCH /api/products/5
// Request body: JSON Patch (see RFC 6902)
[HttpPatch("{id:int}")]
public IActionResult PartialUpdate(int id, [FromBody] JsonPatchDocument<ProductModel> patchDoc)
{
var prod = repo.Get(id);
if (prod == null) return NotFound();
patchDoc.ApplyTo(prod, ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
repo.Update(id, prod);
return NoContent();
}
// DELETE: DELETE /api/products/5
[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
if (!repo.Exists(id)) return NotFound();
repo.Delete(id);
return NoContent();
}
// HEAD: HEAD /api/products/5
[HttpHead("{id:int}")]
public IActionResult Head(int id)
=> repo.Exists(id) ? Ok() : NotFound();
// SEARCH: GET /api/products/search/laptop
// Query param alternative: GET /api/products?term=laptop
[HttpGet("search/{term}")]
public IActionResult Search(string term)
=> Ok(repo.FindByName(term));
}
Routing in a Nutshell
[Route("api/products")]is the base URL for all actions.- Attributes like
[HttpGet("{id:int}")]map HTTP verbs and define route parameters (here, an integeridfrom the path). [FromBody]indicates data is expected in the request body (e.g., JSON).- Query parameters: For
/api/products?sort=name, add a method parameterstring sort. - Two actions can’t have the same route and verb (ambiguous).
Custom/Composite Routing Examples
You can mix-and-match route templates and HTTP methods for any scenario:
// Action bound to GET with custom route segment:
[HttpGet("details/{id:int}/{extra?}")]
public IActionResult Details(int id, string? extra) { ... }
// POST to a subresource
[HttpPost("{id}/comments")]
public IActionResult AddComment(int id, CommentDto dto) { ... }
// PUT by name (non-RESTful/cusom pattern)
[HttpPut("by-name/{name}")]
public IActionResult UpdateByName(string name, ProductModel input) { ... }
// Multiple verbs on one action:
[HttpPost, HttpPatch("partial")]
public IActionResult Upsert(ProductModel input) { ... }
Classic Routing Pipeline (For Context)
In Program.cs:
app.UseRouting();
app.UseEndpoints((endpoints) => {
endpoints.MapControllers(); // Enables attribute-routed controllers
});
For Minimal APIs (Not in controller), you’d use:
app.MapGet("/items", ...);
app.MapPost("/items", ...);
// etc.