Practical .NET

Navigating in Blazor

If you're moving your application's client-side code to Blazor, then you'll want Blazor to manage navigating between pages, too.

In addition to supporting anchor tags that require you to go back to the server to get the next page, Blazor also provides a client-side routing model that avoids those trips as long as you're moving between Blazor-enabled pages. You can implement that model with plain old HTML or you can leverage it with C# (I'll show you how to both).

Caveat: I did this project with Blazor 0.8.0.0, which requires .NET Core 3.0 Preview and Visual Studio 2019 Preview. All of what you see here should, however, work in Visual Studio 2017 and .NET Core 2.x. On the other hand (did I remember to mention that this technology is still "experimental"?), neither Microsoft nor I am promising anything.

As an example: While working on this case study for this article, whenever I triggered a build (by pressing F5, for example) Visual Studio would just stop dead unable, apparently, to even start a build. I would have to cancel the build, wait patiently for the "Build has been cancelled" message and then press F5 again before I could debug my application (it always worked fine the second time). You get the picture.

Configuring Routing
A cshtml file is a Blazor component. So, when we talk about navigating or routing in Blazor, we're really talking about moving between components. As is typical of ASP.NET Core applications, some configuration is required to use Blazor's routing facilities. You must, for example, have a base element in the index.html file in the wwwroot folder of your site that forms the start page for your application. That base element must have an href attribute set to something (right now, the actual string you use doesn't seem to matter). Here's an example:

<base href="/" />

You must also enable routing in an app.cshtml file in your application's root folder with this Router element:

<Router AppAssembly="typeof(Program).Assembly" />

The code to do both these tasks is included in the Blazor project templates so you shouldn't have to provide it yourself. By and large, therefore, unless Blazor behaves oddly, you can ignore these steps.

There is one exception, though: You can use the Router element in the app.cshtml file to provide a fallback page for when your Blazor routing is given a bad route (effectively, a 404 page for bad Blazor URLs). To enable this, you need to set the Router element's FallbackComponent attribute to the Type object for the Blazor component to use as your fallback page.

Assuming I have a component called SorryAboutThat (that is, that I have a SorryAboutThat.cshtml file in my application's Pages folder), this markup in app.cshtml will make that component my fallback:

<Router FallbackComponent="typeof(Pages.MyFallbackRazorComponent)"  ... 

There are two caveats. First, as the comment in in app.cshtml says, the intent is to move this configuration to Program.cs. If so, setting your FallbackComponent in the Router element may stop working in a later version of Blazor. Second, when the user arrives at your fallback page, the address bar of the browser will show the requested URL, not the URL for your fallback page.

Routing in HTML
The first step in getting a page (component) ready to participate in routing is to assign the component a route. You do that in the cshmtl file, using the page directive. This directive, for example, assigns the component to the route "/customers":

@page "/customers"

As with attribute-based routing, you can include parameters in your routes. You can also stack multiple page directives in a cshtml file to provide multiple routes to the same component. These example gives the page two routes ("customer" and "customers/customer") with both pages accepting a parameter called customerId:

@page "/customers/{customerId}"
@page "/customers/customer/{customerId}"

There's a reason this looks like attribute-based routing: During the build process, Blazor creates a C# class from your Blazor component. During that process these page directives are turned into Route attributes on the class that Blazor generates. If you've experimented with Razor Pages, this is the way routing is handled there, also.

With my routes assigned, to navigate to my "/customers" component, I can just use a plain old anchor tag, like this:

<a href="/Customers/Customer/A123">Customer A123</a>

If you provide a valid route, no call to the server is made to fetch the page (requests for images and stylesheets may still be made) -- everything is handled inside the browser. If, on the other hand, your link points to an invalid path on your site, Blazor (having seen all the routes by reading the page directives at compile time) doesn't do anything when you click on the link.

By the way, instead of an anchor tag, you can use Blazor's NavLink element. A NavLink element looks just like an anchor element (well, except for the tag name):

<NavLink href="/Customers/Customer/A123">Customer A123</NavLink>

In theory, when the user is on the page that a NavLink's href attribute points to, any CSS rule that uses the :active selector should behave differently than it does with an anchor tag. I was unable to detect any difference, but that may reflect my general cluelessness when it comes to CSS.

In Code: Routing and Parameters
You can also navigate to any of your components by using the UriHelper class's NavigateTo method, passing the route you want to follow. This code, in a method tied to the click event of a button, routes the use to the customers/customer/1 route:

UriHelper.NavigateTo("customers/customer/1");

To make the UriHelper object available to your component in a variable called UriHelper, add this line after the page directive at the top of your file:

@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper

The UriHelper object also fires a OnLocationChanged event when navigation occurs. A method attached to that event is passed the URL of the location that you've just navigated to.

To catch the values from parameters passedin your routes, you use the same technology I described in an earlier column on assembling a component from other components: properties decorated with Parameter attribute. Unlike that previous example, however, properties that accept parameters embedded in the URL must be declared as public.

This code, for example, creates a custId property that will automatically be loaded with the value from the custId parameter I defined in my earlier page directive:

@functions {

    [Parameter]
    public string custId { get; private set; }

I can now use that property in my Razor page just by prefixing with the usual @ sign, like this:

Customer Number: @custId

You need to be aware of when your component is instantiated, though. If you navigate to a component from another component, the component you are navigating to is instantiated and any parameter properties you have are set from the values in the Route. If you navigate to a component from itself, however, the component is not instantiated, though any parameter properties are set from the Route values. This means that if you recycle within the same page, any fields in your code not set from routing values will hang onto their previous values.

Routing isn't an extensive system (as the documentation says, it's not as sophisticated as the Routing module in Angular). It is, however, enough to get you from one page to another and do it without leaving the browser. Which is, after all, the point: staying on the client.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube