Web assembly and Blazor: state of the art
I had a first look to Blazor and, more in general, to the Web assembly technologies in 2017. The same year, I’ve written about this topic in the following blog post: Web assembly in .NET Core. After two years, Blazor is near to its first official release, it is no longer experimental, and it is becoming part of the .NET ecosystem. The following article gives some quick updates on Blazor framework.
How Blazor works?
First of all, let’s have a look what’s behind Blazor and how it works using the new Web assembly. The following schema shows the foundations of Blazor:
Web assembly stands at the base of the pyramid and it defines a binary standard format which allows running byte code into the browser. Furthermore, one of the
Web assembly is a standard not chained to the .NET ecosystem, but it has been the first step to bring .NET into the client side development.
The other core actor behind Blazor is the Mono framework. Mono is a .NET runtime, and it is part of the .NET runtimes maintained by Microsoft and the community. Mono is designed for portability, therefore it has been compiled into web assembly starting with the following PR: https://github.com/mono/mono/pull/5924
Finally, the top layer there is Blazor. Blazor is the UI framework that defines the startup process of the UI, and also it implements the infrastructure that allows components to communicate together. Starting from .NET Core 3.0, Blazor will be shipped as a part of the framework.
Overview of a Blazor app
It is possible to create a new Blazor template using the following instructions:
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview5-19227-01
dotnet new blazor -n <web_app_name>
The first command installs the Blazor template pack using the version 3.0.0-preview5-199227-01
of .NET Core. The second command creates a new base project in the current folder with the web_app_name
.
The resulting project and file system will be similar to this:
There are some key parts to notice in the project structure. First of all, the Program
and the Startup
classes: the first one has the following implementation:
As you can see the above-mentioned snippet uses the BlazorWebAssemblyHost
class to initialize a new host using the Startup
class. This approach works very similar manner to the approach used in ASP.NET Core applications, but instead of returning an IWebHost
type it returns a new instance of the IWebAssemblyHostBuilder
interface.
The following code acts using the following namespace Microsoft.AspNetCore.Blazor.Hosting
and resolves the Startup class using the following code.
Let’s proceed by having a look at the Startup class which is decidedly simpler compared with the Startup
class of an ASP.NET Core application:
The Configure
method resolves an instance of the IComponentsApplicationBuilder
interface, and it invokes the AddComponent
method in order to initialize the App
component.
The AddComponent
accepts a generic type which represents the main component, and a DOM selector which corresponds to the tag that is used in the index.html
page to render the component.
Component-centric structure
Blazor, just like a common UI framework, has a component-centric structure. Components are all the UI elements that compose the pages. In the same way, components can be nested and reused in other parts of the UI.
Every file with the .razor extension is a component. Components render the HTML elements but also can contain UI logic and event handling, for example, let’s have a look to the FetchData.razor
file:
The following component fetches some weather forecast data present in the application using an AJAX request, and it renders data in form of a table. As a first step, the component uses the @inject
directive to declare a new HTTP client. Secondly, it declares some HTML elements to render in the page, e.g.: the table which contains the forecast data, and it finally declares the UI logic:
The code mentioned above defines a WeatherForecast
type and an array which will contain the information fetched from the data, secondly, it declares an override async Task OnInitAsync()
function that uses the HttpClient
injected in the component to perform an HTTP call to our data. The OnInitAsync
function is one of the built-in lifecycle methods implemented by default in the base class of the component.
Built-in lifecycle methods
The following table describes the lifecycle methods which are part of the ComponentBase.cs, and can be customized by the extended classes:
Lifecycle methods | Description |
OnInit /OnInitAsync | The method executes code at the initialization step of the component. |
OnParametersSet /OnParametersSetAsync | These two methods called when a component has received parameters from its parent caller and the values are assigned to properties. These methods are executed every time the component is rendered. |
OnAfterRender/OnAfterRenderAsync | These methods are called after a component has finished rendering. The elements and the components references are populated at this point. |
SetParameters | The method can set a custom code that interprets the incoming parameters value in any way required |
Routing
Another essential aspect to notice form the above-described component is the @page "/fetchdata"
directive. This directive is part of the routing mechanism of Blazor. By using the same approach of the routing of ASP.NET Core, it is also possible to add custom parameters in the @page
value: something similar to @page "/fetchdata/{day}"
.
Client-side vs Server-side Hosting model
Blazor provides two different hosting models: the client-side one and the server-side.
The client-side hosting model downloads all the .NET dependencies on the client, therefore it doesn’t have any server-side dependency. It provides full web assembly support and also supports offline scenarios. It is possible to create a client-side Blazor app using the following command:
dotnet new blazor -n <webapp_name>
The server-side hosting model is more light-way in terms of resources download on the client. It uses SignalR and web socket technologies to create a communication channel between the client and the server. Therefore, the code runs on the server; the client sends messages at each operation. It also supports old browsers, but it doesn’t have offline support. It is possible to create a server-side Balzor app using the following command:
dotnet new blazorserverside -n <webapp_name>
The main concrete characteristic of between the client-side and server-side hosting models resides in the Program.Main
method. The following is the snippet related to a client-side app:
This one is related to a server-side app:
As you can see, the first one returns a reference to the IWebAssemblyHost
instance, the second one to an IHostBuilder
instance.
Plus, in case of a server-side app, the Startup
class also adds a service to the IServiceProvider
collection using the services.AddServerSideBlazor()
:
The resulting execution of the two hosting models behaves in two different ways. In the case of the client-side approach we can see the following resulting network behavior:
The client-side app downloads the blazor.webassembly.js
file the mono.wasm file, which is the Mono framework compiled for the web assembly, and it downloads all the .NET dll used by the application: System.dll
, System.Core.dll
, System.Net.Http.dll
…;
On the other side, the server-side app uses a web-socket approach. Therefore the payload downloaded with the page is minimum:
Each interaction with the page triggers a new message in the web socket channel:
Final thoughts
Starting in 2017, Blazor is becoming a standard citizen of the .NET ecosystem. Both Microsoft .NET team and the community are investing a lot of time in this project. You can find 3rd party libraries and other material about Blazor here: https://github.com/AdrienTorris/awesome-blazor#libraries–extensions.
Cover image by Corrado Zeni