.NET Academy

Controllers and MVC

Organize APIs with controller classes — attribute routing, model binding, validation and filters.

12 min read

You will learn

  • Write an API controller with attribute routing
  • Use automatic model validation with [ApiController]
  • Apply filters for cross-cutting concerns
  • Choose between controllers and minimal APIs

The class-based style

Before minimal APIs, ASP.NET Core APIs were written with controllers: classes grouping related endpoints ("actions") as methods. You'll find them in most existing .NET codebases, and they remain fully supported.

builder.Services.AddControllers();
...
app.MapControllers();
[ApiController]
[Route("api/[controller]")]                       // → api/products
public class ProductsController(ShopDb db) : ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<ProductDto>> GetAll() =>
        await db.Products.Select(p => new ProductDto(p.Id, p.Name, p.Price)).ToListAsync();
 
    [HttpGet("{id:int}")]                         // → api/products/42
    public async Task<ActionResult<ProductDto>> Get(int id)
    {
        var p = await db.Products.FindAsync(id);
        if (p is null) return NotFound();
        return new ProductDto(p.Id, p.Name, p.Price);
    }
 
    [HttpPost]
    public async Task<ActionResult<ProductDto>> Create(CreateProductRequest request)
    {
        var product = new Product { Name = request.Name, Price = request.Price };
        db.Products.Add(product);
        await db.SaveChangesAsync();
        return CreatedAtAction(nameof(Get), new { id = product.Id }, new ProductDto(product.Id, product.Name, product.Price));
    }
 
    [HttpDelete("{id:int}")]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> Delete(int id) { ... return NoContent(); }
}
  • ControllerBase provides helpers: Ok(), NotFound(), BadRequest(), CreatedAtAction(), NoContent().
  • Attribute routing ([Route], [HttpGet("{id}")]) defines URLs right on the actions.
  • Dependencies arrive through the constructor (DI).
  • ActionResult<T> lets an action return either a value or a status result.

Model binding and validation

Parameters bind from route, query, body and headers just like minimal APIs. Add DataAnnotations to request models:

public class CreateProductRequest
{
    [Required, StringLength(200)]
    public string Name { get; set; } = "";
 
    [Range(0.01, 100_000)]
    public decimal Price { get; set; }
 
    [EmailAddress]
    public string? SupplierEmail { get; set; }
}

With [ApiController], invalid input never reaches your action: the framework responds 400 Bad Request with Problem Details listing every invalid field. For more complex rules, FluentValidation is a popular library.

Filters

Filters run around action execution — MVC's equivalent of middleware, but aware of controllers and actions:

FilterRuns
Authorization filtersFirst — can reject the request
Resource filtersBefore model binding (e.g. caching)
Action filtersBefore and after the action method
Exception filtersWhen an action throws
Result filtersBefore and after the result is written
public class TimingFilter(ILogger<TimingFilter> logger) : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var sw = Stopwatch.StartNew();
        await next();                                    // run the action
        logger.LogInformation("{Action} took {Ms} ms", context.ActionDescriptor.DisplayName, sw.ElapsedMilliseconds);
    }
}
 
builder.Services.AddControllers(o => o.Filters.Add<TimingFilter>());   // global

Minimal APIs have endpoint filters (.AddEndpointFilter(...)) for the same purpose.

MVC with views

The "V" in MVC is for views: controllers can return HTML rendered with Razor templates (return View(model);). For new server-rendered pages, Razor Pages or Blazor (later in this track) are usually simpler — but MVC views are common in existing apps.

Controllers or minimal APIs?

Minimal APIsControllers
StyleFunctions mapped to routesClasses with action methods
CeremonyLowHigher
PerformanceSlightly fasterVery fast
FeaturesEndpoint filters, typed results, groupsFilters pipeline, model-binding extensibility, conventions, views
Common inNew projects, microservicesExisting and large codebases

Both use the same routing, DI, authentication and serialization. Choose one style per project for consistency.

Key takeaways

  • Controllers group actions in classes deriving from ControllerBase, with attribute routing.
  • [ApiController] adds automatic 400 responses for invalid models (DataAnnotations).
  • Filters add cross-cutting behavior around actions.
  • Minimal APIs and controllers are both production-ready; pick one style per project.

Quiz

4 questions · 3 correct to pass
  1. 1.What does the [ApiController] attribute add?

  2. 2.In `[Route("api/[controller]")]` on ProductsController, what's the base route?

  3. 3.What is an action filter useful for?

  4. 4.Should a new API use controllers or minimal APIs?

To complete this lesson

  • ○ Pass the quiz (3/4 correct)