Skip to main content

Command Palette

Search for a command to run...

ASP.NET Core API Architecture: Controllers vs. Minimal APIs

Published
•5 min read•View as Markdown

1. Introduction

ASP.NET Core currently supports two primary paradigms for building RESTful Web APIs: the traditional Controller-based approach (MVC) and the modern Minimal API approach. While both ultimately run on top of the same ASP.NET Core Kestrel server and Middleware pipeline, their architectural implementation, lifecycle, and memory characteristics differ significantly.


2. The Controller Pattern (MVC)

The Enterprise Standard

The Controller pattern relies on Convention-over-Configuration. It uses classes, inheritance, and attributes to define behavior. It is designed for large-scale applications where strict structure is prioritized over raw throughput.

Usage

  • Definition: Classes inherit from ControllerBase.

  • Routing: Defined via attributes ([Route], [HttpGet]) or global conventions.

  • Binding: Complex model binding (Body, Form, Query, Route) happens automatically based on parameter names.

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _service;

    // Constructor Injection (Class-level dependency)
    public UsersController(IUserService service)
    {
        _service = service;
    }

    [HttpGet("{id}")]
    public IActionResult GetUser(int id) => Ok(_service.Get(id));
}

Architecture Under the Hood

  1. Discovery: At startup, the ApplicationPartManager scans all assemblies to build a catalog of ControllerActionDescriptor objects.

  2. Instantiation: For every HTTP request, the framework:

    • Allocates memory for the Controller class.

    • Resolves all constructor dependencies (even those not used by the specific method called).

    • Executes the Action Filter pipeline (Before/After execution).

    • Disposes the Controller instance.

Pros & Cons

ProsCons
Strict Structure: Forces organization (Controllers/Models folders). Easy for juniors to navigate.Performance Overhead: Instantiating a class per request adds GC pressure and CPU cycles.
Batteries Included: Built-in support for OData, Application Parts (Plugins), and Model Validation (ModelState)."Magic" Behavior: Relies heavily on implicit conventions which can be hard to debug (e.g., parameter binding rules).
Rich Filter Pipeline: Powerful IActionFilter, IExceptionFilter support for cross-cutting concerns.Verbose: Requires substantial boilerplate code (namespaces, classes, constructors).
View Support: The only option if you need to render server-side HTML (Razor Views).

3. The Minimal API Pattern

The Cloud-Native Standard

Minimal APIs rely on Configuration-over-Convention. They strip away the MVC layer, mapping routes directly to executing code delegates. It is designed for high-performance microservices and serverless functions.

Usage

  • Definition: Direct mapping on the WebApplication instance (app).

  • Routing: Explicit definition (app.MapGet). No auto-discovery.

  • Binding: Optimized parameter binding.

// Method Injection (Dependency injected only when function runs)
app.MapGet("/api/users/{id}", (int id, IUserService service) => 
{
    return Results.Ok(service.Get(id));
});

Architecture Under the Hood

  1. Discovery: Routes are explicitly registered into the EndpointDataSource. No scanning unless you implement a Module pattern manually.

  2. Instantiation:

    • The "handler" is compiled into a RequestDelegate (a function pointer) at startup.

    • No class is instantiated per request.

    • Dependencies are resolved strictly for the parameters defined in the lambda (Method Injection).

Pros & Cons

ProsCons
High Performance: Lower memory allocation and faster throughput (RPS) due to lack of class instantiation.Lack of Structure: By default, puts everything in Program.cs. Requires discipline to organize (e.g., Carter/Modules).
Low Boilerplate: Eliminates namespace/class wrappers. Ideal for simple CRUD.Limited Filters: "Endpoint Filters" exist but lack the granularity and lifecycle hooks of MVC filters.
Efficient DI: No "Over-fetching" of dependencies. You only instantiate what the specific endpoint needs.No HTML Views: Cannot render Razor pages (.cshtml). Purely for Data APIs (JSON).
Fast Startup: Critical for Serverless (AWS Lambda/Azure Functions) to reduce "Cold Start" times.

4. Engineering Guidelines: Designing the System

When designing a system using these patterns, Software Engineers must follow specific organizational principles to avoid technical debt.

A. Folder Structure: Vertical Slices (REPR Pattern)

Do not organize by "Layer" (Controllers, Models, Services). Organize by Feature. This applies to both patterns but is critical for Minimal APIs.

  • Bad (Horizontal): Changes require jumping between 3 folders.

    • /Controllers/UserController.cs

    • /Models/UserDto.cs

    • /Services/UserService.cs

  • Good (Vertical/REPR): Everything related to one feature is in one place.

    • /Features/Users/CreateUser/Endpoint.cs

    • /Features/Users/CreateUser/Request.cs

    • /Features/Users/CreateUser/Handler.cs

B. Dependency Injection Hygiene

  • Controller Risk: Constructor injection often leads to "Constructor Bloat" (controllers with 10+ dependencies). This indicates a violation of the Single Responsibility Principle (SRP).

  • Minimal API Risk: "Closure Capturing". Never resolve a service outside the lambda and use it inside.

    • Bad: var db = app.Services.GetService<Db>(); app.MapGet("/", () => db.Query()); (Creates a Singleton DB context).

    • Good: app.MapGet("/", (Db db) => db.Query()); (Correct Scoped injection).

C. Choosing the Right Tool

ScenarioRecommendationReason
Microservices / ServerlessMinimal APIStartup speed and low memory footprint save cloud costs.
High-Traffic Public APIMinimal APIRaw throughput (RPS) is maximized.
Complex Enterprise MonolithControllersRigid structure helps large teams maintain consistency; rich filter pipelines handle complex auth/audit rules.
Legacy MigrationControllersEasier to port existing .NET Framework WebAPI code to Core Controllers.
HTML/MVC AppControllersRequired for Razor Views.

5. Summary Verdict

  • Use Controllers if you are building a large, complex application with a team of varying skill levels and need strict guardrails, or if you serve HTML.

  • Use Minimal APIs if you are building microservices, high-performance APIs, or want to embrace modern .NET architectural patterns (Vertical Slices/Carter).

More from this blog

.

.Net Core

32 posts