#csharp #dotnet #backend

Day 15: Introduction to Controllers

Welcome to Day 15. We are officially halfway through!

Minimal APIs are fantastic for microservices. But what if you are building a massive enterprise application with 500 endpoints? Putting them all in Program.cs becomes a nightmare.

This is where Controllers come in.

The Model-View-Controller (MVC) Pattern

In web APIs, we usually skip the “View” (leaving that to Angular/React/Astro), and just use Models and Controllers.

A Controller is a dedicated class that groups related endpoints and handles incoming HTTP requests.

Setting Up Controllers

To use Controllers, we must tell Program.cs to add the necessary services and map them to the routing pipeline.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// 1. Tell the builder we are using Controllers
builder.Services.AddControllers();

var app = builder.Build();

// 2. Map all endpoints found inside Controller classes!
app.MapControllers(); 

app.Run();

Creating a Controller

Create a dedicated Controllers folder in your project. Inside, create a file named ProductsController.cs.

Controllers inherit from ControllerBase and are decorated with routing attributes.

using Microsoft.AspNetCore.Mvc;

namespace MyApi.Controllers;

[ApiController] // Adds automatic validation and binding behaviors
[Route("api/[controller]")] // Automatically becomes route: "/api/products"
public class ProductsController : ControllerBase
{
    // A mock database
    private static List<string> _products = new() { "Laptop", "Mouse" };

    // GET: /api/products
    [HttpGet]
    public IActionResult GetAll() 
    {
        // Ok() is inherited from ControllerBase
        return Ok(_products); 
    }

    // GET: /api/products/1
    [HttpGet("{id}")]
    public IActionResult GetById(int id) 
    {
        if (id < 0 || id >= _products.Count)
            return NotFound();
            
        return Ok(_products[id]);
    }

    // POST: /api/products
    [HttpPost]
    public IActionResult Create([FromBody] string newProduct) 
    {
        _products.Add(newProduct);
        return Created($"/api/products/{_products.Count - 1}", newProduct);
    }
}

Why Controllers?

  1. Organization: All product-related logic lives in ProductsController.cs. User logic lives in UsersController.cs.
  2. Dependency Injection: Just like Minimal APIs, you can ask for services. But instead of cluttering your method signatures, you inject them into the Controller’s Constructor, giving all endpoints inside access to that service!
public class UsersController : ControllerBase 
{
    private readonly IEmailService _emailSvc;

    // Constructor Injection!
    public UsersController(IEmailService emailSvc) 
    {
        _emailSvc = emailSvc;
    }
    
    [HttpPost]
    public IActionResult Register() { ... }
}

Challenge for Day 15

Refactor your Minimal API from Day 12 (List<Product>) into a new InventoryController. Set up AddControllers() in your Program.cs and verify the endpoints still work in the browser!

Tomorrow: Entity Framework Core Setup!