Controller APIs, DTOs & Validation Masterclass

โšก C# 12 & .NET 8 ๐ŸŸข Chapter 34 of 35 ๐Ÿ“‚ Phase 12: ASP.NET Core Web APIs ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: Controllers ยท [ApiController] ยท Routing ยท HTTP Verbs ยท IActionResult ยท ActionResult ยท DTOs ยท Model Binding ยท Data Annotations Validation ยท CRUD

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.

1ControllerBase & Routing Attributes
C# โ€” Full CRUD Controller โ–ถ Run in Compiler
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
);
2Technical FAQs

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.