Skip to content

Route parameters

How URL segments become strongly-typed values in your components - by-name [Parameter] binding, the cascaded parameter bag, wildcards, and fallbacks.

Current count
0
The shared count lives in the host component that declares the routes and is passed into this page through <Content> - visit a counter link below, change it, and come back: route content can share state with its host like any other component tree.

Typed binding via constraints

A parameter with a type constraint doesn't just restrict what matches - it converts the value before your component sees it: {init:int} arrives as an int, {day:datetime} as a DateTime (invariant culture). When constraints chain, the last type constraint decides the CLR type; validation constraints (min, regex, ...) only gate the match. Route values bind automatically to same-named [Parameter] properties on a Component-rendered page - and [BrouterParameter(Name = "...")] overrides the name when the property can't (or shouldn't) match the template's.

@* {init:int} binds the segment as an int. *@
<Broute Path="/counter/{init:int}" Component="@typeof(CounterPage)" />

// CounterPage.razor - bound by name, already converted:
[Parameter] public int Init { get; set; }

// or bind a differently-named property explicitly:
[Parameter, BrouterParameter(Name = "init")] public int StartValue { get; set; }

The cascaded parameter bag

Every matched route also cascades a BrouterRouteParameters value (cascading parameter name "RouteParameters"; it's also the argument of a <Content Context="p"> fragment). It's the escape hatch when you don't want one property per parameter - keys are case-insensitive, and the typed getters convert strings to enums, Guids, and any scalar on demand.

MemberBehavior
this[key]Raw bound value (object) or null when absent.
Contains(key)Whether the template bound this parameter.
Get<T>(key)Convert or throw - distinguishes missing from unconvertible.
TryGet<T>(key, out v)Convert without throwing.
GetOrDefault<T>(key, fallback)Convert or return the fallback.
[CascadingParameter(Name = "RouteParameters")]
public BrouterRouteParameters Parameters { get; set; } = BrouterRouteParameters.Empty;

// Parameters.GetOrDefault<int>("init", 0), Parameters.TryGet<Guid>("id", out var id), ...

Optionals, defaults & catch-alls

A trailing {username?} binds null when the URL omits it - so bind it to a nullable property. {action=Index} supplies its default instead of null. A catch-all ({**slug}) binds the entire remaining path, slashes included, always as a string (constraints on a catch-all validate but never convert). See the template reference for every shape and its matching rules.

Query-string binding

Query values bind through the framework's [SupplyParameterFromQuery] as usual. For types the framework can't parse - enums, Guid, nullables of any scalar, or string[] for repeated pairs (?tag=a&tag=b) - annotate the property with [BrouterQuery] and Brouter converts it for you. Reading without binding also works: Brouter.Location.GetQuery("page") / GetQueryAll("tag").

[Parameter, SupplyParameterFromQuery] public string? Q { get; set; }

// Types the framework can't supply - Brouter converts them:
[Parameter, BrouterQuery] public SortOrder? Sort { get; set; }
[Parameter, BrouterQuery(Name = "tag")] public string[]? Tags { get; set; }

Wildcards & the 404 fallback

A bare * matches any single segment without binding a value - useful for patterns where the segment's content doesn't matter. Wildcards rank below every named parameter in specificity, so they only win when nothing more specific matches. When no route matches at all, the router follows NotFoundUrl (this demo redirects to its /404 page); alternatively an inline <NotFound> fragment can render in place without changing the URL.

@* Any first segment, literal "test" second. *@
<Broute Path="/*/test" ... />

@* Router-level fallback wiring: *@
<Brouter NotFoundUrl="404"> ... </Brouter>