Attribute routing allows us to specify URL routes directly in controllers and actions using attributes like [Route], [HttpGet], [HttpPost].
It is a very flexible, convenient, and easy-to-maintain approach. So it is the preferred and most commonly used routing method in ASP.NET applications.
Initial setup
Before we can use attribute routing, we must first enable it in the Program.cs file.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers(); // Enables attribute routing
app.Run();Main routing attributes
We have two “families” of parameters available for use.
- The
Routeattribute - The
HttpGet,HttpPost… attributes
Let’s look at each of them,
The [Route] attribute is used to define the access route (URL) that must match for a controller or an action to be executed.
It can be used at:
- Classes (controllers): defines a base route.
- Methods (actions): defines a specific route for that action.
For example, if we use it in a controller,
[Route("api/products")]
public class ProductsController : ControllerBase
{
// Here the routes start with /api/products
}It can be used at the action level, but it is generally used at the controller level, as it does not specify the HTTP verb (get, post, etc…)
These attributes (called “verb attributes”) are used to indicate what type of HTTP request an action handles.
| Attribute | Description |
|---|---|
[HttpGet] | For GET requests |
[HttpPost] | For POST requests |
[HttpPut] | For PUT requests |
[HttpDelete] | For DELETE requests |
[HttpPatch] | For PATCH requests |
For example:
[HttpGet]
public IActionResult Get() { ... }Complete example
Let’s see how to use each of them with a complete example, where we use them together,
[Route("api/products")]
public class ProductsController : ControllerBase
{
[HttpGet] // GET /api/products
public IActionResult GetAll() => Ok();
[HttpGet("{id}")] // GET /api/products/5
public IActionResult GetById(int id) => Ok();
[HttpPost("create")] // POST /api/products/create
public IActionResult Create([FromBody] Product product) => Ok();
[HttpDelete("delete/{id}")] // DELETE /api/products/delete/5
public IActionResult Delete(int id) => Ok();
}In this example, we see a controller called ProductsController that manages routes under the prefix api/products thanks to the [Route("api/products")] attribute.
- The action
GetAllresponds to GET requests at/api/products. GetByIdis also a GET, but expects anidparameter in the URL, like/api/products/5.Createuses POST with the subroute"create"and receives aProductobject in the request body (using[FromBody]).Deleteresponds to DELETE at the route/api/products/delete/{id}, deleting the corresponding product.
Route parameters
Of course, attribute routing allows us to receive parameters, both in simple routes and complex patterns.
These parameters can be captured directly from the URL and automatically mapped to the arguments of our methods.
Basic parameters
Parameters are defined within curly braces {} and are automatically mapped to method parameters:
[HttpGet("category/{category}/product/{id}")]
public IActionResult GetProductByCategory(string category, int id)
{
// GET api/products/category/electronics/product/123
return Ok($"Product {id} in category {category}");
}Advanced routing patterns
Attribute routes with multiple parameters
Attribute routes can include multiple parameters. For example:
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
[HttpGet("{orderId}/items/{itemId}")]
public IActionResult GetOrderItem(int orderId, int itemId)
{
// Logic to get the order item
}
}In this case, a URL like /api/order/456/items/789 will map to the action GetOrderItem, and the values 456 and 789 will be passed as parameters orderId and itemId, respectively.
Routes with default values
We can define default values for route parameters:
[HttpGet("page/{number:int=1}")]
public IActionResult GetPage(int number)
{
// GET api/products/page -> number = 1
// GET api/products/page/5 -> number = 5
return Ok();
}In this case, if no value is provided for id, 1 will be used as the default value.
Routes with optional parameters
Optional parameters are indicated with a question mark (?):
[HttpGet("list/{category?}")]
public IActionResult ListProducts(string category = null)
{
// GET api/products/list
// GET api/products/list/electronics
return Ok();
}If no value is provided for id, the action will return a list of all products.
Multiple routes for one action
[HttpGet("search/{term}")]
[HttpGet("find/{term}")]
public IActionResult SearchProducts(string term)
{
// Accessible from both routes:
// GET api/products/search/laptop
// GET api/products/find/laptop
return Ok();
}Route constraints
Route constraints allow limiting the values that parameters can take in a route.
Some of the most common constraints include:
| Route pattern | Applied constraint |
|---|---|
{id:int} | Only accepts integers |
{number:decimal} | Only accepts decimal numbers |
{price:decimal:range(0.01,999.99)} | Decimal in a specific range |
{id:int:min(1)} | Integer greater than or equal to 1 |
{code:alpha} | Only accepts alphabetic characters |
{code:length(5)} | String of exactly 5 characters |
{name:minlength(3)} | String with a minimum of 3 characters |
{date:datetime} | Only accepts valid dates |
{guid:guid} | Only accepts valid GUIDs |
To apply them, we can specify types and constraints directly in the template:
[Route("api/[controller]")]
public class UserController : ControllerBase
{
[HttpGet("{id:int}")]
public IActionResult GetUser(int id)
{
// Logic to get the user
}
[HttpGet("{name:alpha}")]
public IActionResult GetUserByName(string name)
{
// Logic to get the user by name
}
}In this example:
- The action
GetUserwill only accept integer values for the parameterid. If a non-integer value is provided, the request will not match this route. - The action
GetUserByNamewill only accept alphabetic values for the parametername. If a non-alphabetic value is provided, the request will not match this route.