Routing is the mechanism by which a web server maps HTTP requests to a specific fragment in our application’s code.
For example, if a user requests the URL (e.g., GET /products/1), ASP.NET needs to know what code should be executed to handle this request.
Routing is the system through which we define which code fragment should be executed for each possible URL.
To do this, we use the concept of a route, which is simply a text string that defines a pattern that can match the URL of an HTTP request.
Routes can also contain dynamic segments that are replaced based on the request (that’s where most of their power lies).
/products/{id}
In this case, {id} is a dynamic parameter and can match any value in that position, such as /products/123.
When a client makes an HTTP request to an ASP.NET Core application, the routing system compares the request URL with the routes defined in the application.
Types of Routing
ASP.NET supports multiple routing approaches:
| Routing Type | Where it’s defined | Main Advantages |
|---|---|---|
| Minimal APIs | Program.cs (from .NET 6+) | ✔️ Concise code ✔️ Fast for prototypes |
| Attribute Routing | In controllers) | ✔️ Flexibility ✔️Explicit control |
| Conventional Routing | Program.cs or Startup.cs | ✔️ Centralized configuration ✔️ Useful in large apps |
Minimal APIs
The simplest option is to use minimalist route mapping, defining routes directly in Program.cs.
This is a very straightforward approach, introduced in .NET 6, to have simple behavior similar to other frameworks.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello, world!");
app.MapGet("/greet/{name}", (string name) => $"Hello, {name}!");
app.MapPost("/create", async (HttpContext context) =>
{
string body = await new StreamReader(context.Request.Body).ReadToEndAsync();
return Results.Ok($"Data received: {body}");
});
app.Run();
In this case:
MapGetresponds toGETrequests at/greet.MapPosthandlesPOSTrequests at/create.
Advantages
✔️ Less boilerplate code ➡️ Ideal for small projects.
Controller Routing
The “conventional” and most used routing in ASP.NET is controller routing. (a controller is simply a code fragment designed to respond to a route).
Within controller routing, we can in turn have:
- Attribute routes: Defined directly on controllers and actions using attributes.
- Conventional routes: Defined in the application’s configuration file or via attributes on controllers and actions.
This is the most commonly used routing. Attribute routes are defined directly on controllers and actions using the [Route] attribute.
This approach is very flexible and allows defining specific routes for each action.
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
// Logic to get the product
}
}
In this example:
- The
[Route("api/[controller]")]attribute defines a base route for theProductController. The[controller]keyword is automatically replaced by the controller name (in this case,Product). - The
[HttpGet("{id}")]attribute defines a specific route for theGetProductaction. The URL/api/product/123will be mapped to this action, and the value123will be passed as theidparameter.
This is an older alternative for controller routing that doesn’t use attributes.
It’s configured in Program.cs or Startup.cs.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
In this example:
controller: Specifies the controller that will handle the request.action: Indicates the action or method within the controller.id?: An optional parameter.- A request to
https://localhost:5001/Products/Details/5will be mapped to theProductscontroller,Detailsaction, and will pass the parameterid=5.
Conventional routing is used less in new projects, but you might encounter it in existing applications.
Combining Conventional and Attribute Routes
It’s possible to combine conventional and attribute routes in the same application (although it’s probably not a good idea 🙄).
In this case, attribute routes take precedence over conventional routes. This means that if an action has an attribute route defined, it will be used instead of the conventional route.
Seriously, don’t do it if you can avoid it 🙅
Routes with Parameters
In any of the routing types we’ve seen, it’s possible to include parameters that are passed to actions as arguments. For example:
app.MapControllerRoute(
name: "product",
pattern: "product/{id}",
defaults: new { controller = "Product", action = "Details" });
In this case, a URL like /product/123 will be mapped to the Details action of the Product controller, and the value 123 will be passed as the id parameter.
We will see them in more detail in each of the corresponding articles
Design Best Practices
Let’s go over some tips to follow before you dive into designing your APIs and web applications.
Consistent Naming
The naming of endpoints is very important when designing a web API. A couple of guidelines to follow 👇.
- Use plural nouns:
/productsinstead of/getProducts - Lowercase and hyphens:
/user-roles, never something like/userRoles - Avoid verbs in routes: Use HTTP methods to indicate actions
API Versioning
It’s also very common for an API to change at some point. It’s a good precaution to include a version reference in APIs.
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult GetById(int id) { /* ... */ }
}
