If you want your route to be based on the class and action names and still want to use attributes, there is a middle way of using attributes for conventional routing: token replacement.
[action], [area], and [controller] will be replaced with the values of the action name, area name, and controller name from the action where the route is defined. Let's see an example. Here is the original code:
[Route("[controller]/[action]")]
public class ProductsController : Controller
{
[HttpGet] // Matches '/Products/List'
public IActionResult List()
{
// ...
}
[HttpGet("{id}")] // Matches '/Products/Edit/{id}'
public IActionResult Edit(int id)
{
// ...
}
}
The following code shows the change:
public class ProductsController : Controller
{
[HttpGet("[controller]/[action]")] // Matches '/Products/List'
public IActionResult List()
{
// ...
}
[HttpGet("[controller]/[action]/{id}")] // Matches '/Products/Edit/{id}'
public IActionResult Edit(int id)
{
// ...
}
}
Attribute routes can also be combined with inheritance. This is particularly powerful combined with token replacement:
[Route("api/[controller]")]
public abstract class MyBaseController : Controller { ... }
public class ProductsController : MyBaseController
{
[HttpGet] // Matches '/api/Products'
public IActionResult List() { ... }
[HttpPost("{id}")] // Matches '/api/Products/{id}'
public IActionResult Edit(int id) { ... }
}