ASP.Net Core: Routing - Minimal API
1. Basic Explicit Mapping (The Foundation)
In Minimal APIs, every route is mapped explicitly using extension methods on the WebApplication (app) object. There is no auto-discovery by default.
Standard HTTP Verbs: Mapping is done by calling the method corresponding to the HTTP verb.
// GET: /api/hello app.MapGet("/api/hello", () => "Hello World"); // POST: /api/users app.MapPost("/api/users", (UserDto user) => { // Save user logic... return Results.Created($"/api/users/{user.Id}", user); }); // PUT: /api/users/1 app.MapPut("/api/users/{id}", (int id, UserDto user) => { ... }); // DELETE: /api/users/1 app.MapDelete("/api/users/{id}", (int id) => { ... });Handler Types: You can map a route to a lambda (inline function) or a method reference.
// Option A: Inline Lambda (Good for simple logic) app.MapGet("/inline", () => "Inline"); // Option B: Method Reference (Cleaner) string LocalFunction() => "Local Function"; app.MapGet("/method", LocalFunction);
2. Route Parameters & Constraints
Just like Controllers, Minimal APIs support capturing values from the URL and enforcing data types.
Simple Parameters: The parameter name in the lambda must match the route segment
{...}.// URL: /users/5 app.MapGet("/users/{id}", (int id) => $"User ID: {id}");Constraints (Data Type Enforcement): Use
:to enforce rules. If the rule fails, the API returns404 Not Found.// Matches: /products/10 (int) // Skips: /products/abc app.MapGet("/products/{id:int}", (int id) => ...); // Matches strings with min length 3 app.MapGet("/search/{query:minlength(3)}", (string query) => ...);Catch-All (Wildcards): Use
{*name}to capture the rest of the URL path.// Matches: /files/images/logo.png // path = "images/logo.png" app.MapGet("/files/{*path}", (string path) => ...);
3. Route Groups (Essential for Organization)
Instead of repeating the same prefix /api/users for every route, use Route Groups. This also allows you to apply middleware (like Authentication) to a whole batch of endpoints at once.
// 1. Create a Group
var userGroup = app.MapGroup("/api/users");
// 2. Map routes relative to the group
// Final URL: /api/users
userGroup.MapGet("/", GetUsers);
// Final URL: /api/users/{id}
userGroup.MapGet("/{id}", GetUserById);
// 3. Apply Security to the WHOLE group
userGroup.RequireAuthorization();
4. Advanced Parameter Binding
Sometimes you need to tell ASP.NET exactly where to look for data (Body, Query, Header), rather than letting it guess.
using Microsoft.AspNetCore.Mvc; // Required for these attributes
app.MapPost("/orders/{id}", (
[FromRoute] int id,
[FromBody] OrderDto order,
[FromQuery] int page,
[FromHeader(Name = "X-Request-ID")] string requestId
) => {
return $"Order {id}, Page {page}, Request {requestId}";
});
5. Industry Standard: The "Module" Pattern (Real World)
Problem: In a real app with 500+ endpoints, Program.cs becomes massive and unreadable if you list all app.MapGet calls there.
Solution: Use the Carter library or a custom Module Interface to split routes into separate files (Feature Slices).
The Carter Library Approach (Industry Favorite)
This achieves the exact same result but removes the need for writing the EndpointExtensions.cs file manually. It is battle-tested and supports advanced features like validation pipelines.
Install: dotnet add package Carter
1. The Feature Module (/Features/Products/GetProducts/GetProductsEndpoint.cs)
Inherit from ICarterModule instead of a custom interface.
using Carter;
using Microsoft.AspNetCore.Mvc;
namespace MyApi.Features.Products.GetProducts;
public class GetProductsEndpoint : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/products");
group.MapGet("/", HandleAsync)
.WithName("GetProducts")
.WithOpenApi();
}
internal async Task<IResult> HandleAsync(ILogger<GetProductsEndpoint> logger)
{
logger.LogInformation("Fetching products...");
var data = new[] { "Laptop", "Mouse", "Keyboard" };
return Results.Ok(data);
}
}
2. The Wiring (Program.cs)
It's even shorter.
using Carter; // Import the library
var builder = WebApplication.CreateBuilder(args);
// 1. Add Carter to DI
builder.Services.AddCarter();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 2. Map Carter (Auto-scans and maps all ICarterModules)
app.MapCarter();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.Run();
Comments
Join the discussionNo comments yet. Be the first to comment.