Null Reference Exception in Blazor Router

If you’ve used Blazor for a while you may have run into the following (pretty unhelpful) error message where the page router is throwing a NullReferenceException:

A screenshot of a stack trace produced from a .NET application. The main messages are "An unhandled exception occurred while processing the request. NullReferenceException: Object reference not set to an instance of an object. Microsoft.AspNetCore.Components.Routing.Router.Refresh(bool isNavigationIntercepted)". A full stack trace is visible below.
But what am I trying to reference that is null?!

The worst part is that I always seem to run into this error after making a bunch of changes and then having no idea exactly what I did to break the build (yeah I know, I should probably run my code more while making changes).

I’ve managed to form a general idea of why the error is thrown and have found two concrete situations that reproducibly cause the issue, so I’ve finally written them down for when I inevitably run into this situation again!

What’s Going Wrong?

So this is a bit speculative, but I believe that the NullReferenceException is thrown in circumstances where the router is unable to correctly map out all of the pages/potential routes in the application without duplicate paths or ambiguous parameter lists.

It would be nice if this exception was caught and rethrown as something more useful to the end user, but given that basic framework for what is happening in the background I managed to find two concrete examples of what can cause this error!

Culprit 1: Duplicate/Ambiguous Page Directives

In Blazor pages and Razor components, you specify the path where a page should be accessible from by using the @page directive. For example, we may have SuperCoolPage.razor which is defined as follows:

@page "/SuperCoolPage"

<h1>Super Cool Page!</h1>
<p>This page is just the coolest</p>

In the above example, we could get to this page in our application by accessing “<URL>/SuperCoolPage”.

The problems start to arise when we find ourselves with duplicate or ambiguous paths (I usually run into this problem if I copy and paste an existing page without changing the @Page value)! Since the router isn’t able to definitively tell which of the pages the request should be routed to, you hit an exception.

Note that ambiguous paths can be quite sneaky. As an example, having the following @page directives (either on separate pages or together on the same page) will cause the error.

@page "/Page/{Id:int}"

@page "/Page/{FirstParam:bool}"

This is because even though the they take different kinds of parameters, it’s ambiguous enough that the router isn’t sure where it should route “<URL>/Page/<SomeValue>” to.

If you look at the above and think that it doesn’t seem unambiguous (“just parse the parameter as each type and see which fits!”), take a look at the following and ask yourself how a request to “<URL>/Page/1” should be handled.

@page "/Page/{IntParam:int}"
@page "/Page/{DoubleParam:double}"

@if (IntParam.HasValue)
    <p>IntParam has been passed in and is set to @IntParam</p>
    <p>IntParam has not been passed in</p>

@if (DoubleParam.HasValue)
    <p>DoubleParam has been passed in and is set to @DoubleParam</p>
    <p>DoubleParam has not been passed in</p>

@code {
    [Parameter] public int? IntParam { get; set; }
    [Parameter] public double? DoubleParam { get; set; }

It’s better to squash potential ambiguity and unclear route handling at compile time rather than wondering how your requests are being parsed later!

Culprit 2: Invalid Parameter Constraints/Ordering

If you’ve recently been tweaking the parameters on a given page, you may have accidentally introduced some invalid parameter constraints or put the parameters in an order that the router can’t handle correctly. Let’s look at what these might look like!

NOTE: Although I mentioned parameters in the first section, that was in the context of identical routes; this is specifically about badly specified parameter constraints and invalid parameter lists.

Explicitly Specifying a string Parameter Constraint

@page "/SuperCoolPage/{Param:string}"

This is one that continues to confuse me since it feels like it should work. If I can specify an int, a double or even a bool constraint, why can’t I specify that my parameter has to be a string? I’m guessing it’s because any parameter not given a type constraint is inherently handled as a string, but it would be a win for consistency!

As I alluded to above, the fix is to specify the parameter name without a type constraint (i.e. @page "/SuperCoolPage/{Param}") since any value without a type constraint will be handled as a string by default.

If you were trying to set Param as nullable (i.e. string?), the best way to do this is to provide two page directives, one with the parameter and one without; Param will be null when not provided (the first path) and have a value when it is provided (the second path):

@page "/SuperCoolPage"
@page "/SuperCoolPage/{Param}"

Required Parameters Following Optional Parameters

@page "/SuperCoolPage/{OptionalParam:int?}/{RequiredParam:int}"

If you’ve provided a few parameters in a row and a required parameter comes after an optional parameter, you’ll get the error. This is because the router is unable to properly parse a path like “<URL>/SuperCoolPage/1” (using the example above); should it set OptionalParam or RequiredParam?

The fix here is to make sure that all of your required parameters come first. This may mean that you need to shuffle some values around, but it may also be correct in your situation to make optional parameters (such as OptionalParam above) required or to make them both optional depending on context and what works for your application.

Hopefully the above has helped set you on the path to figuring out why you’re seeing this error! If you ran into a different scenario that lead to a NullReferenceException in the Blazor router I’d love to hear about it so that I can add it to the list!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.