Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

ASP.NET Core Blazor is Microsoft's framework for implementing web-based applications, aimed at developers with knowledge of .NET and C#. It exists alongside other frameworks such as ASP.NET Core MVC. About two and a half years after the release of Blazor WebAssembly and based on our experiences from many customer projects at Thinktecture, we want to have a close look at the following questions: What is the current state of the framework? How can you successfully use Blazor? And where does it have limitations?

In diesem Artikel:

cl-neu
Christian Liebel ist Consultant bei Thinktecture. Sein Fokus liegt auf Webstandards und Progressive Web Apps. Er vertritt Thinktecture beim W3C.

Thanks to my colleagues Patrick and Sebastian for their valuable insights into their projects, whose learnings I could consider in this article.

One Blazor in three flavors

The framework currently offers three different execution and hosting models:

First, there is Blazor Server, which relies on a stateful server to run the application. In this case, the rendering of Razor templates to HTML takes place on the server. Via a SignalR connection, user interactions are reported from the web browser to the web server, triggering a re-render there. The server returns a diff of the changed HTML content over the same connection (see Figure 1). Any .NET code can be executed on the server, and all platform-specific interfaces can be used. In addition, the code on the server is protected from third-party inspection.

Figure 1: How a Blazor Server application works

In contrast, Blazor WebAssembly allows the application to run completely on the client as a Single-Page Application (SPA). When the application is invoked, all required source files are loaded from the web server. This can be a static web server, with no special requirements. At runtime, HTTPS and WebSockets connections (e.g. via SignalR) are used to exchange data with APIs (see Figure 2).

Figure 2: In the SPA model, all source files are transferred to the client

The term WebAssembly refers to the byte code of the same name, which can be executed by the JavaScript runtime environments of all common browsers. Code from any programming language can be compiled into this byte code and thus made executable in web browsers. The prerequisite for this is that all interfaces used are also available in the browser. For example, random access to the file system or device interfaces is generally not possible from the browser. With Blazor WebAssembly, .NET assemblies are executed on a version of the Common Language Runtime (CLR) compiled to WebAssembly. If Ahead-of-Time compilation (AoT) is used, most of the C# code is compiled directly to WebAssembly, which can bring performance advantages, for example for performance-critical applications such as image processing.

Finally, there’s a third variety: Blazor Hybrid. Here, the web-based user interface is embedded via WebView in a .NET Multi-platform App UI (MAUI), WPF or Windows Forms application. Rendering is done by the application itself, WebAssembly is not used at all. The source files are delivered as a part of the application package, so the application can be run offline. In addition, as with Blazor Server, all platform-specific interfaces can be accessed.

Blazor WebAssembly is the tool of choice in many cases

In this article, we will mainly focus on Blazor WebAssembly, which seems to be more suitable for many types of web applications: This is because Blazor Server requires the operation of a server as well as a permanent and stable connection with low latency to it. Delays are already noticeable to the user in the range of a few dozen milliseconds. If the connection is lost altogether, the application can no longer be operated at all, as Figure 3 shows. Since the server is stateful, it can only serve a certain number of users simultaneously. This can be a problem for scaling.

Figure 3: If the server connection is interrupted, Blazor Server apps can, by default, no longer be used

Offline scenarios also cannot be reasonably implemented with Blazor Server, as would be desirable for Progressive Web Apps (PWA). With Blazor Hybrid, on the other hand, only desktop or mobile applications can be built, which requires deployments via the usual platform-specific paths such as installers or app stores—these applications cannot be run in the browser. Conversely, however, apps written in Blazor WebAssembly that run in the browser could also be packaged for desktop and mobile platforms, such as via Electron or MAUI. An overview of the above points is provided by Table 1.

Blazor WebAssembly
Blazor Server
Blazor Hybrid
Calling arbitrary platform-specific interfaces
(only if packaged)
(only on server)
Support for offline capability
Executable in browsers
Packageable for mobile/desktop plattorms
Code can be kept secret

Table 1: Overview of the functional features of the individual hosting models

In the projects we have been involved with at Thinktecture, Blazor WebAssembly has predominantly proved to be the more suitable choice. Only in one project where a machine control system was involved Blazor Server was chosen since it is not possible to communicate directly with the unit from the web browser, and the control PC is located directly next to the machine, which is why offline scenarios are not relevant. The approach is also suitable if the binary code is not to be transferred to the user’s computer for confidentiality reasons.

Does it have to be Blazor?

Blazor WebAssembly was first released in May 2020, joining veteran frontend frameworks like Angular. Compared to these, Blazor WebAssembly is still a young framework with corresponding growth potential. With the availability of AoT compilation, server-side prerendering, native dependencies, as well as improvements in interoperability with JavaScript as part of the release of .NET 6, a meaningful level of maturity was reached for the first time in November 2021, so that we at Thinktecture can generally recommend the use of the framework—given suitable conditions in the respective projects. Microsoft is also constantly improving Blazor: With .NET 7 in November 2022, more useful features were added, for example, Blazor components can now be used directly as web components; there have been improvements in routing and data binding. However, some interesting features, such as multithreading, unfortunately missed their way into the release.

The choice of a suitable technology is often made at the very beginning of a project. This depends, among other things, on the experience and development potential of the team of developers. Angular uses many concepts known from the Extensible Application Markup Language (XAML) and of the Windows Presentation Foundation (WPF). The TypeScript programming language used in Angular was designed by Anders Hejlsberg, the same language designer responsible for C#. For these reasons, Angular is also a reasonable choice for a team of .NET developers.

Blazor is not a magical Windows-to-web converter

Where there is only .NET and C# expertise, Blazor may be more appropriate. But even with Blazor, developers can’t get around Hypertext Markup Language (HTML), Cascading Style Sheets (CSS), and, in the end, JavaScript. Non-trivial Blazor projects will still need to interact with JavaScript, especially for integrating existing JavaScript libraries. While there are only a few Blazor components, there are plenty of JavaScript-based graphics libraries, PDF generators, and the like. While they can be used directly from frameworks like Angular, Blazor WebAssembly requires the cumbersome use of the JS interop bridge and often the writing of appropriate binding code in JavaScript. However, JS interoperability has improved significantly with .NET 5: it is now possible to load JavaScript modules directly instead of having to make objects available on the global window object. Also, accesses to JavaScript object references now work sufficiently fast.

Similarly, SPAs written with Blazor WebAssembly must integrate with the same authentication flows as other web applications. Currently, authentication in Blazor is based on the outdated oidc-client.js library, a planned rebuild for .NET 7 unfortunately did not make it into the release.

Blazor also uses the Component-Based Software Engineering (CBSE) approach: An application is broken down into many small, reusable components. The framework has also recently been further developed in this respect, for example, since .NET 5 it has been possible to isolate CSS styles per component, a feature that has been available since the first release of Angular.

Furthermore, the framework runs in the browser, so developers need to be able to understand error messages and debug there as well. Blazor WebAssembly is therefore not a miraculous machine that simply converts .NET apps to the web. It is the same abstraction layer that Angular, React, or Vue represent—including all advantages and disadvantages.

Developers should also use Windows systems. Even though Blazor apps can be implemented with JetBrains Rider or on Visual Studio for Mac, and the support has undergone several improvements, development including debugging only works really well in Visual Studio 2022 on Windows. For example, Hot Reload, the immediate reloading of the application after changes in the source code, only works in this development environment. Rider did not have full support for the new SDK immediately after the release of .NET 7.

Mobile users and firewalls can cause problems

Another decisive factor for the choice of technology is the target group of the application: Is it a publicly accessible application or an internally used tool? The reason for this is the bundle size of Blazor WebAssembly applications. Even a simple Hello World application with Blazor WebAssembly is already 5.7 megabytes large (see Figure 4), while a comparable Angular application starts at about 150 kilobytes. The basis for comparison here are the Hello World applications that are created via dotnet new blazorwasm-empty or ng new. The transferred bytes of the uncompressed productive builds that can be built with ng build or dotnet publish -c release are counted. The used versions were the .NET SDK 7.0.100 and Angular 15.0.0. With activated Brotli compression, the size of Angular can be reduced to about 50 kilobytes, if you additionally switch off culture information in Blazor WebAssembly, you can reach a bundle size of 2 megabytes. Blazor also caches the DLL files, so they don’t have to be downloaded again the next time they are requested. While there have been significant improvements recently, Blazor’s size overhead will never go away entirely: Since .NET isn’t web-based, appropriate translation layers must be included. If the application is also to be used on mobile, the sheer scale of Blazor apps can already be the dealbreaker. The initial runtime performance of Blazor apps is also not ideal: after Blazor boots up, the .NET assemblies are downloaded, thus resulting in a long loading time for the user, during which all they see is a loading screen.

Figure 4: A "Hello World" Blazor application already requires 5.7 megabytes to be transferred.

In one project, the firewall deployed at an end customer prevented the .NET assembly DLL files from being downloaded, rendering the Blazor app completely useless. It is understandable that security solutions consider downloading a large number of DLL files as a threat. Unfortunately, AoT compilation does not remedy this, but actually makes the size situation even worse, as the resulting WebAssembly bundles sometimes become twice as large. In addition, the number of transferred DLLs is only reduced, since for Reflection, for example, .NET assemblies still have to be transferred. Whether this can be further optimized is questionable. In this respect, Blazor WebAssembly is rather unsuitable for B2C solutions where the environment such as firewalls or mobile use cannot be controlled. But even for B2B applications or entirely internal solutions, the size and runtime performance of applications can become problematic: One customer’s employee dialing into the corporate network from home via VPN had to wait about 20 seconds for a Blazor application to launch. Another customer switched from Blazor WebAssembly back to Angular in frustration due to the significantly worse runtime performance, since he had already used Angular before and Blazor offered no advantages.

Built-in solutions for better performance

The server-side prerendering of Blazor WebAssembly, which has been available since .NET 5, can provide a partial remedy here. The page is pre-rendered on the server side, and the finished HTML is transferred to the client (see Figure 5). This in turn requires the operation of a server configured for this purpose. Without prerendering, the source files of the application can be transferred to any static web server and executed from there. Furthermore, developers must additionally test this behavior.

Figure 5: With server-side prerendering, the HTML is already assembled on the server, which reduces the perceived loading time.

Once loaded, however, a Blazor WebAssembly app can achieve roughly the same speed as any other web application—with the same pitfalls that apply to all other frameworks: Such as rendering too much data. An essential factor for runtime performance is the number of displayed nodes in the Document Object Model (DOM). A list with thousands of rows should not be rendered directly. Alternatives are paging or virtualized lists. For the latter, Blazor WebAssembly even offers a built-in solution, unlike Angular and co: The component optimizes the rendering of list data by only actually inserting the currently visible records into the DOM. The underlying data can either be kept in memory or dynamically reloaded as needed. This results in optimal runtime performance even for very long lists. Another option added with .NET 7 is the experimental QuickGrid, which also includes paging.

Conclusion

Blazor WebAssembly is primarily suited for internal enterprise applications where the environment can be clearly controlled, where there is existing .NET and C# expertise, where a move to other approaches is not feasible, and where Windows is used as the development environment.

But this is not to be understood exclusively: One of our customers does use Blazor WebAssembly for a B2C web application. The users tolerate the somewhat longer loading time, which is still shorter than with one or the other Windows application, the customer can develop the app himself as far as possible thanks to C# knowledge and then gets external support for the JavaScript integration and bug fixes in the project.

Due to its unusual architecture and poor scalability, we recommend avoiding Blazor Server if possible and prefer Blazor WebAssembly instead. Only this flavor of the framework fits with other single-page app approaches like Angular, React, or Vue, supports offline capability, and can also be hosted in Electron or MAUI using the exact same source files.

In short, the Blazor WebAssembly concept for forms-over-data business applications works very well, it is sufficiently stable and mature. In addition, Microsoft is constantly developing the framework family further.

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Database Access with Sessions
.NET
KP-round

Data Access in .NET Native AOT with Sessions

.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.
15.11.2023
Old computer with native code
.NET
KP-round

Native AOT with ASP.NET Core – Overview

Originally introduced in .NET 7, Native AOT can be used with ASP.NET Core in the upcoming .NET 8 release. In this post, we look at the benefits and drawbacks from a general perspective and perform measurements to quantify the improvements on different platforms.
02.11.2023
.NET
KP-round

Optimize ASP.NET Core memory with DATAS

.NET 8 introduces a new Garbage Collector feature called DATAS for Server GC mode - let's make some benchmarks and check how it fits into the big picture.
09.10.2023
.NET CORE
pg

Incremental Roslyn Source Generators: High-Level API – ForAttributeWithMetadataName – Part 8

With the version 4.3.1 of Microsoft.CodeAnalysis.* Roslyn provides a new high-level API - the method "ForAttributeWithMetadataName". Although it is just 1 method, still, it addresses one of the biggest performance issue with Source Generators.
16.05.2023
AI
favicon

Integrating AI Power into Your .NET Applications with the Semantic Kernel Toolkit – an Early View

With the rise of powerful AI models and services, questions come up on how to integrate those into our applications and make reasonable use of them. While other languages like Python already have popular and feature-rich libraries like LangChain, we are missing these in .NET and C#. But there is a new kid on the block that might change this situation. Welcome Semantic Kernel by Microsoft!
03.05.2023
.NET
sg

.NET 7 Performance: Regular Expressions – Part 2

There is this popular quote by Jamie Zawinski: Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems."

In this second article of our short performance series, we want to look at the latter one of those problems.
25.04.2023