60-Second Version: Routing = Receptionist. /products/5 โ "Send to ProductController.Details, id=5". Two types: Conventional = 1 rule for all, Attribute = custom URL per action.
1. Conventional Routing: One Pattern Rules All
Set in Program.cs:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
| URL | Controller | Action | Id |
/ | Home | Index | null |
/product | Product | Index | null |
/product/details/5 | Product | Details | 5 |
{id?} = optional. =Home = default if missing.
2. Attribute Routing: Custom URLs
Put route directly on action:
public class BlogController : Controller
{
[Route("blog/{year:int}/{month:int}/{slug}")]
public IActionResult Post(int year, int month, string slug)
{
// Matches: /blog/2026/1/aspnet-core-routing
return View();
}
}
Why? SEO-friendly URLs. /blog/2026/1/my-post beats /blog/post?year=2026&month=1&slug=my-post
3. Route Constraints: Filter Bad URLs
{id:int} = only match if id is number. /product/details/abc โ 404, not crash.
| Constraint | Example | Matches |
:int | {id:int} | 5, 99 |
:guid | {id:guid} | UUID only |
:alpha | {name:alpha} | letters only |
:min(18) | {age:min(18)} | 18+ |
Beginner Trap: Karan defines [Route("product/{id}")] and [Route("product/{name}")]. Both match /product/5. AmbiguousMatchException. Fix: Add constraints {id:int} vs {name:alpha}.
1. Routing Pipeline: How URL Becomes Action
Request GET /product/details/5:
- EndpointRoutingMiddleware: Parses URL, checks route table. Sets
HttpContext.GetRouteData()
- Route Values:
{controller="Product", action="Details", id="5"}
- EndpointMiddleware: Finds
ProductController.Details(int id) method
- ActionInvoker: Binds
"5" โ int id = 5, calls method
What is Endpoint? Final destination. Routing picks it, Middleware executes it.
2. Order Matters in Conventional Routes
Routes are checked top-down. First match wins.
app.MapControllerRoute(
name: "blog",
pattern: "blog/{year}/{month}/{slug}",
defaults: new { controller = "Blog", action = "Post" });
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Wrong order: Put "default" first โ /blog/2026/1/test matches controller=blog, action=2026. 404. Specific routes first, catch-all last.
3. Attribute Routing + Conventional = Can Mix
You need app.MapControllers(); in Program.cs to enable attributes.
// Program.cs
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapControllers(); // Scans for [Route] attributes
// Controller
[Route("api/[controller]")] // Token replacement: [controller] = Product
public class ProductController : ControllerBase
{
[HttpGet("{id:int}")] // Combines: /api/product/5
public IActionResult Get(int id) { }
}
4. URL Generation: Link Generation & Redirects
Never hardcode <a href="/Product/Details/5">. Route changes break it.
| Method | Code | Output |
| Tag Helper | <a asp-controller="Product" asp-action="Details" asp-route-id="5"> | /Product/Details/5 |
| Url Helper | @Url.Action("Details", "Product", new { id = 5 }) | /Product/Details/5 |
| Redirect | RedirectToAction("Details", new { id = 5 }) | 302 to /Product/Details/5 |
Why: If you change route to [Route("items/{id}")], all links auto-update.
5. Areas: Split Large Apps
/Admin/Product/Index vs /Product/Index. Same controller name, different area.
// Folder: Areas/Admin/Controllers/ProductController.cs
[Area("Admin")]
public class ProductController : Controller
{
public IActionResult Index() { } // URL: /Admin/Product/Index
}
// Program.cs
app.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
FAANG Tip: [Route("{*slug}")] = catch-all. Matches everything including /a/b/c/d. If placed first, it eats all requests. Always put catch-all routes last. Also, attribute routes don't inherit. [Route("api")] on controller + [HttpGet("test")] on action = /api/test, not /api/controller/test.
No comments yet. Be the first to share your thoughts!