Conventional and Attribute Routing
Conventional routing builds every address from one pattern; attribute routing gives each action its own address with [Route] and [HttpGet]. Constraints such as {id:int} decide which addresses match.
Journeyman · ASP.NET Core · C# · Updated
After this lesson you can
- Give actions their own addresses with [Route], [HttpGet] and [HttpPost]
- Use route constraints such as {id:int}, and explain how conventional and attribute routes differ
Key takeaways
- Conventional routing builds every address from one pattern in
Program.cs, such as{controller=Home}/{action=Index}/{id?}. [Route("products")]on a controller starts every address in it;[HttpGet("...")]and[HttpPost("...")]on an action add the rest and say which method it answers.- A controller with attribute routes is reached only through them: its conventional address, such as
/Products/Details/7, is 404 Not Found. - A constraint such as
{id:int}matches only whole numbers, so/products/abcis 404 instead of reachingDetailswith anidof 0. - Choose attribute routes for friendly addresses such as
/products/7, and for every Web API.
Routing is how ASP.NET Core turns an address into a controller action. You've used one way so far; there are two.
Conventional routing
Program.cs holds one pattern for the whole app:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Every controller's addresses follow it: /Products/Details/7, /Products/Search?name=sauce. Add an action, and it has an address at once. That's conventional routing: addresses by convention.
It's quick, but the addresses come from your class and method names, so /Products/Details/7 is what the world sees. Many sites prefer shorter, friendlier ones, such as /products/7.
Attribute routing
Attribute routing puts each address on the action itself:
[Route("products")]
public class ProductsController : Controller
{
// GET /products
[HttpGet("")]
public IActionResult Index(int page = 1) { ... }
// GET /products/7
[HttpGet("{id:int}")]
public IActionResult Details(int id) { ... }
// GET /products/search?q=sauce
[HttpGet("search")]
public IActionResult Search([FromQuery(Name = "q")] string? text) { ... }
// GET /products/new
[HttpGet("new")]
public IActionResult Create() { ... }
// POST /products/new
[HttpPost("new")]
[ValidateAntiForgeryToken]
public IActionResult Create(Product product) { ... }
}
[Route("products")]on the class is the start of every address in it.[HttpGet("...")]and[HttpPost("...")]on an action add the rest, and say which method it answers.{id:int}is a route parameter, filled into theidparameter, as with conventional routing.
The two Create actions share the address /products/new: the GET shows the form, the POST saves it.
Tag helpers follow the attribute routes, with no changes to your views: asp-action="Details" asp-route-id="7" now writes href="/products/7".
Once a controller has attribute routes
A controller with attribute routes is reached only through them. /Products/Details/7 no longer works for this controller: it's 404 Not Found. Other controllers, such as HomeController, keep their conventional addresses. A site can mix the two, controller by controller.
Route constraints
The :int in {id:int} is a constraint: the route only matches when that part is a whole number.
| Address | Result |
|---|---|
/products/7 |
Details(7) |
/products/abc |
404: abc isn't an int, so no route matches |
/products/search |
Search: a fixed word in a template always beats a {parameter} |
That's why constraints matter: without :int, /products/abc would still reach Details, with an id it couldn't fill (0). (/products/search is safe either way: a fixed word always beats a {parameter}.)
| Constraint | Matches |
|---|---|
{id:int} |
Whole numbers |
{id:int:min(1)} |
Whole numbers from 1 up |
{slug:alpha} |
Letters only |
{date:datetime} |
Dates, such as 2026-03-06 |
{id?} |
Optional: the part may be left out |
Which to use
| Conventional | Attribute | |
|---|---|---|
| Addresses come from | One pattern in Program.cs |
Each action's own attributes |
| Good for | Quick sites where class names make fine addresses | Chosen, friendly addresses; and Web APIs, which always use attribute routes |
| A new action | Has an address at once | Needs its own [HttpGet] or [HttpPost] |
Check yourself
2 questions to try on the lesson page.
So what
Routes are the front door of every action. Conventional routes come for free; attribute routes let you choose addresses people can read and remember, and you'll use them for every Web API you build.
Key terms
- Conventional Routing
- Attribute Routing
- Route Template
- Route Constraint