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
Route
attribute - 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
GetAll
responds to GET requests at/api/products
. GetById
is also a GET, but expects anid
parameter in the URL, like/api/products/5
.Create
uses POST with the subroute"create"
and receives aProduct
object in the request body (using[FromBody]
).Delete
responds 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
GetUser
will only accept integer values for the parameterid
. If a non-integer value is provided, the request will not match this route. - The action
GetUserByName
will only accept alphabetic values for the parametername
. If a non-alphabetic value is provided, the request will not match this route.