LEARN BUILD GROW TOGETHER
Practical knowledge for developers
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Showing posts with label Minimal APIs. Show all posts

Routing and Route Parameters in .NET Core Web API with Practical C# Examples

 

Introduction

Whenever a client sends an HTTP request to a .NET Core Web API, the application needs to identify which endpoint should process that request.

For example, imagine your Employee Management application has the following requirements:

  • Retrieve all employees.

  • Retrieve an employee using their ID.

  • Retrieve employees belonging to a particular department.

  • Search employees using their name.

  • Retrieve employees based on department and location.

How does the API distinguish between these requests?

This is where Routing comes into the picture.

Routing is the mechanism that matches an incoming HTTP request to an endpoint based on information such as the URL and HTTP method.

In this article, we'll understand routing step by step and implement practical examples using Controllers and Minimal APIs.

1. What is Routing in .NET Core Web API?

Routing is the process of matching an incoming HTTP request to an appropriate endpoint.

An endpoint is the destination in your application that handles a particular request.

For example:

GET /api/employees
GET /api/employees/101
GET /api/employees/department/IT

Although all three requests belong to the Employee Management API, they perform different operations.

The routing system identifies the matching endpoint and forwards the request for execution.

Consider this simple controller:

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    [HttpGet]
    public IActionResult GetEmployees()
    {
        return Ok("Get all employees");
    }

    [HttpGet("{id:int}")]
    public IActionResult GetEmployeeById(int id)
    {
        return Ok($"Get employee with ID: {id}");
    }
}

Here, we have defined two endpoints:

HTTP methodURLAction
GET/api/employeesGetEmployees
GET/api/employees/101GetEmployeeById

When a client requests /api/employees/101, routing matches the second action and provides 101 as the id parameter.

2. Understanding Attribute Routing

Attribute routing allows you to define routes directly on controllers and their action methods using attributes.

It is commonly used in controller-based Web APIs.

Let's understand the two important routing attributes.

A. Route attribute

The [Route] attribute defines the route pattern for a controller or action.

Example:

[Route("api/employees")]
public class EmployeesController : ControllerBase
{
}

Now the controller's base route is:

/api/employees

You can also use a controller token:

[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
}

Here, [controller] is replaced with the controller name without the Controller suffix.

Therefore, the resulting route is:

/api/employees

B. HTTP method attributes

HTTP method attributes define which HTTP method an action accepts.

Common attributes include:

[HttpGet]
[HttpPost]
[HttpPut]
[HttpPatch]
[HttpDelete]

For example:

[HttpGet]
public IActionResult GetEmployees()
{
    return Ok("Employee list");
}

[HttpPost]
public IActionResult CreateEmployee()
{
    return Ok("Employee created");
}

Both actions use the same route, but they handle different HTTP methods.

This is useful when designing RESTful APIs.

3. What are Route Parameters?

Route parameters allow you to pass dynamic values through the URL.

Suppose you want to retrieve an employee with ID 101.

Instead of creating a separate endpoint for every employee, you can define a dynamic route:

[HttpGet("{id}")]
public IActionResult GetEmployeeById(int id)
{
    return Ok($"Employee ID: {id}");
}

Now the same endpoint can handle:

GET /api/employees/101
GET /api/employees/102
GET /api/employees/103

The framework extracts the value from the URL and binds it to the id parameter.

For example:

GET /api/employees/101

The action receives:

id = 101;

This is called route parameter binding.

Multiple route parameters

You can also define multiple parameters in a single route.

For example:

[HttpGet("{department}/{location}")]
public IActionResult GetEmployeesByDepartmentAndLocation(
    string department,
    string location)
{
    return Ok(new
    {
        Department = department,
        Location = location
    });
}

Request:

GET /api/employees/IT/Delhi

The framework binds:

department = IT
location   = Delhi

Expected response:

{
  "department": "IT",
  "location": "Delhi"
}

This approach is useful when the URL structure naturally identifies the requested resource or its hierarchy.

4. What are Route Constraints?

Sometimes you want a route parameter to accept only a particular type of value.

For example, an employee ID should be an integer.

Without a route constraint:

[HttpGet("{id}")]
public IActionResult GetEmployee(int id)
{
    return Ok(id);
}

You can explicitly define the expected parameter type using a route constraint.

Example:

[HttpGet("{id:int}")]
public IActionResult GetEmployee(int id)
{
    return Ok($"Employee ID: {id}");
}

The :int constraint ensures that the route matches only when the supplied value is an integer.

For example:

GET /api/employees/101

Matches the route.

But:

GET /api/employees/abc

Does not match this integer-constrained route.

If no other endpoint matches, the application returns a 404 Not Found response.

Common route constraints

ConstraintPurposeExample
intInteger value{id:int}
longLong integer{id:long}
guidGUID value{id:guid}
boolBoolean value{active:bool}
alphaAlphabetic characters{name:alpha}
min(value)Minimum numeric value{age:min(18)}
max(value)Maximum numeric value{age:max(60)}
range(min,max)Numeric range{age:range(18,60)}
minlength(value)Minimum string length{name:minlength(3)}
maxlength(value)Maximum string length{name:maxlength(20)}

Practical example

[HttpGet("employee/{id:guid}")]
public IActionResult GetEmployeeByGuid(Guid id)
{
    return Ok(new
    {
        EmployeeId = id
    });
}

This endpoint matches GUID values such as:

/api/employees/employee/550e8400-e29b-41d4-a716-446655440000

Route constraints are useful for selecting the correct endpoint based on the URL structure and expected value format.

Important: Route constraints are not a replacement for business validation. For example, {age:int} checks that the value is an integer, but it does not establish whether that age is valid for your business rules.

5. What are Optional Route Parameters?

Sometimes a route parameter should be optional.

For example, you may want to retrieve all employees when no department is supplied, or retrieve employees belonging to a particular department when one is provided.

You can use ? to define an optional route parameter.

Example:

[HttpGet("department/{departmentName?}")]
public IActionResult GetEmployeesByDepartment(string? departmentName)
{
    if (string.IsNullOrWhiteSpace(departmentName))
    {
        return Ok("Returning all employees");
    }

    return Ok($"Returning employees from {departmentName}");
}

Now consider these requests:

Request 1:

GET /api/employees/department

Expected result:

Returning all employees

Request 2:

GET /api/employees/department/IT

Expected result:

Returning employees from IT

The ? makes the route segment optional, while string? indicates that the parameter may be null.

In real applications, you should clearly define what happens when an optional value is missing.

6. What are Query String Parameters?

Query string parameters are values passed in the URL after the ? character.

They are commonly used for filtering, searching, sorting, and pagination.

For example:

GET /api/employees?department=IT

Here:

  • department is the query parameter.

  • IT is its value.

Unlike route parameters, query parameters are not part of the route path.

Practical example

[HttpGet("search")]
public IActionResult SearchEmployees([FromQuery] string department)
{
    return Ok(new
    {
        Department = department
    });
}

Request:

GET /api/employees/search?department=IT

The framework binds IT to the department parameter.

Multiple query parameters

You can also send multiple query parameters.

Example:

GET /api/employees/search?department=IT&location=Delhi

Controller action:

[HttpGet("search")]
public IActionResult SearchEmployees(
    [FromQuery] string department,
    [FromQuery] string location)
{
    return Ok(new
    {
        Department = department,
        Location = location
    });
}

Expected response:

{
  "department": "IT",
  "location": "Delhi"
}

Route parameters vs Query parameters

Both are useful, but they serve different purposes.

ComparisonRoute parametersQuery parameters
LocationInside URL pathAfter ?
Common purposeIdentify a resourceFilter or search resources
Example/employees/101/employees?department=IT
Required by routeDepending on route definitionUsually optional
Multiple valuesDefined as route segmentsJoined using &

Practical recommendation:

Use route parameters when identifying a specific resource.

Use query parameters when filtering or modifying how a collection is retrieved.

For example:

GET /api/employees/101
GET /api/employees?department=IT
GET /api/employees?page=1&pageSize=10

7. Routing in Minimal APIs

So far, we have explored routing through Controllers.

Minimal APIs also support routing, but instead of using controller attributes, they use endpoint mapping methods.

For example:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Get all employees
app.MapGet("/api/employees", () =>
{
    return Results.Ok("All employees");
});

// Get employee by ID
app.MapGet("/api/employees/{id:int}", (int id) =>
{
    return Results.Ok($"Employee ID: {id}");
});

// Get employees by department
app.MapGet("/api/employees/department/{department}", 
    (string department) =>
{
    return Results.Ok($"Department: {department}");
});

// Search employees using query parameters
app.MapGet("/api/employees/search", 
    (string department, string location) =>
{
    return Results.Ok(new
    {
        Department = department,
        Location = location
    });
});

app.Run();

Let's understand these examples.

Example 1:

app.MapGet("/api/employees", ...);

Maps a GET request to the employee collection.

Example 2:

app.MapGet("/api/employees/{id:int}", ...);

Maps a GET request with an integer route parameter.

Example 3:

app.MapGet("/api/employees/department/{department}", ...);

Maps a GET request with a department route parameter.

Example 4:

app.MapGet("/api/employees/search",
    (string department, string location) => ...);

The department and location values are inferred from query parameters because they are not route parameters or otherwise supplied from another binding source.

Request:

GET /api/employees/search?department=IT&location=Delhi

Minimal APIs provide a straightforward way to define route patterns and bind incoming values to endpoint parameters.

8. Route Groups in Minimal APIs

As the number of endpoints increases, you may notice that many endpoints share the same URL prefix.

For example:

/api/employees
/api/employees/101
/api/employees/department/IT

Instead of repeating the common prefix, Minimal APIs provide MapGroup().

Example:

var employeeGroup = app.MapGroup("/api/employees");

employeeGroup.MapGet("/", () =>
{
    return Results.Ok("All employees");
});

employeeGroup.MapGet("/{id:int}", (int id) =>
{
    return Results.Ok($"Employee ID: {id}");
});

employeeGroup.MapGet("/department/{department}", 
    (string department) =>
{
    return Results.Ok($"Department: {department}");
});

All endpoints share the /api/employees prefix.

This makes endpoint organization easier, especially when an application has multiple API modules.

Route groups can also be used to apply shared endpoint configuration, such as authorization requirements, to a group of endpoints.

9. How does .NET Core Web API select the correct route?

Imagine you have the following endpoints:

[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
    return Ok($"Employee ID: {id}");
}

[HttpGet("search")]
public IActionResult SearchEmployees()
{
    return Ok("Search employees");
}

Now consider these requests:

GET /api/employees/101
GET /api/employees/search

The routing system considers the available route patterns and their constraints to identify the appropriate endpoint.

The first request matches the integer route.

The second request matches the literal search route.

Endpoint selection is based on route matching, HTTP method, constraints, and endpoint metadata—not simply on the order in which controller methods appear in the file.

If no suitable endpoint matches, the application generally returns 404 Not Found.

If a path matches an endpoint but the HTTP method is not supported, the application can return 405 Method Not Allowed.

This distinction is useful when debugging routing problems.

10. Common routing mistakes developers should avoid

1. Forgetting the controller route prefix

If your controller has:

[Route("api/[controller]")]

Remember that the resulting URL includes the api prefix and controller name.

For EmployeesController, the route starts with /api/employees.

2. Using route parameters when query parameters are more suitable

For filtering a collection, a query string is often clearer:

GET /api/employees?department=IT

Instead of creating a separate route for every possible filter.

3. Forgetting route constraints

If an endpoint expects an integer ID, adding {id:int} makes the intended route requirement explicit.

4. Confusing route parameters with query parameters

These are different request values:

GET /api/employees/101
GET /api/employees?id=101

The first uses a route parameter. The second uses a query parameter.

5. Creating ambiguous routes

Avoid defining multiple endpoints with overlapping route patterns and the same HTTP method if the framework cannot distinguish them.

Design routes with clear and predictable patterns.

6. Using route constraints as business validation

An integer constraint verifies the URL value's format. It does not verify whether an employee exists or whether the value satisfies business rules.

11. Interview questions

Q1. What is routing in .NET Core Web API?

Routing is the process of matching incoming HTTP requests to the appropriate endpoints based on information such as the URL and HTTP method.

Q2. What is attribute routing?

Attribute routing allows developers to define routes directly on controllers and action methods using attributes such as [Route], [HttpGet], and [HttpPost].

Q3. What is the difference between route parameters and query parameters?

Route parameters are part of the URL path and are commonly used to identify resources. Query parameters appear after the ? character and are commonly used for filtering, searching, and pagination.

Q4. What is a route constraint?

A route constraint restricts the values that can match a route parameter. For example, {id:int} matches integer values.

Q5. How do you define an optional route parameter?

You can use ? in the route template.

Example:

[HttpGet("department/{name?}")]

Q6. What is the purpose of [FromQuery]?

[FromQuery] explicitly tells the framework to bind a parameter from the query string.

Q7. What is the purpose of [controller] in routing?

It is a route token that is replaced with the controller name without the Controller suffix.

Q8. What is MapGroup() in Minimal APIs?

MapGroup() allows related endpoints to share a common route prefix and endpoint configuration.

Q9. What is the difference between 404 and 405?

404 Not Found indicates that no matching resource or endpoint was found. 405 Method Not Allowed indicates that the target resource does not support the requested HTTP method.

Q10. Can we use both route parameters and query parameters in the same endpoint?

Yes. For example:

GET /api/employees/101?includeDepartment=true

Here, 101 identifies the employee, while includeDepartment is a query parameter.

Conclusion

Routing is one of the fundamental concepts in .NET Core Web API development.

It helps the application identify the correct endpoint for every incoming request.

In this article, we explored:

  • Attribute routing

  • Route parameters

  • Multiple route parameters

  • Route constraints

  • Optional route parameters

  • Query string parameters

  • Routing in Minimal APIs

  • Route groups

We also learned how route parameters and query parameters are used for different purposes when designing API URLs.

Understanding these concepts will help you create clean, predictable, and maintainable API endpoints.

What's next?

In Day 6, we'll move from individual routing concepts to practical implementation.

We'll Build Your First .NET Core Web API step by step, create a new project, configure the application, implement employee endpoints, run the API, and test it using an API client.

We'll bring together the concepts we've learned so far and build our first complete working API project.


Skills Tutorials – Learn • Build • Grow Together

Continue learning with our 30-day .NET Core Web API series.

Minimal APIs vs Controllers in .NET Core Web API: Differences with Practical Examples

 

Introduction

When building a .NET Core Web API, one important architectural decision is how you want to define and organize your API endpoints.

.NET provides two commonly used approaches:

  1. Minimal APIs

  2. Controller-based APIs

Both approaches allow you to create HTTP endpoints, receive requests, execute application logic, and return responses to clients.

But their programming styles and organization are different.

Imagine you are developing an Employee Management application. You need endpoints to retrieve employees, create new employees, update employee details, and delete employee records.

Should you implement these endpoints directly in Program.cs using Minimal APIs, or should you create an EmployeesController?

Let's understand both approaches with practical C# examples.

1. What are Minimal APIs?

Minimal APIs were introduced in .NET 6 to provide a simplified way of building HTTP APIs with less boilerplate code.

Instead of creating separate controller classes with attributes, you can define API endpoints directly using methods such as:

  • MapGet()

  • MapPost()

  • MapPut()

  • MapPatch()

  • MapDelete()

These methods map incoming HTTP requests to the code responsible for processing them.

Minimal APIs are particularly useful for lightweight services, small applications, microservices, and APIs where a straightforward endpoint-based approach is sufficient.

Example: Creating an API using Minimal APIs

Create a new Web API project using the following command:

dotnet new web -n EmployeeMinimalApi

Open Program.cs.

Replace its contents with:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

var employees = new List<Employee>
{
    new Employee(101, "Rahul", "IT"),
    new Employee(102, "Priya", "HR")
};

// Get all employees
app.MapGet("/api/employees", () =>
{
    return Results.Ok(employees);
});

// Get employee by ID
app.MapGet("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    return employee is not null
        ? Results.Ok(employee)
        : Results.NotFound();
});

// Create employee
app.MapPost("/api/employees", (Employee employee) =>
{
    var newId = employees.Count == 0
        ? 1
        : employees.Max(x => x.Id) + 1;

    var newEmployee = employee with { Id = newId };

    employees.Add(newEmployee);

    return Results.Created(
        $"/api/employees/{newEmployee.Id}",
        newEmployee);
});

// Update employee
app.MapPut("/api/employees/{id:int}",
    (int id, Employee updatedEmployee) =>
{
    var index = employees.FindIndex(x => x.Id == id);

    if (index == -1)
    {
        return Results.NotFound();
    }

    employees[index] = updatedEmployee with { Id = id };

    return Results.Ok(employees[index]);
});

// Delete employee
app.MapDelete("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    if (employee is null)
    {
        return Results.NotFound();
    }

    employees.Remove(employee);

    return Results.NoContent();
});

app.Run();

public record Employee(int Id, string Name, string Department);

Let's understand what is happening here.

Step 1: Create the application

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

This creates and builds the web application.

Step 2: Define the endpoints

app.MapGet("/api/employees", () =>
{
    return Results.Ok(employees);
});

This maps an HTTP GET request to /api/employees.

When a client calls this endpoint, the application returns the employee collection.

Step 3: Handle route parameters

app.MapGet("/api/employees/{id:int}", (int id) =>
{
    var employee = employees.FirstOrDefault(x => x.Id == id);

    return employee is not null
        ? Results.Ok(employee)
        : Results.NotFound();
});

Here, the employee ID comes from the URL and is provided to the endpoint handler.

Step 4: Return HTTP responses

Minimal APIs provide the Results class to create HTTP responses.

Examples:

Results.Ok(data);
Results.Created(location, data);
Results.NotFound();
Results.NoContent();

These results represent HTTP responses such as 200, 201, 404, and 204.

Testing Minimal API endpoints

Run your application and test the following requests using Postman.

HTTP methodEndpointPurpose
GET/api/employeesGet all employees
GET/api/employees/101Get employee by ID
POST/api/employeesCreate employee
PUT/api/employees/101Update employee
DELETE/api/employees/101Delete employee

For POST, use this JSON request body:

{
  "id": 0,
  "name": "Amit",
  "department": "Finance"
}

The API assigns the new ID.

The example uses an in-memory collection, so records are not permanently stored. Also, the PATCH operation is not implemented in this particular example; it can be added using MapPatch().

2. What are Controller-based APIs?

Controller-based APIs use controller classes to organize HTTP endpoints.

A controller is a class that typically inherits from ControllerBase and contains action methods that handle incoming requests.

Instead of defining all endpoints in Program.cs, you organize them into separate controller classes.

For example:

EmployeeManagement.Api
│
├── Controllers
│   └── EmployeesController.cs
│
├── Models
│   └── Employee.cs
│
└── Program.cs

This approach is commonly used in enterprise applications where APIs contain multiple endpoints and require clear separation of responsibilities.

Example: Creating an API using Controllers

Create a new Web API project:

dotnet new webapi --use-controllers -n EmployeeControllerApi

Open Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.MapControllers();

app.Run();

Here:

  • AddControllers() registers the controller services.

  • MapControllers() maps controller endpoints into the application's routing system.

Now create a folder named Controllers and add EmployeesController.cs.

using Microsoft.AspNetCore.Mvc;

namespace EmployeeControllerApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private static readonly List<Employee> Employees =
    [
        new Employee
        {
            Id = 101,
            Name = "Rahul",
            Department = "IT"
        },
        new Employee
        {
            Id = 102,
            Name = "Priya",
            Department = "HR"
        }
    ];

    [HttpGet]
    public IActionResult GetEmployees()
    {
        return Ok(Employees);
    }

    [HttpGet("{id:int}")]
    public IActionResult GetEmployeeById(int id)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        return Ok(employee);
    }

    [HttpPost]
    public IActionResult CreateEmployee(Employee employee)
    {
        employee.Id = Employees.Count == 0
            ? 1
            : Employees.Max(x => x.Id) + 1;

        Employees.Add(employee);

        return CreatedAtAction(
            nameof(GetEmployeeById),
            new { id = employee.Id },
            employee);
    }

    [HttpPut("{id:int}")]
    public IActionResult UpdateEmployee(
        int id,
        Employee updatedEmployee)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        employee.Name = updatedEmployee.Name;
        employee.Department = updatedEmployee.Department;

        return Ok(employee);
    }

    [HttpDelete("{id:int}")]
    public IActionResult DeleteEmployee(int id)
    {
        var employee = Employees.FirstOrDefault(x => x.Id == id);

        if (employee is null)
        {
            return NotFound();
        }

        Employees.Remove(employee);

        return NoContent();
    }
}

Create the Employee.cs model:

namespace EmployeeControllerApi;

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Department { get; set; } = string.Empty;
}

Understanding the controller code

Let's understand the important parts.

1. Controller declaration

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
  • [ApiController] enables API-specific conventions and behaviors, including automatic model validation responses in supported scenarios.

  • [Route] defines the route pattern.

  • [controller] is replaced by the controller name without the Controller suffix.

  • ControllerBase provides functionality commonly needed when building APIs.

Therefore, the base route becomes:

/api/employees

2. HTTP GET action

[HttpGet]
public IActionResult GetEmployees()
{
    return Ok(Employees);
}

The [HttpGet] attribute maps GET requests to this action.

3. HTTP POST action

[HttpPost]
public IActionResult CreateEmployee(Employee employee)
{
    // Create employee
}

The incoming request body is bound to the Employee parameter.

4. HTTP PUT action

[HttpPut("{id:int}")]
public IActionResult UpdateEmployee(int id, Employee updatedEmployee)

This action accepts the employee ID from the URL and employee information from the request body.

5. HTTP DELETE action

[HttpDelete("{id:int}")]
public IActionResult DeleteEmployee(int id)

This action identifies the employee using the route ID and removes the corresponding record.

The controller approach organizes multiple endpoints within a dedicated class rather than placing all endpoint definitions inside Program.cs.

3. Minimal APIs vs Controllers: Key differences

Now that we have implemented both approaches, let's compare them.

FeatureMinimal APIsControllers
Programming styleEndpoint-basedClass and action-based
Code structureCan be compactUses controller classes
RoutingMapGet(), MapPost() etc.Routing attributes
Request handlingEndpoint handlersController action methods
HTTP responsesResults and typed resultsIActionResult, ActionResult<T>
OrganizationFlexible; can use route groups and extension methodsNaturally organized by controllers
FiltersEndpoint filtersMVC filters and other MVC extensibility
Model validationCan be implemented using endpoint logic, validation libraries, and framework features[ApiController] provides automatic model validation responses for invalid models
Dependency injectionSupportedSupported
Suitable forLightweight endpoints and focused servicesStructured APIs and controller-oriented applications

Important: Both approaches support routing, dependency injection, authentication, authorization, and other application capabilities. The choice is mainly about programming style, organization, and the features your application needs.

4. Advantages of Minimal APIs

Minimal APIs provide several benefits.

Less boilerplate code

You can create endpoints without defining a separate controller class for every API group.

Simple endpoint definitions

HTTP methods and their handlers can be defined directly using methods such as MapGet() and MapPost().

Useful for small services

When an application has a limited number of endpoints, Minimal APIs can provide a straightforward implementation.

Support for route groups

You can group related endpoints together and apply shared route prefixes or endpoint configuration.

For example:

var employeeGroup = app.MapGroup("/api/employees");

employeeGroup.MapGet("/", () => "Get employees");

employeeGroup.MapGet("/{id:int}", (int id) =>
    $"Get employee {id}");

Both endpoints share the /api/employees route prefix.

This helps organize Minimal APIs as the number of endpoints grows.

5. Advantages of Controller-based APIs

Controllers are useful when you want an organized structure for larger applications.

Clear separation of endpoints

Related API operations can be grouped inside a controller class.

For example:

Controllers
│
├── EmployeesController.cs
├── DepartmentsController.cs
├── ProductsController.cs
└── OrdersController.cs

Attribute-based routing

You can define routes using attributes such as:

[HttpGet]
[HttpPost]
[HttpPut("{id}")]
[HttpDelete("{id}")]

MVC features

Controller-based APIs support MVC features such as:

  • Action filters

  • Model binding

  • Model validation

  • Controller conventions

  • MVC-specific extensibility

Familiar structure for enterprise applications

Teams already working with MVC and controller-based architectures may find this approach easier to maintain and extend.

6. Can Minimal APIs support Dependency Injection?

Yes. Both Minimal APIs and Controllers support Dependency Injection.

Dependency Injection allows you to provide required services to application components rather than creating those dependencies manually everywhere.

For example, imagine you have an IEmployeeService.

First, register the service:

builder.Services.AddScoped<IEmployeeService, EmployeeService>();

In a Minimal API, you can inject it into an endpoint handler:

app.MapGet("/api/employees",
    (IEmployeeService employeeService) =>
{
    return employeeService.GetAllEmployees();
});

In a controller, you can inject it through the constructor:

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeService _employeeService;

    public EmployeesController(
        IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet]
    public IActionResult GetEmployees()
    {
        return Ok(_employeeService.GetAllEmployees());
    }
}

The important point is that Dependency Injection is not exclusive to either approach.

We'll explore Dependency Injection in detail in Day 8 of this series.

7. When should you use Minimal APIs?

Minimal APIs are a good choice when:

  • You are building a small API service.

  • You need a limited number of HTTP endpoints.

  • You are developing lightweight microservices.

  • You want a straightforward endpoint-based programming model.

  • Your team prefers explicit endpoint registration and organization.

For example, imagine an internal service that exposes only a few endpoints to retrieve application configuration or check service health.

Minimal APIs may be a suitable choice.

However, Minimal APIs can also be used in larger applications. You can organize them with route groups, extension methods, and separate endpoint-registration files.

8. When should you use Controllers?

Controllers are a good choice when:

  • Your application has many related API endpoints.

  • You prefer organizing endpoints into controller classes.

  • You need MVC-specific features such as action filters.

  • Your team already follows a controller-based architecture.

  • You want a familiar structure for a larger API codebase.

For example, an Employee Management application may eventually contain:

  • Employee management

  • Department management

  • Attendance management

  • Payroll management

  • Reporting

  • Role-based access control

A controller-based structure can make it easier to organize these related areas.

Again, this does not mean Minimal APIs cannot handle such an application. Both approaches can be used with a well-designed architecture.

9. Which approach is better: Minimal APIs or Controllers?

There is no single answer that applies to every application.

The decision depends on the application requirements, team experience, architecture, and the framework features you need.

Consider this practical comparison:

Scenario 1: Small internal service

You need five endpoints to expose application health and configuration information.

Minimal APIs may be a convenient choice because they allow you to define the endpoints with relatively little setup.

Scenario 2: Large Employee Management system

You have multiple business modules, several developers, complex request handling, and MVC-specific requirements.

Controllers may provide a more familiar organizational structure.

Scenario 3: Existing controller-based application

Your organization already has a controller-based Web API with established conventions and reusable components.

Continuing with controllers may be the most practical decision. You do not need to rewrite the application simply because Minimal APIs are available.

My recommendation: Choose the approach based on actual requirements, not just the amount of code. A maintainable API with clear responsibilities is more important than choosing the approach with the fewest lines.

10. Important points developers should remember

Before moving forward, keep these points in mind:

  1. Minimal APIs were introduced in .NET 6.

  2. Both Minimal APIs and Controllers can build HTTP APIs.

  3. Both approaches support Dependency Injection.

  4. Minimal APIs use endpoint mapping methods such as MapGet() and MapPost().

  5. Controllers use controller classes, action methods, and routing attributes.

  6. Minimal APIs support route groups and endpoint filters.

  7. Controllers support MVC features such as action filters and controller conventions.

  8. Neither approach automatically makes your API secure or production-ready. You must configure the appropriate security, validation, error handling, and monitoring features.

11. Interview questions

Q1. What are Minimal APIs in .NET?

Minimal APIs provide a simplified way to build HTTP APIs by defining endpoints directly using methods such as MapGet(), MapPost(), MapPut(), and MapDelete().

Q2. In which .NET version were Minimal APIs introduced?

Minimal APIs were introduced in .NET 6.

Q3. What is the difference between Minimal APIs and Controllers?

Minimal APIs define endpoints using endpoint mapping methods, while Controllers organize API endpoints inside classes containing action methods.

Q4. Can we use Dependency Injection in Minimal APIs?

Yes. Minimal APIs support Dependency Injection. Registered services can be injected directly into endpoint handlers.

Q5. Do Minimal APIs support authentication and authorization?

Yes. Minimal APIs support authentication and authorization through the ASP.NET Core authentication and authorization infrastructure.

Q6. What is MapGet()?

MapGet() maps an HTTP GET request to an endpoint handler.

Q7. What is MapControllers()?

MapControllers() maps controller action endpoints into the application's routing system.

Q8. Can Minimal APIs be used for large applications?

Yes. Minimal APIs can be organized using route groups, extension methods, and separate endpoint-registration files. They are not limited to small applications.

Q9. Can we use Minimal APIs and Controllers in the same project?

Yes. A .NET application can contain both Minimal API endpoints and controller-based endpoints.

Q10. Which approach is faster?

Performance depends on the actual application, workload, configuration, and implementation. You should benchmark your application's real requirements rather than assuming one approach is always faster.

Conclusion

Minimal APIs and Controller-based APIs are two supported approaches for building modern .NET Core Web APIs.

Minimal APIs provide a straightforward endpoint-based programming model, while Controllers provide a class-based structure with MVC-specific features.

In this article, we implemented the same Employee Management functionality using both approaches and compared their structure, advantages, and use cases.

The most important takeaway is that both approaches can build maintainable and production-ready APIs when designed properly.

What's next?

In Day 5, we'll explore Routing and Route Parameters in .NET Core Web API.

We'll understand how API URLs are mapped to endpoints, how route parameters work, how to use optional parameters and constraints, and how to design clean and predictable API routes with practical C# examples.


Skills Tutorials – Learn • Build • Grow Together

Continue learning with our 30-day .NET Core Web API series.