Skip to content

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.

Two families. Type constraints (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}" ... />