Lately the Blazor technology received a lot of attention both from Microsoft and from the .NET community. Blazor is a UI technology. The ASP.NET team develops it since 2017. Its promise is to develop rich web UI applications with HTML, CSS and C# instead of javascript, that run in all modern browsers without any plugin needed. Blazor can potentially supersede Javascript with C# in the future, this would be a revolution!
Now (September 2020) Microsoft proposes a mature enough Blazor infrastructure. Because running and debugging C# code in all modern browsers cannot be something trivial, when running a Blazor application a lot of things happens under the hood.
Today most of developers use high-level technologies that simplify the development. Personally I started programming with 68000 assembly when I was a teenager in the 80s. As all developers I enjoy high-level technologies like bytecode, runtime, JIT, debugging tools… that simplify my life. But to me it remains essential to understand what’s happening at runtime under my feet. With Blazor it is even more important to understand internals than with other technologies. Blazor can be a double-edged sword depending on the requirement of your application. Not understanding how Blazor works can lead you to wrong decisions.
There is really 2 distinct flavours of Blazor
Actually Microsoft officially proposes 2 ways to host a Blazor application:
- Blazor WebAssembly:
- The browser itself hosts Blazor.
- The browser loads .NET DLLs that contain the compiled IL code.
- Some magics (detailed below) let’s run this compiled IL code in the browser without a plugin. As the name suggests this is possible thanks to the WebAssembly standard.
- In most scenario the application is autonomous. It handles user actions and other events in the browser itself and doesn’t callback the server.
- Blazor Server:
- An ASP.NET Core application hosts Blazor on the server-side.
- A server round-trip is required to handle most of events, including user actions.
- The open-source library ASP.NET Core SignalR handles two-way connection between the browser and the server.
- There is no C# code running on the client side.
Both hosting modes are based on ASP.NET Razor syntax that mixes HTML code, C# code and specific Blazor tags to create dynamic web-pages. Both hosting modes rely on the same razor syntax. However what happens internally to render the page and handles updates is totally different.
Creating a Blazor WebAssembly App and a Blazor Server App Visual Studio projects
The easiet way to get your hands into both Blazor hosting modes is to create a Visual Studio solution that contains a Blazor Server and a Blazor WebAssembly projects. From Visual Studio 2019, choose the Blazor App template:
And then choose between Blazor Server or WebAssembly app. For the WebAssembly app untick ASP.NET Core hosted. You need to install the Microsoft.AspNetCore.Components.WebAssembly.Templates to get the Blazor WebAssembly App project template.
Both projects implement the same simple application. There are few differences in code that makes a massive difference at run-time. For example there is a counter.razor page whose source code is exactly the same in both hosting modes:
It is important to keep in mind that both Blazor projects compiles to regular .NET Core and .NET Standard DLLs. The Output window shows:
1 2 3 4 |
1>BlazorServer -> C:\Users\pat\source\repos\BlazorServer\BlazorServer\bin\Debug\<strong>netcoreapp3.1</strong>\BlazorServer.dll 1>BlazorServer -> C:\Users\pat\source\repos\BlazorServer\BlazorServer\bin\Debug\<strong>netcoreapp3.1</strong>\BlazorServer.Views.dll 2>BlazorWebAssembly -> C:\Users\pat\source\repos\BlazorServer\BlazorWebAssembly\bin\Debug\<strong>netstandard2.1</strong>\BlazorWebAssembly.dll 2>BlazorWebAssembly (Blazor output) -> C:\Users\pat\source\repos\BlazorServer\BlazorWebAssembly\bin\Debug\<strong>netstandard2.1</strong>\wwwroot |
At this time (3rd September 2020) the Blazor server project can already be compiled to .NET 5.0 previews but not the Blazor WebAssembly project.
These DLLs are plainly regular .NET DLLs and NDepend can analyze both Blazor projects. Here is the NDepend dependency graph of these two projects, including all system assemblies referenced. Both page namespaces are selected. The selected pages code consumes directly boxes in blue. It consumes indirectly boxes in light blue.
This graph immediately shows a difference between the two projects: in Blazor Server we have a Data layer that is missing in Blazor WebAssembly. Both projects propose the same weather forecast page through the page FetchData.razor. However the Blazor WebAssembly project obtains this data from a HTTP call to the JSON file sample-data/weather.json. The Blazor Server implementation obtains this data from a WheatherForecastService class that generates random data. A difference at runtime is that with the Blazor WebAssembly version the weather data remains unchanged when you refresh the page, while the weather data changes on page refresh with the Blazor Server version.
Internals of the Blazor WebAssembly App
We saw that with the Blazor WebAssembly hosting mode there is some sort of magic that let’s run your .NET DLL into the browser.
Blazor WebAssembly
First let’s notice that this magic works in all modern browsers without the need for the user to download a plugin. This remark is essential. Indeed this the second time that Microsoft proposes a technology to run .NET code in the browser. The first attempt was the Silverlight technology first released in 2007 to compete with Adobe Flash. However Silverlight, like Flash, both required the user to install a plugin within the browser to run. This need for a plugin adapted to all browsers provoked frictions and leaded to Silverlight deprecation in 2012.
The WebAssembly standard
As is name suggests the Blazor WebAssembly magic relies on the WebAssembly standard (often shortened to Wasm). WebAssembly is a portable binary-code language, much like the .NET IL code. All modern browsers support WebAssembly. WebAssembly was first announced in 2015 and became a World Wide Web Consortium recommendation in December 2019. It is the fourth language to run natively in browser with HTML CSS and Javascript. The goal of WebAssembly is high performance pages: HTML CSS and Javascript are textual languages that require some parse and compile steps before execution. WebAssembly is a binary format that skips these steps.
Moreover JavaScript runs within a controlled environment, called the JavaScript Runtime. This environment provides a single thread for each tab or domain. WebAssembly code also relies on this runtime. It means that a Javascript function can call a WebAssembly function and vice-versa. Here is an article about the internal work done on Firefox to make these two-way cross-call fast.
dotnet.wasm
The IL format and the WebAssembly are 2 binaries format but they are different. Hence Microsoft implemented a .NET Runtime compiled in WebAssembly: dotnet.wasm. dotnet.wasm contains a .NET runtime implementation based on mono runtime that – among several responsibilities – compiles your IL code to execute it within the runtime boundaries.
As you know the .NET runtime is quite a complex piece of software. Microsoft is working on making it smaller and it currently weights 1.878KB or 759 KB when compressed. This represents a problem with Blazor WebAssembly. It consumes a lot of bandwidth, at least the first time it is executed. Then resources can be cached. When running the BlazorWebAssembly project not only the BlazorWebAssembly.dll is transferred but also many system DLLs are transferred in addition to dotnet.wasm. One essential thing to note is that all your code is transferred to client side, thus it must not contain secrets like a connection string.
6.4 MB of data is transferred the first time the page is used in DEBUG mode and 2.2 MB in RELEASE mode, for a lightweight demo application with no heavy resources like image. In 2021 the Google Core Web Vitals will be the next official google ranking factor. It means that your page size will become an official and essential SEO ranking factor. In this context the large bandwidth consumed by Blazor WebAssembly app might become overkill.
Note: You might not see all those DLLs loaded in the Chrome > Dev Tools [F12] > Network. I had this problem in RELEASE mode even with Disable Cache checked. Apparently a recent change in Blazor v3.2.0 provoked that see the answer on stackoverflow about my questions : Why I don’t see DLLs and dotnet.wasm being loaded on a Blazor WebAssembly app in Browser > Dev Tools > Network?
dotnet.webassembly.js
One of the first resource the browser loads is the script dotnet.webassembly.js. This script is responsible for loading system DLLs and booting the .NET runtime. In this article we can see how this script can be hacked to show a loading progress indicator for a Blazor WebAssembly application.
A WebAssembly application cannot manipulate the DOM directly. dotnet.webassembly.js also takes care to wire up all JavaScript interop between your IL code – executed within the WebAssembly context – and the DOM elements. This step is essential because WebAssembly code cannot directly manipulate the DOM.
The Virtual DOM
Refreshing the DOM requires a lot of CPU work. Thus for performance reasons, client-side tools such as React and Angular both implement a Virtual DOM. This virtual DOM is diff-ed with the DOM and only the delta is committed to the DOM. Blazor also relies on such virtual DOM approach. By decompiling both Blazor DLLs we can see that each razor page contains a BuildRenderTree() method generated by the compiler. The method generated is exactly the same in both hosting mode. This method accepts a Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder object and contains code to build the virtual DOM with this builder object. Both hosting modes rely on committing only the delta to the DOM:
- For the Blazor WebAssembly mode this minimizes the Javascript/WebAssembly cross calls and the amount of data exchanged.
- For the Blazor Server mode events and DOM delta are transferred back and forth to the server through the SignalR connection. This minimizes the amount of data transferred both ways.
Debugging a Blazor WebAssembly App
It is possible to debug your .NET code that is executed in the browser only with Chrome (version 70 or later) and Edge (version 80 or later) and with the DEBUG version of your Blazor WebAssembly project. The debugging session can be handled by Visual Studio, Visual Studio Code and Visual Studio for Mac. As shown in the screenshot below it just works with the latest VS 2019 v16.6+ version. It just works thanks to a lot of work done within dotnet.wasm as we can see in the stack trace. More details on debugging Blazor WebAssembly can be found in this Microsoft documentation.
The same microsoft documentation explains the steps to debug your C# code from within the browser. This capability is quite impressive IMHO. Doing so requires to start your browser with remote debugging enabled.
Internals of the Blazor Server App
We’ve seen that the Blazor WebAssembly hosting mode relies on a non-trivial but ingenuous plumbing. In comparison the Blazor Server hosting mode is closer to what ASP.NET Core Developers are used to.
- The browser only deals with javascript, CSS and HTML code.
- It involves no WebAssembly.
- The server executes all compiled C# code. No C# code goes to the client side.
- Only 165KB is transferred, to be compared with the 6.4MB and 2.2 MB transferred in Blazor WebAssembly DEBUG and RELEASE mode.
- The blazor.server.js file is loaded from the _Host.cshtml file. This javascript contains the logic to intercept user actions, like button click, and relies on the SignalR bi-directional connection to ask the server the DOM delta (as explained above). You can track all SignalR messages from the _blazor?xyz connection seen as pending in the Browser Developer Tools > Connection tab.
Blazor WebAssembly vs. Blazor Server: Pros and Cons
Based on what we saw it is easy to compare the two Blazor hosting modes.
Blazor Server Pros
- The page load is lightweight, similar to what we have with other web technologies. This is important since performance is more and more taken account by search engine ranking algorithms.
- The server accesses DB or cloud services easily since the connection secrets live only on the server side.
- Debugging is done on the server side.
- It supports 100% of browser, even those without WASM support like Internet Explorer.
Blazor Server Cons
- You need an ASP.NET Core server.
- It cannot handle Offline and Serverless scenarios.
- Applications used by a lot of users simultaneously can provoque scalability problems. Indeed the server maintains a SignalR connection for each user. Such connection requires around 85KB in process memory per active client.
- Each user interaction provokes a network round-trip and this can lead to latency issues.
Blazor WebAssembly Pros
- C# code executed from within all modern browsers as-is, with no need for extra plugin, bye bye javascript (YEAH!)
- Faster UI thanks to both WebAssembly performances and in-browser computation.
- It supports Serverless and Offline scenario. The whole page can be provided via a Content Delivery Network (CDN) .
Blazor WebAssembly Cons
- Payload: Any real-world page requires several MB of resources to be loaded the first time. Both users with a limited connection and search engine ranking algorithm don’t like that. We can expect progress in this domain and it’ll be interesting to see till how much the .NET runtime and BCL can be compacted.
- The code executed within the browser cannot embed secrets data to access secure resources.
Conclusion
Hopefully you now have a solid idea of what’s happening under the hood. You also understand the differences and similarities between Blazor WebAssembly and Blazor Server projects.
Keep in mind that we can expect significant evolutions in the future.
Blazor potentially will render to something else than HTML, like native controls for example. Daniel Roth the principal program manager for .NET, said in 2019: “Blazor was architected from the beginning so that its renderer was extensible. The default renderer in Blazor renders HTML, and that’s why you use Blazor to build Web applications, not too surprising there, but the renderer can be replaced. In fact you can replace it with a different renderer that renders to whatever you want like. You might render instead to native controls.“. For example you can experiment ElectronNET to build Blazor applications that run on any OS (Windows, Mac, Linux…)
Also Microsoft is actively working on compiling your C# code directly to WebAssembly. This is AOT, Ahead of Time compilation. You can track progresses here. This won’t be available within the .NET 5.0 timeframe but the team is working on this since January 2018 and they’ve made a lot of progress.
You can now start developing with Blazor safely with this knowledge. An excellent and free online source to learn how to code with Blazor is the Blazor University website.
Nice article. One correction: .net windows forms controls could run in internet explorer at one time. That was the first .net browser technology, before silverlight. So this new webassembly approach is the third time .net has been integrated. And this time is the best, since wasm runtimes are in all the browsers, and now we are seeing wasm being used as a sandboxing replacement for code run on servers (much faster to start up and tear down compared to other technologies).