Route Constraints
🎯 What You'll Learn
- What route constraints are
- Built-in constraints (int, guid, regex, etc.)
- Multiple constraints
- Custom constraints
- When to use constraints
- Best practices
What are Route Constraints?
Route constraints restrict parameter values to ensure only valid URLs match your routes.
[HttpGet("{id}")]
public IActionResult GetProduct(int id) { }
// ✅ /api/products/123 → works
// ❌ /api/products/abc → 400 Bad Request (binding fails)
[HttpGet("{id:int}")]
public IActionResult GetProduct(int id) { }
// ✅ /api/products/123 → works
// ❌ /api/products/abc → 404 Not Found (route doesn't match)
Without constraint: Route matches, binding fails → 400 Bad Request
With constraint: Route doesn't match → 404 Not Found
Built-in Constraints
| Constraint | Description | Example |
|---|---|---|
:int |
Integer | {id:int} |
:long |
Long integer | {id:long} |
:decimal |
Decimal number | {price:decimal} |
:double |
Double | {value:double} |
:float |
Float | {value:float} |
:bool |
Boolean | {active:bool} |
:guid |
GUID | {id:guid} |
:datetime |
DateTime | {date:datetime} |
:alpha |
Letters only | {name:alpha} |
:min(n) |
Minimum value | {age:min(18)} |
:max(n) |
Maximum value | {age:max(100)} |
:range(min,max) |
Value range | {age:range(18,100)} |
:length(n) |
Exact length | {code:length(5)} |
:minlength(n) |
Minimum length | {name:minlength(3)} |
:maxlength(n) |
Maximum length | {name:maxlength(50)} |
:regex(pattern) |
Regular expression | {zip:regex(^\\d{{5}}$)} |
Common Constraint Examples
Integer Constraint
[HttpGet("{id:int}")]
public IActionResult GetProduct(int id)
{
return Ok($"Product {id}");
}
// ✅ /api/products/123
// ❌ /api/products/abc
GUID Constraint
[HttpGet("{id:guid}")]
public IActionResult GetOrder(Guid id)
{
return Ok($"Order {id}");
}
// ✅ /api/orders/3fa85f64-5717-4562-b3fc-2c963f66afa6
// ❌ /api/orders/123
Range Constraint
[HttpGet("age/{age:range(18,100)}")]
public IActionResult GetByAge(int age)
{
return Ok($"Age: {age}");
}
// ✅ /api/users/age/25
// ❌ /api/users/age/15 (too young)
// ❌ /api/users/age/150 (too old)
Regex Constraint
[HttpGet("zip/{zipCode:regex(^\\d{{5}}$)}")]
public IActionResult GetByZip(string zipCode)
{
return Ok($"Zip: {zipCode}");
}
// ✅ /api/locations/zip/12345
// ❌ /api/locations/zip/1234 (too short)
// ❌ /api/locations/zip/abcde (not digits)
In regex constraints, use double curly braces {{}} to escape single braces.
Multiple Constraints
Combine multiple constraints with : separator.
// Integer with minimum value
[HttpGet("{id:int:min(1)}")]
public IActionResult GetProduct(int id) { }
// ✅ /api/products/1
// ❌ /api/products/0
// ❌ /api/products/-1
// String with length constraints
[HttpGet("{code:alpha:length(5)}")]
public IActionResult GetByCode(string code) { }
// ✅ /api/products/ABCDE
// ❌ /api/products/ABC (too short)
// ❌ /api/products/ABC12 (contains digits)
Custom Constraints
Create custom constraints by implementing IRouteConstraint.
public class ProductSkuConstraint : IRouteConstraint
{
public bool Match(
HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var value) || value == null)
return false;
var sku = value.ToString();
// SKU format: PRD-XXXXX (e.g., PRD-12345)
return sku?.StartsWith("PRD-") == true && sku.Length == 9;
}
}
Register Custom Constraint
builder.Services.AddRouting(options =>
{
options.ConstraintMap.Add("productsku", typeof(ProductSkuConstraint));
});
Use Custom Constraint
[HttpGet("{sku:productsku}")]
public IActionResult GetBySku(string sku)
{
return Ok($"Product SKU: {sku}");
}
// ✅ /api/products/PRD-12345
// ❌ /api/products/ABC-12345
// ❌ /api/products/PRD-123
Complete InvenTrack Example
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Integer constraint
// GET /api/products/123
[HttpGet("{id:int:min(1)}")]
public IActionResult GetById(int id)
{
return Ok($"Product {id}");
}
// GUID constraint
// GET /api/products/guid/3fa85f64-5717-4562-b3fc-2c963f66afa6
[HttpGet("guid/{id:guid}")]
public IActionResult GetByGuid(Guid id)
{
return Ok($"Product GUID: {id}");
}
// Range constraint
// GET /api/products/price/50
[HttpGet("price/{maxPrice:decimal:range(1,10000)}")]
public IActionResult GetByMaxPrice(decimal maxPrice)
{
return Ok($"Products under ${maxPrice}");
}
// Alpha constraint
// GET /api/products/category/electronics
[HttpGet("category/{name:alpha:minlength(3)}")]
public IActionResult GetByCategory(string name)
{
return Ok($"Category: {name}");
}
// Regex constraint for SKU
// GET /api/products/sku/PRD12345
[HttpGet("sku/{sku:regex(^PRD\\d{{5}}$)}")]
public IActionResult GetBySku(string sku)
{
return Ok($"Product SKU: {sku}");
}
// Boolean constraint
// GET /api/products/active/true
[HttpGet("active/{isActive:bool}")]
public IActionResult GetByActiveStatus(bool isActive)
{
return Ok($"Active: {isActive}");
}
}
When to Use Constraints
- Type safety: Ensure parameters are the correct type
- Route disambiguation: Distinguish between similar routes
- Validation: Enforce business rules (min/max values)
- SEO-friendly URLs: Enforce URL patterns
- API versioning: Match specific version patterns
Best Practices
- Use constraints for type safety:
{id:int}instead of{id} - Combine constraints:
{id:int:min(1)}for positive integers - Regex sparingly: Complex regex can hurt performance
- Custom constraints: For complex business rules
- 404 vs 400: Constraints return 404 (route doesn't match)
- Document constraints: Comment complex patterns
Key Takeaways
- Route constraints: Restrict parameter values
- Built-in constraints: int, guid, regex, range, etc.
- Multiple constraints: Combine with
: - Custom constraints: Implement
IRouteConstraint - 404 vs 400: Constraints → 404, binding fails → 400
- Type safety: Use
:int,:guid, etc. - Validation: Use
:min,:max,:range - Patterns: Use
:regexfor complex patterns
You now understand route constraints! In the next section, we'll explore URL Generation—how to generate URLs programmatically for links, redirects, and API responses.