Controller APIs, DTOs & Validation Masterclass
Welcome to Phase 12 (Chapter 34): C# ASP.NET Core Controller-Based APIs, DTOs, Routing & Validation Masterclass! Controller-based APIs organize endpoints inside classes that inherit from ControllerBase. In this chapter, we master controller routing with [ApiController] & [Route] attributes, HTTP verb attributes, IActionResult & ActionResult<T>, Data Transfer Objects (DTOs), model binding, data validation with Data Annotations, global exception handling, and CRUD operations with in-memory storage.
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")] // Route: /api/products
public class ProductsController : ControllerBase
{
private static List<ProductDto> _products = new()
{
new ProductDto(1, "Laptop", 75000m),
new ProductDto(2, "Mouse", 1200m)
};
// GET /api/products
[HttpGet]
public ActionResult<List<ProductDto>> GetAll() => Ok(_products);
// GET /api/products/1
[HttpGet("{id:int}")]
public ActionResult<ProductDto> GetById(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
return product is null ? NotFound($"Product {id} not found.") : Ok(product);
}
// POST /api/products
[HttpPost]
public ActionResult<ProductDto> Create([FromBody] ProductDto dto)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
_products.Add(dto);
return CreatedAtAction(nameof(GetById), new { id = dto.Id }, dto);
}
// PUT /api/products/1
[HttpPut("{id:int}")]
public IActionResult Update(int id, [FromBody] ProductDto dto)
{
var existing = _products.FirstOrDefault(p => p.Id == id);
if (existing is null) return NotFound();
_products[_products.IndexOf(existing)] = dto;
return NoContent();
}
// DELETE /api/products/1
[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product is null) return NotFound();
_products.Remove(product);
return NoContent();
}
}
// DTO using record with validation
public record ProductDto(
int Id,
[property: Required, MinLength(2)] string Name,
[property: Range(0.01, double.MaxValue)] decimal Price
);
Q1: What is the difference between IActionResult and ActionResult<T>?
IActionResult returns any HTTP response. ActionResult<T> additionally tells Swagger the exact response type, generating accurate API documentation automatically.
Q2: What does [ApiController] attribute do?
[ApiController] enables automatic model validation (returns 400 BadRequest when ModelState is invalid without manual checks), binds complex types from request body by default, and improves error response formatting.