Conventional vs Attribute Routing
🎯 What You'll Learn
- Conventional routing (template-based)
- Attribute routing (decorator-based)
- When to use each approach
- Combining both routing types
- Token replacement in routes
- Best practices
Two Routing Approaches
| Feature | Conventional Routing | Attribute Routing |
|---|---|---|
| Definition | Centralized route templates | Routes defined on controllers/actions |
| Configuration | Program.cs | Controller attributes |
| Flexibility | Less flexible | More flexible |
| Best For | MVC apps with consistent patterns | Web APIs, custom routes |
Conventional Routing
Conventional routing defines route templates centrally in Program.cs.
Basic Setup
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Route Template Breakdown
{controller=Home} → Controller name (default: Home)
{action=Index} → Action name (default: Index)
{id?} → Optional ID parameter
URL Matching Examples
URL: /
→ Controller: Home, Action: Index
URL: /Products
→ Controller: Products, Action: Index
URL: /Products/Details
→ Controller: Products, Action: Details
URL: /Products/Details/123
→ Controller: Products, Action: Details, id: 123
Multiple Routes
// Custom route for blog
app.MapControllerRoute(
name: "blog",
pattern: "blog/{year}/{month}/{day}/{slug}",
defaults: new { controller = "Blog", action = "Post" });
// Default route
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Routes are matched in the order they're registered. More specific routes should come before general routes.
Attribute Routing
Attribute routing defines routes using attributes on controllers and actions.
Controller-Level Routes
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// GET /api/products
[HttpGet]
public IActionResult GetAll() { }
// GET /api/products/123
[HttpGet("{id}")]
public IActionResult GetById(int id) { }
// POST /api/products
[HttpPost]
public IActionResult Create() { }
}
Token Replacement
[controller] and [action] tokens are replaced with actual names.
[Route("api/[controller]")] // → api/products
public class ProductsController : ControllerBase
{
[HttpGet("[action]")] // → api/products/search
public IActionResult Search() { }
}
Multiple Routes per Action
[HttpGet("{id}")]
[HttpGet("by-id/{id}")] // Alternative route
public IActionResult GetById(int id)
{
// Accessible via:
// GET /api/products/123
// GET /api/products/by-id/123
}
Override Controller Route
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Relative route: /api/products/123
[HttpGet("{id}")]
public IActionResult GetById(int id) { }
// Absolute route (starts with /): /health
[HttpGet("/health")]
public IActionResult Health() { }
}
Routes starting with / or ~/ override the controller route.
Use sparingly to avoid confusion.
Combining Both Approaches
You can use both conventional and attribute routing in the same application.
// Conventional routing for MVC controllers
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// Attribute routing for API controllers
app.MapControllers();
Complete InvenTrack Example
API Controller (Attribute Routing)
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// GET /api/products
[HttpGet]
public IActionResult GetAll()
{
return Ok("All products");
}
// GET /api/products/123
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
return Ok($"Product {id}");
}
// GET /api/products/search?name=laptop
[HttpGet("search")]
public IActionResult Search([FromQuery] string name)
{
return Ok($"Searching for {name}");
}
// GET /api/products/category/electronics
[HttpGet("category/{categoryName}")]
public IActionResult GetByCategory(string categoryName)
{
return Ok($"Products in {categoryName}");
}
// POST /api/products
[HttpPost]
public IActionResult Create([FromBody] ProductDto product)
{
return Created("/api/products/123", product);
}
// PUT /api/products/123
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] ProductDto product)
{
return NoContent();
}
// DELETE /api/products/123
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
return NoContent();
}
}
MVC Controller (Conventional Routing)
public class HomeController : Controller
{
// GET /
public IActionResult Index()
{
return View();
}
// GET /Home/About
public IActionResult About()
{
return View();
}
}
When to Use Each
| Use Conventional Routing | Use Attribute Routing |
|---|---|
| MVC apps with consistent patterns | Web APIs |
| Simple CRUD operations | Custom route requirements |
| Traditional web applications | RESTful APIs |
| When routes follow conventions | When routes need flexibility |
Best Practices
- APIs: Use attribute routing for flexibility
- MVC apps: Use conventional routing for consistency
- Token replacement: Use
[controller]and[action]for maintainability - Route order: Specific routes before general routes
- Absolute routes: Avoid unless necessary
- Consistency: Stick to one approach per controller type
- Versioning: Use routes like
api/v1/[controller] - Documentation: Comment complex routes
Key Takeaways
- Conventional routing: Centralized templates in Program.cs
- Attribute routing: Routes defined on controllers/actions
- Token replacement:
[controller]and[action] - Flexibility: Attribute routing is more flexible
- APIs: Prefer attribute routing
- MVC: Conventional routing works well
- Combining: Both can coexist in the same app
- Route order: Matters for conventional routing
- Absolute routes: Start with
/to override controller route
You now understand conventional vs attribute routing! In the next section, we'll explore Route Parameters—how to capture data from URLs and use it in your actions.