Route constraints
Every built-in constraint, live. Type a value and hit test on any row - the actual router decides: match lands on the constrained route, rejection falls through to an unconstrained fallback route.
int, guid, ...)
validate and convert - the parameter binds a real int/Guid/...
Validation constraints (alpha, min(10), regex(...), ...)
only accept or reject - the value stays a string. Chained together, the last type
constraint decides the bound value: {v:int:min(1):max(5)} binds an int.
Your test value
→ navigates to /c/<kind>/…
| Constraint | Rule | Try it |
|---|---|---|
|
{v:int}
type |
Parses as int (invariant culture); binds an int. | |
|
{v:long}
type |
Parses as long; binds a long. | |
|
{v:bool}
type |
true / false, case-insensitive; binds a bool. | |
|
{v:guid}
type |
Any standard Guid format; binds a Guid. | |
|
{v:datetime}
type |
Invariant-culture DateTime; binds a DateTime. | |
|
{v:decimal}
type |
Invariant-culture decimal; binds a decimal. | |
|
{v:alpha}
validation |
ASCII letters A-Z only (empty passes too). | |
|
{v:min(10)}
validation |
Numeric value ≥ 10. | |
|
{v:max(10)}
validation |
Numeric value ≤ 10. | |
|
{v:range(1,10)}
validation |
Numeric value between 1 and 10. | |
|
{v:minlength(3)}
validation |
Text length ≥ 3. | |
|
{v:maxlength(5)}
validation |
Text length ≤ 5. | |
|
{v:length(2,4)}
validation |
Text length between 2 and 4 (length(4) = exact). | |
|
{v:regex(^[a-z]+\d+$)}
validation |
Matches the inline pattern (case-insensitive, invariant). | |
|
{v:file}
validation |
Looks like a file name: a '.' with something after it. | |
|
{v:nonfile}
validation |
Does NOT look like a file name (classic static-asset filter). | |
|
{v:slug}
custom |
Custom: ≥ 3 chars, letters/digits/dashes only. | |
|
{v:int:min(1):max(5)}
chain |
int AND 1..5 - binds an int, the validators just gate it. |
How the tester works
Two routes exist per row - a constrained one and one shared unconstrained fallback. Both can match the same URL, and specificity picks the winner: a constrained parameter outranks a plain one. When the constraint rejects the value, the constrained route simply doesn't match and the fallback renders the rejection page.
@* The winner when the value passes - a constrained parameter ranks higher: *@ <Broute Path="/c/int/{v:int}">...</Broute> @* The shared fallback that renders rejections - matches any /c/x/y: *@ <Broute Path="/c/{kind}/{value}">...</Broute>
Custom constraints
The slug row is not built-in - it's registered at startup on
BrouterOptions.Constraints, scoped to this app's DI container. Custom
constraints are used in templates exactly like built-ins.
// Program startup (see Extensions/IServiceCollectionExtensions.cs): services.AddBitBrouterServices(o => { o.Constraints.Register("slug", new BrouterTypeRouteConstraint<string>((string s, out string r) => { r = s; return s.Length >= 3 && s.All(c => char.IsLetterOrDigit(c) || c == '-'); })); }); // Then in any template: <Broute Path="/posts/{name:slug}" ... />