Navigating your app via code

Access to browser navigation from Blazor is provided via the IUriHelper service. This can be injected into a Blazor component using @inject in a razor file, or the [Inject] attribute in a CS file.

The IUriHelper service has two members that are of particular interest; NavigateTo and OnLocationChanged.

The OnLocationChanged event

OnLocationChanged is an event that is triggered whenever the URL in the browser is altered. It passes an instance of LocationChangedEventArgs which provides the following information:

public readonly struct LocationChangedEventArgs
{
	public string Location { get; }
	public bool IsNavigationIntercepted { get; }
}

The Location property is the full URL as it appears in the browser, including the protocol, path, and any query string.

IsNavigationIntercepted indicates whether the navigation was initiated via code or via an HTML navigation.

  • false
    The navigation was initiated by IUriHelper.NavigateTo being called from code.
  • true
    The user clicked an HTML navigation element (such as an a href), and Blazor intercepted the navigation instead of allowing the browser to actually navigate to a new URL, which would result in a request to the server. It will also be true in other cases, such as if some JavaScript on the page causes a navigation (for example, after a timeOut). Ultimately, any navigation event that wasn’t initiated via IUriHelper.NavigateTo will be considered an intercepted navigation, and this value will be true.

Note there is currently no way to intercept a navigation and prevent it from proceeding.

Observing OnLocationChanged events

It is important to note that the IUriHelper service is a long-living instance. Consequently, any component that subscribes to its OnLocationChanged event will be strongly referenced for the duration of its lifetime. It is therefore important our components also unsubscribe when they are destroyed, so they may be garbage collected.

Currently, the ComponentBase class does not have a lifecycle event for when it is destroyed, but it is possible to implement the IDisposable interface.

@implement IDisposable
@inject IUriHelper UriHelper

protected override void OnInitialized()
{
	// Subscribe to the event
	UriHelper.OnLocationChanged += LocationChanged;
	base.OnInitialized();
}

void LocationChanged(object sender, LocationChangedEventArgs e)
{
	string navigationMethod = e.IsNavigationIntercepted ? "HTML" : "code";
	System.Diagnostics.Debug.WriteLine($"Notified of navigation via {navigationMethod} to {e.Location}");
}

void IDisposable.Dispose()
{
	// Unsubscribe from the event when our component is disposed
	UriHelper.OnLocationChanged -= LocationChanged;
}

The NavigateTo method

The IUriHelper.NavigateTo method enables C# code to control the browser’s URL. As with intercepted navigations, the browser does not actually navigate to a new URL. Instead the URL in the browser is replaced, and the previous URL is inserted into the browser’s navigation history, but no request is made to the server for the content of the new page. Navigations made via NavigateTo will cause the OnLocationChanged event to trigger, passing the new URL and false for IsNavigationIntercepted.

For this example we’ll alter the standard Blazor template again. We’ll use what we learned previously in Route parameters and Optional route parameters.

First, delete the Index.razor and FetchData.razor pages, and remove the links to those pages inside the NavMenu.razor file. Also in NavMenu, change the href of the link to counter to href="", because we are going to make it the default page.

Edit Counter.razor and give it two routes, "/" and "/counter/{CurrentCount:int}", also make sure it descends from the CounterBase class so we can see navigation logs in the browser’s console window – the CounterBase.cs file was outlined previously in the OnLocationChanged section.

@page "/"
@page "/counter/{CurrentCount:int}"
@inherits CounterBase

We’ll also need to change the currentCount field so it is a property with a getter and setter, and decorate it as a [Parameter]. Note that it has been renamed from camelCase to PascalCase too.

[Parameter] public int CurrentCount { get; set; }

We now have a counter page that can be accessed either simply be reaching the main page of the app, or by specifying /counter/X, where X is an integer value.

The IUriHelper was injected into our CounterBase class, and so is accessible in our Counter.razor file.

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

	bool forceLoad;

	void AlterBy(int adjustment)
	{
		int newCount = CurrentCount + adjustment;
		UriHelper.NavigateTo("/counter/" + newCount, forceLoad);
	}
}

We’ll call the AlterBy method from two buttons, one to increment the CurrentCount and one to decrement it. There is also an option the user will be able to select, forceLoad, which will set the relevant parameter in the call to NavigateTo so we can see the difference. The whole file should eventually look like this:

@page "/"
@page "/counter/{CurrentCount:int}"
@implements IDisposable
@inject IUriHelper UriHelper

<h1>Counter value = @CurrentCount</h1>

<div class="form-check">
	<input @bind=@forceLoad type="checkbox" class="form-check-input" id="ForceLoadCheckbox" />
	<label class="form-check-label" for="ForceLoadCheckbox">Force page reload on navigate</label>
</div>

<div class="btn-group" role="group">
	<button @onclick=@( () => AlterBy(-1) ) class="btn btn-primary">-</button>
	<input value=@CurrentCount readonly class="form-control" />
	<button @onclick=@( () => AlterBy(1) ) class="btn btn-primary">+</button>
</div>
<a href="/Counter/0">Reset</a>

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

	bool forceLoad;

	void AlterBy(int adjustment)
	{
		int newCount = CurrentCount + adjustment;

		if (newCount > 5)
			UriHelper.NavigateTo("https://ibm.com");

		UriHelper.NavigateTo("/counter/" + newCount, forceLoad);
	}

	protected override void OnInitialized()
	{
		// Subscribe to the event
		UriHelper.OnLocationChanged += LocationChanged;
		base.OnInitialized();
	}

	private void LocationChanged(object sender, LocationChangedEventArgs e)
	{
		string navigationMethod = e.IsNavigationIntercepted ? "HTML" : "code";
		System.Diagnostics.Debug.WriteLine($"Notified of navigation via {navigationMethod} to {e.Location}");
	}

	void IDisposable.Dispose()
	{
		// Unsubscribe from the event when our component is disposed
		UriHelper.OnLocationChanged -= LocationChanged;
	}
}

Clicking the - or + buttons will call the AlterBy method which will instruct the IUriHelper service to navigate to /counter/X, where X is the value of the adjusted CurrentCount – resulting in the following output in the browser’s console:

WASM: ´╗┐Notified of navigation via code to http://localhost:6812/counter/1
WASM: Notified of navigation via code to http://localhost:6812/counter/2
WASM: Notified of navigation via code to http://localhost:6812/counter/3
WASM: Notified of navigation via code to http://localhost:6812/counter/4

Clicking the Reset link will result in an Intercepted navigation (i.e. not initiated in C# code) and navigate to /counter/0, resetting the value of CurrentCount.

WASM: ´╗┐Notified of navigation via code to http://localhost:6812/counter/1
WASM: Notified of navigation via code to http://localhost:6812/counter/2
WASM: Notified of navigation via code to http://localhost:6812/counter/3
WASM: Notified of navigation via code to http://localhost:6812/counter/4
WASM: Notified of navigation via HTML to http://localhost:6812/Counter/0

ForceLoad

The forceLoad parameter instructs Blazor to bypass its own routing system and instead have the browser actually navigate to the new URL. This will result in an HTTP request to the server to retrieve the content to display.

Note that a force load is not required in order to navigate to an off-site URL. Calling NavigateTo to another domain will invoke a full browser navigation.

Play with the GitHub example for this section. Look in the browser’s Console window to see how IsNavigationIntercepted differs when navigating via the buttons and the Reset link, and look in the browser’s Network window to see how it behaves differently based on whether you are:

  • Navigating with forceLoad set to false.
  • Navigating with forceLoad set to true.
  • Navigating to an off-site URL.

To observe the last scenario, you may wish to update your AdjustBy method to navigate off-site when CurrentValue passes a specific value.

void AlterBy(int adjustment)
{
	int newCount = CurrentCount + adjustment;

	if (newCount > 5)
		UriHelper.NavigateTo("https://ibm.com");

	UriHelper.NavigateTo("/counter/" + newCount, forceLoad);
}