Blazor: Handling 404s with IIS

or any other web server

Jonny Olliff-Lee
The Startup

--

Background

Before I get into it, I just want to quickly give some background. If you’d prefer you can just skip ahead to the “The Problem”. At Huddle we have a small internal SPA that our Support team use for tasks that would normally require manual querying on the database or executing stored procedures. This SPA was written by some of our back-end developers in Knockout.js, at the time this was a quick and easy solution.

A few years down the line and the Support team have requested new features to be added, and so rather than continue to support an older framework we decided to move to something more modern. The choices were React (heavily used by our front-end team) or Blazor WebAssembly (from now on I’ll just refer to it as Blazor). As this was still a back-end support feature, the decision was made to try and experiment with Blazor, and so the team began to re-write the SPA.

The Problem

As Blazor is running in the client browser all the routing is handled by Blazor itself, not the web server. The downside of this is that the application has to be running for the routing to take place. So for example if your’re on a page in the app like /users/123 and you accidentally hit refresh you’ve stopped the Blazor app and your web server will try to resolve that URI itself. Most of the time this URI won’t exist and your web server serves up a 404 instead. Far from ideal as your user now has to go back to the root URI for your application and start again. This is what I set out to solve.

The Solution Part 1: IIS URL Rewrite Rules

If you’re not running IIS as your web server, you should be able to get gist of what I’m doing and implement in whatever way you want. Some simple JavaScript a 404 page will achieve the same results.

In IIS we’re going to take advantage of it’s in build URL Rewriting functionality. If you don’t have the necessary IIS component installed a quick Google will provide the instructions on how to.

The steps for setting up you’re new rule are:

1. Select URL Rewrite from the IIS section of your Site in IIS
2. Use the Add Rule(s) action to create a Blank Rule
3. Give it a sensible name, I’m calling mine Support SPA Rewrite
4. Next is setting the Pattern my app is hosted in a Virtual Directory at/supportapp so my pattern is ^supportapp/([a-z].*). It should look something like this:

Pattern field in Add Rewrite rule

5. Next is the most important part of the config for IIS and where I was going wrong for a while. Under the Conditions section you need to click Add and select Is Not a File from the Check if input string menu. Click Ok. It should look like this:

6. Lastly we need to tell IIS what to do when we hit this rule. We keep the default Action type of Rewrite. We set the Rewrite URL to the root of our application, so for me it’s /supportapp?returnUrl={R:1}. Also select Stop processing of subsequent rules.

Rewrite URL

7. Click Apply in the Actions panel on the right and you’re good to go!

Before we move on I just want to quickly go over the ?returnUrl={R:1}part of the Rewrite URL. If we remove this, then the user will be redirected back to the root of the application (and will restart the app) which is a step in the right direction. Ideally though we’d take them back to where they were. So we use the {R:1} part to capture the path in the URI that was not part of the root path. We then add that to a query string param returnUrl. Will then read that query string in Blazor in the next section. You can call this whatever you want, it doesn’t have to be returnUrl.

The Solution Part 2: Blazor app

This section is completely web server agnostic and is purely using the tools and features of Blazor we have available and no JavaScript interop either!

The simplest approach is to make a change to the MainLayout.razor. You put the code that actually does the work where ever you want but for simplicity I’m going to do everything in there.

Firstly you need to inject NavigationManager from Microsoft.AspNetCore into your MainLayout.razor.

@using Microsoft.AspNetCore.WebUtilities@inject NavigationManager _navigationManager

Then you need use one of the lifecycle methods to trigger the reading of the URI and query string. I’m using the OnInitialized() method.

protected override void OnInitialized()
{
// We use NavigationManager to get the current URI
var uri = _navigationManager.Uri;

// Then we ues it again to get just the query string
var queryString = _navigationManager.ToAbsoluteUri(uri).Query;

// We parse the query string into key, value pairs
var queryParams = QueryHelpers.ParseQuery(queryString);

// If there is no query string
// or it doesn't contain returnUrl we continue
if (!queryParams.Any() || !queryParams.ContainsKey("returnUrl"))
{
base.OnInitialized();
return;
}

// We grab the returnUrl from the query string
var returnUrl= queryParams["returnUrl"];

// Finally we use NavigationManager to take us back to that page
_navigationManager.NavigateTo($"supportapp/{returnUrl}");
}

That’s it! Now if you Blazor application receives an URI in the form of /supportapp?returnUrl=users then once the app initialises it will take the user to /supportapp/users.

Almost everything in this can be changed to fit your own naming conventions, code structure etc, this is just a simple demo.

Thanks for reading, and leave any feedback or questions in the comments.

--

--