Trailblazor Routing is a flexible and alternative Approach to routing and navigation for Blazor applications.
By default routes are scanned for and registered through @page directives at the top of components. While thats still possible, Trailblazor Routing allows setting up RoutingProfiles to configure routes and their metadata.
There are different types of Blazor applications and not all of them are supported as of right now. Following types of Blazor apps are supported:
- Blazor WASM
- MAUI Blazor
Since Blazor Servers are just regular ASP.NET Core web apps juiced up with Blazor services and a Blazor SignalR hub, I assume I need to do more than just implement a custom router Razor component. I think the components URIs are also mapped as endpoints in some way. Again, as of right now this is not done by Trailblazor Routing and in order to be able to open up an HTTP request to some component route successfully the Razor components in question still need an @page directive.
I have looked into it very briefly and I assume I might even have to manually add the render modes to components or so, but I am not sure. I am also not sure that I can even do that from outside of the framework if I have to.
I am going to look into it when I have time, but no promises are made.
- Add the
AddTrailblazorRouting(IRoutingOptionsBuilder)extension method to yourIServiceCollection. - Use the
IRoutingOptionsBuilderto configure routing specifics and registerIRoutingProfile. - Implement one or mutliple
IRoutingProfileand use the exposedIRoutingConfigurationBuilderto configure theIRoutingConfiguration. This holds node registrations and other details. - Replace the microsoft
Routercomponent with theTrailblazorRoutercomponent. TheFoundandNotFoundrenderfragments and their contents such as theRouteVieworAuthorizeRouteViewcan stay. The content displayed when a route has not been found however can also be set using theIRoutingConfigurationBuilder.
builder.Services.AddTrailblazorRouting(options =>
{
var assembly = Assembly.Load("My.Example.App");
options.AddProfile<MyRoutingProfile>();
options.AddProfile(typeof(MyOtherProfile));
options.AddProfilesFromAssemblies(assembly); // Scan for IRoutingProfiles
options.ScanForNodesInAssemblies(assembly); // Scan for components with an @page directive and optional attributes configuring the node
options.DisableRoutingConfigurationValidation(); // Disable validating the IRoutingConfiguration after it has been fully configured. Validation is on by default
options.ConfigureConfiguration(builder =>
{
// Configure the IRoutingConfiguration in the extension method directly
// This action is used after all routing profiles have been run through!
});
});Routing profiles are always registered as a transient IRoutingProfile service. They are resolved using the IServiceProvider the first time the IRoutingConfiguration is accessed. Therefore routing profiles enjoy full dependency injection support!
internal sealed class RoutingProfile(IConfiguration _configuration) : IRoutingProfile
{
public void ConfigureRoutes(IRoutingConfigurationBuilder builder)
{
builder.AddNode<Home>("Home", "/", n => n.WithUris("/home", "/landing-page"));
builder.AddNode("Content", g =>
{
g.AddNode<Counter>("Counter", "/counter", n => n.WithUris("/counter/{count}"));
g.AddNode<Weather>("Weather", "/weather");
});
// Use the injected configuration or so...
}
}<TrailblazorRouter>
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</TrailblazorRouter>Components can be scanned for through their assemblies as shown above. In order to be picked up they need either a @page directive or a RouteAttribute. Trailblazor specifics can also be configured using attributes. There are a few self-explainatory ones, all of which have sufficient documentation as comments.
NodeKeyAttribute: Sets the nodes key. Optional, but if not set then the name of the component will be used as a key and since keys have to be unique this could potentially cause conflicts down the roadNodeParentAttribute: Sets the key of the nodes parent node. OptionalNodeMetadataAttribute: Sets metadata of the node. Optional, multiple attributes are allowed
The IRoutingOptions contain settings about the orchestration of the framework. Logging, validation or what type of profiles are used are more administrative than functional. The IRoutingConfiguration contains direct registrations of nodes and "routes", so simply nodes with URIs and a component type.
Both are accessible through their respective provider services, the IRoutingOptionsProvider and IRoutingConfigurationProvider.
There is full support for default parameters in the URI like in vanilla Blazor routing. Additionally standard URL query parameters are supported as well. Both types of parameters can be used in tandem or individually.
The component has an @page directive with its parameter configured directly in its URI.
@page "/counter/{Count:int}"
@attribute [NodeKey("counter")]
@attribute [NodeMetadata("metadata-key", "metadata-value")]
<h1>Counter</h1>
<p role="status">Current count: @Count</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[Parameter]
public int Count { get; set; }
private void IncrementCount() => Count++;
}The 'count' parameter can now be addressed as an inline parameter in the URL, but also as a query parameter. The QueryParameterAttribute flags a property to be a query parameter. Thus it is required to place a ParameterAttribute above it. A static name of the query parameter key can bet set using the QueryParameterAttribute attribute. This will also be the name of the parameter in the inline URI when registering it.
<h1>Counter</h1>
<p role="status">Current count: @Count</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@if (FlavorText != null)
{
<p>Flavor text: @FlavorText</p>
}
@code {
[Parameter]
[QueryParameter("count")]
public int Count { get; set; }
[Parameter]
[QueryParameter("flavor")]
public string? FlavorText { get; set; }
private void IncrementCount() => Count++;
}The component added as a node in a routing profile. Note that the 'count' parameter is configure inline, while the 'flavor' is not. Both are still addressable as query parameters. Its your choice how you want to use this.
builder.AddNode<Counter>("Counter", "/counter", n => n.WithUris("/counter/{count}"));Accessing the currently navigated to INode or parameters from the URI is very simple. The TrailblazorRouter always passes down an instance of the RouterContext as a cascading value. This cascading parameter will never be null.
@page "/metadata"
@attribute [NodeKey("metadata")]
@attribute [NodeParent("metadata")]
@attribute [NodeMetadata("cool", "metadata")]
@{
var metadatas = GetMetadata();
}
@if (metadatas.Length > 0)
{
<div>
@foreach (var metadata in metadatas)
{
<p>@metadata</p>
}
</div>
}
@code {
[CascadingParameter]
private RouterContext Context { get; set; } = null!;
private string[] GetMetadata()
{
if (Context.RouteNode == null)
return [];
return Context.RouteNode.Metadata.Select(m => $"{m.Key}; {m.Value}").ToArray();
}
}Additionally the IRouterContextAccessor service provides the same instance of the current RouterContext. Inject the service to access routing data in your own services. You can also subscribe to it to catch when the RouterContext has been updated.
Either the IRoutingConfigurationProvider can be used to provide the IRoutingConfiguration, which contains all registered nodes as both a hierarchical list, for when you want to implement breadcrumbsn or a menu, and a flattened list of nodes that simply contains every node.
However you can also use the INodeProvider service to make some operations a little more easy.
The INodeResolver service is used internally to parse parameters from a relative URI and find the INode that matches the URI.
As mentioned above the NotFound render fragment of the router component can be used. Additionally the IRoutingConfiguration contains information about what happens when no node for the current relative URI was found.
You can either set a redirect URI, that is to be redirected when no node could be resolved, or you can set a component that is to be rendered instead of the, what would be the target nodes component. Both can be set at the same time but the redirect URI will always be prioritized.