Master ASP.NET Core routing: attribute routes, controller structure, model binding from multiple sources, and action result types.
ASP.NET Core routing maps incoming HTTP requests to controller actions using attribute routing. Controllers accept requests, bind model data from route segments, query strings, and request bodies, and return strongly typed action results that set status codes and response formats.
Attribute routing uses [HttpGet], [HttpPost] and route templates with token substitution:
1[ApiController]
2[Route("api/[controller]")] // api/products
3public class ProductsController : ControllerBase
4{
5 // GET api/products
6 [HttpGet]
7 public IActionResult GetAll() => Ok(products);
8
9 // GET api/products/5
10 [HttpGet("{id}")]
11 public IActionResult GetById(int id) => Ok(product);
12
13 // GET api/products/5/reviews
14 [HttpGet("{id}/reviews")]
15 public IActionResult GetReviews(int id) => Ok(reviews);
16
17 // POST api/products
18 [HttpPost]
19 public IActionResult Create([FromBody] ProductDto dto) => Created(...);
20
21 // PUT api/products/5
22 [HttpPut("{id}")]
23 public IActionResult Update(int id, [FromBody] ProductDto dto) => Ok(updated);
24
25 // DELETE api/products/5
26 [HttpDelete("{id}")]
27 public IActionResult Delete(int id) => NoContent();
28}
29
30// Route constraints
31[HttpGet("{id:int}")] // Only integers
32[HttpGet("{name:alpha}")] // Only letters
33[HttpGet("{slug:regex(^[a-z]+$)}")] // Regex match
34[HttpGet("{date:datetime}")] // Valid DateTime
35[HttpGet("{id:min(1):max(100)}")] // Range constraintControllers derive from ControllerBase and receive services via constructor injection:
1[ApiController]
2[Route("api/[controller]")]
3[Produces("application/json")]
4public class UsersController : ControllerBase
5{
6 private readonly IUserService _userService;
7 private readonly ILogger<UsersController> _logger;
8
9 public UsersController(IUserService userService, ILogger<UsersController> logger)
10 {
11 _userService = userService;
12 _logger = logger;
13 }
14
15 [HttpGet]
16 [ProducesResponseType(typeof(IEnumerable<UserDto>), 200)]
17 public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
18 {
19 var users = await _userService.GetAllAsync(page, pageSize);
20 return Ok(users);
21 }
22
23 [HttpGet("{id}")]
24 [ProducesResponseType(typeof(UserDto), 200)]
25 [ProducesResponseType(404)]
26 public async Task<IActionResult> GetById(int id)
27 {
28 var user = await _userService.GetByIdAsync(id);
29 if (user == null) return NotFound();
30 return Ok(user);
31 }
32
33 [HttpPost]
34 [ProducesResponseType(typeof(UserDto), 201)]
35 [ProducesResponseType(400)]
36 public async Task<IActionResult> Create([FromBody] CreateUserDto dto)
37 {
38 var user = await _userService.CreateAsync(dto);
39 return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
40 }
41}ASP.NET Core can bind action parameters from multiple sources using binding attributes:
1// Model binding sources
2[HttpGet("{id}")]
3public IActionResult Get(
4 [FromRoute] int id, // From URL path
5 [FromQuery] string filter, // From query string
6 [FromHeader] string apiKey, // From request headers
7 [FromBody] RequestDto body, // From request body
8 [FromServices] IMyService svc // From DI container
9) { }
10
11// Example: GET /api/products/5?category=electronics
12// Header: X-Api-Key: abc123
13[HttpGet("{id}")]
14public IActionResult GetProduct(
15 [FromRoute] int id,
16 [FromQuery] string category,
17 [FromHeader(Name = "X-Api-Key")] string apiKey)
18{
19 // id = 5
20 // category = "electronics"
21 // apiKey = "abc123"
22}
23
24// Complex query parameters
25public record ProductQuery(
26 string? Search,
27 string? Category,
28 decimal? MinPrice,
29 decimal? MaxPrice,
30 int Page = 1,
31 int PageSize = 20
32);
33
34[HttpGet]
35public IActionResult Search([FromQuery] ProductQuery query)
36{
37 // Automatically binds all query params to record
38}
39
40// Custom model binder
41public class CommaSeparatedModelBinder : IModelBinder
42{
43 public Task BindModelAsync(ModelBindingContext context)
44 {
45 var value = context.ValueProvider.GetValue(context.ModelName).FirstValue;
46 var result = value?.Split(',').Select(int.Parse).ToList();
47 context.Result = ModelBindingResult.Success(result);
48 return Task.CompletedTask;
49 }
50}Return semantic HTTP responses using typed IActionResult helpers:
1// Common action results
2[HttpGet]
3public IActionResult GetExample()
4{
5 // Success responses
6 return Ok(data); // 200 OK with body
7 return Created("/url", data); // 201 Created
8 return CreatedAtAction("Get", new { id = 1 }, data);
9 return NoContent(); // 204 No Content
10 return Accepted(); // 202 Accepted
11
12 // Client error responses
13 return BadRequest("Invalid input"); // 400 Bad Request
14 return BadRequest(ModelState); // 400 with validation errors
15 return Unauthorized(); // 401 Unauthorized
16 return Forbid(); // 403 Forbidden
17 return NotFound(); // 404 Not Found
18 return Conflict("Already exists"); // 409 Conflict
19
20 // Server errors
21 return StatusCode(500, "Error"); // 500 Internal Error
22
23 // Redirects
24 return RedirectToAction("Index");
25 return RedirectToRoute("routeName");
26
27 // Files
28 return File(bytes, "application/pdf", "file.pdf");
29 return PhysicalFile("/path/file.pdf", "application/pdf");
30}
31
32// Typed results with ActionResult<T>
33[HttpGet("{id}")]
34public async Task<ActionResult<ProductDto>> GetById(int id)
35{
36 var product = await _repo.GetByIdAsync(id);
37 if (product == null) return NotFound();
38 return product; // Implicitly Ok(product)
39}Your progress is saved in this browser, on this device only.