Blazor: switching Server and WebAssembly at runtime

Sergey Zaikin
ITNEXT
Published in
6 min readMar 14, 2021

--

ASP.NET Core Blazor is a web framework designed to run client-side in the browser on a WebAssembly-based .NET runtime (Blazor WebAssembly) or server-side in ASP.NET Core (Blazor Server), but these two models cannot be used at the same time. More information about hosting models is available in the documentation.

This article describes how to

  • run Server and WebAssembly simultaneously in one application,
  • switch from Server to WebAssembly in runtime without reloading the application,
  • implement universal Cookie-based authentication mechanism,
  • sync Server and WebAssembly state using gRPC.

TL;DR:

Introduction: why we need it

Both hosting models have their pros and cons:

Blazor Server pros:

  • Small size of resources to download (~250 KB).
  • Fast loading.
  • Responsive UI.

Blazor Server cons:

  • Since DOM changes are calculated on the server, a responsive UI needs a reliable and fast connection to the server.
  • If the connection is broken, the application in the browser will stop working.
  • If the server is restarted, the browser application will stop working and the interface state will be lost.
  • Difficult to scale, as the client will only work with the server that stores its state.

Blazor WebAssembly pros:

  • There are no downsides of Blazor Server, as the app runs offline in the browser. For example, you can work offline, or do a PWA.

Blazor WebAssembly cons:

  • Indecently large size: 10–15 MB.
  • Due to this size, it may take 15–20 seconds from following the link to the appearance of the interface (for the first launch), which in the modern world is already beyond acceptable.

It should be noted that pre-rendering is available for both hosting models, and it greatly improves responsiveness, we will use it. But even with pre-rendering enabled for WebAssembly, the interface will remain unresponsive for too long, the same 15–20 seconds for the first launch and 5–10 seconds for repeated ones.

To combine the advantages of Server and WebAssembly, I had the idea to implement a hybrid mode of operation: the application should run in Server mode, and later go into WebAssembly mode unnoticed by the user, for example, when navigating between pages.

Next, I will tell you how I managed to implement this.

Part 1: running Server and WebAssembly simultaneously

We need to start by hosting WebAssembly application inside an ASP.NET Core application and then enabling Pre-rendering.

With this configuration, Blazor starts up in the file _Host.cshtml by adding a component to the page that will create a DOM of our application, and downloading the script that makes our application interactive.

For Server it looks like this:

And for WebAssembly like this:

Therefore, nothing prevents us from loading them at the same time:

Of course this will not work. The point is that the <component> tag turns into html like this:

During application initialization, Blazor looks for this fragment and then replaces it with the application’s DOM. If the scripts blazor.server.js and blazor.webassembly.js are run at the same time, they will both compete for the first component, ignoring the second.

This is easily avoided if we start running blazor.webassembly.js only after blazor.server.js has finished, something like this:

Now both applications start, but do not work correctly. The problem is that both applications subscribe to events (click, submit, onpush, etc.) on document and window. Because of this, Server and WebAssembly try to handle each other's events.

We only want them to listen for events inside their <srvr-app> and <wasm-app> nodes. To do this, we will have to break js best practices and override addEventListener for window and document:

Both apps are now working. All that remains is to wait for WebAssembly to load and enable it by hiding <srvr-app> and displaying <wasm-app>:

Let’s put this logic in a file blazor.hybrid.js and connect it to _Host.cshtml instead of the first two scripts. In the same file we will place a function that will switch the placement model based on a signal from the application. We will call it from the c# code of our application.

From the side of the c# application, let’s create a RuntimeHeader.razor component with the following content:

That’s it, the hybrid app is working. It remains to add a few conveniences, for example, the ability to set the type of the running application in appsettings.json

where HybridType is

Part 2: Authentication

To use Server and WebAssembly at the same time, we need to create an authentication mechanism that works with both models.

Since the server side and the client are located in the same application, Cookie Authentication works well for us.

Let’s configure Startup.cs to use Cookie Authentication as usual.

The problem remains to be solved: when Blazor Server executes client-side code of the application, which makes calls to the API over HTTP, it will use the HttpClient inside its process. This means that for the authorization mechanism to work, we need to add client cookies to this HttpClient instance. Let’s configure Dependency Injection so that it creates the correct HttpClient for the Blazor Server:

API requests that Blazor Server makes to itself within the process will now be authorized.

But in Blazor Server, we cannot use the Set-Cookie HTTP header, because it will set the Cookie for our internal HttpClient. Therefore, for Blazor Server and Blazor WebAssembly, we will create different implementations of the IAuthService interface to force Blazor Server to set a cookie for the client browser.

For WebAssembly WasmAuthService.cs and ServerAuthService.cs for Server.

We now have an authentication mechanism that works with both Blazor Server and Blazor WebAssembly.

Part 3: Synchronizing Server and WebAssembly State

This is a difficult task. If we limit ourselves to switching Server to WebAssembly at the time of navigation, we can skip solving it.

But we will not look for simple ways, and we will synchronize the state of the component Counter.razor using gRPC streaming.

To do this let’s create a gRPC service.

and implement it in CounterService.cs.

See how our application is divinely statically typed, on the client side in Counter.razor we create an instance of ICounterService:

And on the backend side, we see where the SubscribeAsync method is used:

This is possible with protobuf-net.Grpc library, which allows us to use a code-first approach in creating gRPC services, and not write *.proto-files manually.

Let’s configure Dependency Injection to create instances of gRPC services:

Now our DI can create gRPC services. Authorization checks in gRPC services are performed using the [Authorize] attribute, just like in a regular ASP.NET Core controller. In this application, the service WeatherForecastService is marked with this attribute.

Result

As it turns out, making a hybrid ASP.NET Core Blazor app isn’t difficult. The app can be hosted in Kestrel, IIS (IIS only supports HTTPS), and Docker (which uses Kestrel).

The resulting project is available on GitHub.

Also, I have published an image for docker:

> docker run --rm -p 80:80/tcp jdtcn/hybridblazorserver:latest

You can run an application in a container in any of the modes:

> docker run --rm -p 80:80/tcp jdtcn/hybridblazorserver:latest -e HybridType=HybridManual

You can use demo user name and password to log in.

The new Blazor framework gives c# developers endless possibilities for imagination.

Try it, experiment!

--

--