Telerik blogs

Routing is a key feature of any single-page application. It allows the developer to arrange the website and the user to navigate different pages.

Turning Blazor Components into Routeable Pages

Whenever we use the @page directive on a Blazor component, the compiler will add the RouteAttribute during compilation, making it available as a route.

It’s what differentiates regular components that are referenced within other Blazor components from routable components, usually referenced as Blazor pages.

The Router in Blazor

When creating a Blazor project using Visual Studio 2022, you’ll get the following code in the App.razor file:

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

The Router component is from the Microsoft.AspNetCore.Components.Routing namespace and requires a reference to the application assembly.

At startup, the router reads the referenced application assembly and looks for RouteAttribute applied to compiled Blazor component classes.

A route can either be found or not found. In case it’s found, we use the RouteView component to render the view connected with the routing information.

We also specify a default layout component. The default layout is used for all page components that do not explicitly specify a layout using the @layout directive.

If the requested route cannot be found, we display a simple error using a trivial HTML snippet. We could customize and improve the error message if a page is not found here, but that’s beyond the focus of routing and navigation fundamentals.

The FocusOnNavigate component allows you to focus on an HTML element after the navigation has been completed. We can use any CSS selector to define what element to focus on in navigation. The default template uses the h1 selector, which is a good default that helps screen readers understand that the page has been navigated.

Important: Routing in Blazor requires that we set the base tag in the head section of the HTML. However, using the default template, it is correctly set in the _Host.cshtml file. Make sure to adjust this value depending on where and how you host your application in production.

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- code omitted -->
    <base href="~/" />
    <!-- code omitted -->
    </head>

Hint: The router acts the same for Blazor Server, as well as Blazor WebAssembly applications.

Passing Route Parameters

Static routes are the foundation, but dynamic routes are what we need in a real application. Dynamic routes contain parameters to pass data during page navigation.

Consider an example where you have the following Library page component:

@page "/library"
<h3>Library</h3>

<ul>
    <li><a href="/book/1">My first book</a></li>
    <li><a href="/book/2">My second book</a></li>
    <li><a href="/book/3">My third book</a></li>
</ul>

We define a list of three books. Every book is a link to a page. We provide the id of the book as the route parameter.

Now let’s take a look at the Book page component.

@page "/book/{id}"
<h3>Book @Id</h3>

@code {
    [Parameter]
    public string Id { get; set; }
}

First, we use the @page directive to define the route. We use /book as the static part of the route and use curly braces to define the id parameter as a placeholder.

In the code section, we define a property of type string and name it the same as the placeholder in the route. Routes are case-insensitive.

We can use the Id property in the HTML template or the C# code like every other property. It will be populated on navigation. In this example, we render the id as part of the h3 tag.

Applying Route Constraints

In the previous example, we defined the id route parameter of type string. It’s the default because whenever we define a URL, it is a string and can contain any type of route parameter.

In our example, we use the id of the book as the route parameter. It would proably be better to use int instead of string as the type for the Id property.

If we only change the type of the parameter, we’ll get an error during navigation because the Router could not pass a string argument to an int property.

However, we can specify the type of a route parameter with the following syntax:

@page "/book/{id:int}"

We use a colon followed by the type we want to use for the route parameter.

The whole Book component now looks like this:

@page "/book/{id:int}"
<h3>Book @Id</h3>

@code {
    [Parameter]
    public int Id { get; set; }
}

I suggest using the correct type for route parameters. It allows for cleaner code within the page implementation. For example, we do not need to manually parse the id from a string in our example. It saves a few extra lines of code.

The less code we write, the less potential for introducing bugs we have.

The NavigationManager

So far, we used links to navigate between pages. The NavigationManager allows us to navigate from one page to another within the C# interaction code.

Let’s quickly change the previous example to navigate by clicking on a li element instead of using the anchor tag.

@page "/library"
@inject NavigationManager Navigation

<h3>Library</h3>

<ul>
    <li @onclick="() => NavigateToBook(1)">My first book</li>
    <li @onclick="() => NavigateToBook(2)">My second book</li>
    <li @onclick="() => NavigateToBook(3)">My third book</li>
</ul>

@code {
    public void NavigateToBook(int id)
    {
        Navigation.NavigateTo($"book/{id}");
    }
}

We removed the a tag from within the li element of the book list. Instead, we added the @onclick event to the li tag. We then call the NavigateToBook event and provide the id of the book as its argument.

The NavigateToBook method defined within the code section of the component uses the Navigation property injected using the @inject directive on top of the component.

When navigating using the NavigationManager, we can provide different NavigationOptions. For example, we can bypass client-side routing and force the browser to load the new page from the server using the ForceLoad property. Further, we can replace the history entry using the ReplaceHistoryEntry property.

Hint: Besides initiating navigation, we can also listen to the LocationChanged event for route changes and execute custom code when navigating on a specific page.

Working with Query Strings

Besides working with URL parameters using the Parameter attribute, Blazor also offers a simple API to read query strings.

A query string is a URL that looks like this:

/search?firstname=Claudio&lastname=Bernasconi

To let a page component read a parameter from a query string, we need to use the SupplyParameterFromQuery attribute and the Parameter attribute.

Consider the following Search page component code:

@page "/search"
<h3>Search</h3>
<p>Search intent: First name: @FirstName Last name: @LastName</p>

@code {
    [Parameter]
    [SupplyParameterFromQuery]
    public string FirstName { get; set; }

    [Parameter]
    [SupplyParameterFromQuery]
    public string LastName { get; set; }
}

We define two properties of type string and apply both the Parameter and the SupplyParameterFromQuery attributes.

In the template code, we simply render the content of both properties on screen.

When we call the URL /search?firstname=Claudio&lastname=Bernasconi, Blazor provides both properties with their respective portion of the query string. It’s a simple and very powerful solution.

If you want to have a different name for the property than in the URL, you can use the Name property of the SupplyParameterFromQuery attribute to define the name in the URL.

Loading Indication Between Page Transitions

Sometimes loading a page can take a long time. We can use the Navigating component within the Router component to show custom markup during page transition.

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <!-- Code omitted -->
    </Found>
    <NotFound>
        <!-- Code omitted -->
    </NotFound>
    <Navigating>
        <p>Page is loading...</p>
    </Navigating>
</Router>

I generally keep page navigations as short as possible. For example, I load data asynchronously, making the page load a lot faster (initially without data).

The NavLink component can be used instead of a simple HTML anchor tag. It automatically applies an “active” CSS class based on whether its URL matches the current URL.

The NavMenu component generated by the Visual Studio 2022 project templates, contains an example of how to use the NavLink component:

<nav class="flex-column">
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
            <span class="oi oi-home" aria-hidden="true"></span> Home
        </NavLink>
    </div>
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="counter">
            <span class="oi oi-plus" aria-hidden="true"></span> Counter
        </NavLink>
    </div>
    <div class="nav-item px-3">
        <NavLink class="nav-link" href="fetchdata">
            <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
        </NavLink>
    </div>
</nav>

It uses the NavLink component and provide the default CSS class applied to all links. In this case, it’s “nav-link”. The link is provided to the href property.

Besides specifying the matching behavior using the NavLinkMatch enumeration, we can also provide a custom CSS class for the active NavLink using the ActiveClass property. The default value is “active”.

Conclusion

Routing and navigation are fundamentals when working with single-page applications. Blazor provides a simple yet powerful implementation of both out of the box.

We don’t have to select one of the multiple options for routing; it’s all included in Blazor. Still, it provides a lot of room for custom code and is very flexible and extensible. It not only handles simple use cases but also provides APIs for advanced scenarios.

It contributes to Blazor, allowing for the rapid development of modern single-page web applications.

If you want to learn more about Blazor development, consider reading other articles of the Blazor Basics series, or watch the FREE Blazor Crash Course on YouTube.


About the Author

Claudio Bernasconi

Claudio Bernasconi is a passionate software engineer and content creator writing articles and running a .NET developer YouTube channel. He has more than 10 years of experience as a .NET developer and loves sharing his knowledge about Blazor and other .NET topics with the community.

Related Posts

Comments

Comments are disabled in preview mode.