© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
N. VermeirIntroducing .NET 6https://doi.org/10.1007/978-1-4842-7319-7_5

5. Blazor

Nico Vermeir1  
(1)
Merchtem, Belgium
 

Blazor, the new kid on the block in the web frontend world since 2018. It understandably gets compared to the likes of Angular, React, and Vue all the time, but it is a whole other beast. For starters, Blazor is not JavaScript based; it is .NET based.

Blazor, in its pure web form, has two flavors. There is Blazor Server and Blazor WebAssembly. No matter what version you prefer, the development experience is the same; you use C# and HTML to build your frontend application. But, haven’t we tried this before? Wasn’t there something called Silverlight that also let us build frontend web applications with C#? There sure was, but Silverlight was based on a plugin system that was Windows only; it could never survive in this day and age where so much of what we do on the Internet happens on mobile devices or on non-Windows devices. Blazor manages to bring us .NET to the web frontend using only open web standards. No plugins are required; Blazor applications run in the same secure sandbox as JavaScript-based frameworks but with added flexibility, depending on your choice of Blazor flavor.

Blazor WebAssembly

Blazor WebAssembly is the version of Blazor that comes the closest to JavaScript frameworks like Angular and React in that the code is executed in the user’s browser instead of on a server. This is however not done by transpiling C# code into JavaScript in a TypeScript kind of way; instead, Blazor makes use of WebAssembly, an open web standard that defines a binary code format for running applications in the browser sandbox. In other words, WebAssembly is a platform that can run applications. WebAssembly, or Wasm, became a W3C recommendation in December 2019. One of the main objectives of Wasm was getting better, even near-native, performance out of web applications. While JavaScript is definitely a powerful language, it still lacks the performance, features, and maturity from the more enterprise-ready managed languages like C# and Java.

After Wasm was officially a supported standard in the most common browsers, someone at Microsoft decided to see if they could get .NET to run on that new platform. After a while, a proof of concept was ready and demoed using a stripped down version of Mono. This proof of concept turned into a development team; the development team turned the demo into a product.

Blazor Wasm is evolving fast, but it is also limited by WebAssembly itself in some ways. The current version of Wasm doesn’t allow direct manipulation of the DOM and has no multithreading. Both limitations are being addressed in future versions of WebAssembly.

Creating a Blazor Wasm Project

.NET 6 comes with default templates for Blazor Wasm applications. Figure 5-1 shows the second step in the wizard where we can provide additional information.
Figure 5-1

Blazor Wasm template wizard

The template can be configured in multiple ways. First there are some built-in authentication types like single user accounts or the Microsoft Identity Platform. Choosing one of these options will adjust the template with boilerplate code that enables either local users or users to log in using their Microsoft account. More information on the built-in security can be found at https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-6.0. Configure for HTTPS is checked by default and sets the configuration so that the application only responds to HTTPS requests.

The second option allows you to host your application in an ASP.NET Core project. By default, a Blazor Wasm project can be a static hosted web application; by adding a server-side component, we still maintain the exact same Blazor client project, but we get extra server-side capabilities. Since Blazor Wasm cannot connect directly to a database, we usually need an API in some form to feed data to the app; in .NET, this is usually an ASP.NET WebAPI. By making your Blazor app ASP.NET Core hosted, you automatically get your WebAPI and Wasm application in one application. Project-wise you will get three projects, the Blazor Wasm client project, the ASP.NET Core server project, and a shared project that can share classes between both. Only the Server project needs to be deployed. Since that project has a reference to the Blazor client project, it will automatically include it in the artifacts that get deployed onto the web server. Figure 5-2 shows a diagram to illustrate the relation between client, server, and shared.
Figure 5-2

Blazor client/server architecture

Blazor Progressive Web Apps

The final option in the project wizard is Progressive Web Application. A Progressive Web Application, or PWA, is a web application that can install itself like a native application. Meaning that it will get an icon and when launched will not show any browser controls anymore, but it will still be “just” a web application. By checking the option in the project wizard, the template will include a manifest.json file. This file enables browsers to install web applications as a PWA. Listing 5-1 shows the default manifest.json included with a new created project. You will find the manifest.json in the Blazor client project under the wwwroot folder.
{
  "name": "BlazorWasmDemo",
  "short_name": "BlazorWasmDemo",
  "start_url": "./",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#03173d",
  "prefer_related_applications": false,
  "icons": [
    {
      "src": "icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    },
    {
      "src": "icon-192.png",
      "type": "image/png",
      "sizes": "192x192"
    }
  ]
}
Listing 5-1

Default manifest file for a Blazor Wasm app

As you can tell, it consists of meta information that the operation system needs to be able to make it look like the web application is installed on the OS. If we launch this application in a browser that supports PWA installation, you will see an icon lighting up allowing us to install the PWA as an app. Figure 5-3 shows the browser option in Microsoft Edge once it detects a manifest.json file. The exact look might be different depending on your browser and browser version.
Figure 5-3

Installing a web app as PWA

Besides a manifest.json file, there’s also a service worker added to the project. Service workers are JavaScript code that acts like a proxy between the application and the network. Service workers enable offline access to web applications by caching data and fetching updated data once the network is available.

More information on building a Blazor-based PWA, including the manifest file and service workers, can be found at https://docs.microsoft.com/en-us/aspnet/core/blazor/progressive-web-app?view=aspnetcore-6.0&tabs=visual-studio.

Exploring the Blazor Client Project

Figure 5-4 shows a default Blazor WASM client project loaded in Visual Studio.
Figure 5-4

A Blazor WASM project

Blazor makes use of the Razor framework that made its debut in ASP.NET MVC. If you have done ASP.NET MVC before, you will recognize a lot of things, but there are some Blazor-specific things in there as well.

Listing 5-2 shows the content of the counter page.
@page "/counter"
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
    private int currentCount = 0;
    private void IncrementCount()
    {
        currentCount++;
    }
}
Listing 5-2

Counter page in the default Blazor template

Razor is a framework that mixes HTML and .NET code. It allows us to declare variables and bind them to HTML elements or trigger .NET methods from HTML elements.

In Razor, an @ sign signals the start of a .NET statement. An example of this is @page "/counter" at the top of the page. Page is an attribute; instead of using square brackets, we use the @ sign again to set this attribute to the counter page. In case of the page attribute, it is used for Blazor’s navigation service. This page can now be accessed through https://the-webapps-url/counter.

The @code directive specifies the code block for this specific page; those code blocks are scoped to the page they are declared in. The sample code declares a private field called currentCount in a normal C# way. That field is bound to in HTML by prefixing it with an @ sign, <p role="status">Current count: @currentCount</p>.

This page also demonstrates updating a data field by calling a method. The HTML button specifies an @onclick event. This type of event is different from the HTML/JavaScript combination you might be used to because of the @ sign. Once again, this signals a .NET statement, in this case calling a method that is declared within this page. The IncrementCount method increases the integer field with 1, immediately updating the UI as a result.

The interesting part here is that the page is not updated through a server callback but rather through updating the DOM; this can be seen in most browser’s dev tools (F12). Looking at the visual tree in dev tools, they often mark the elements that are changing. Figure 5-5 shows this for the counter page right after clicking the button for the fifth time.
Figure 5-5

Updating the DOM after a button click

As you can see, only one element is marked, so only one small part of the page is changing. This results in fast web applications that feel more native than, for example, ASP.NET MVC applications that often rely on page reloads and server callbacks.

Blazor in .NET 6

To see how .NET 6 handles Blazor projects, let’s start at the beginning. As usual a Blazor app starts at Program.cs, shown in Listing 5-3.
using BlazorWasmDemo;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
Listing 5-3

Program class of a Blazor WASM application

Blazor in .NET 6 makes use of top-level statements to trim down the size and complexity of the Program file. As you can see in Listing 5-3, there is no namespace, no class declaration, and no Main method declaration; the Main method is still there but it is hidden away as a syntactic trick; should you inspect the intermediate language, you will find the Main method there. More information on top-level statements can be found here https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/top-level-statements.

This sample application sets the App component as root component for this web application. The system uses selectors like selectors in CSS to select the correct element in the index.html file where to inject the components. In the default template, the #app selector is used. If you open the default index.html file in the wwwroot folder of your project, you will find a div with app as ID. This is the div where our Blazor application will be hosted.

The App component is listed in Listing 5-4.
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there’s nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
Listing 5-4

Default app component

The App component is the most top-level component in a default Blazor application. It configures the router, to enable page navigation, and considers the router to be similar to a NavigationPage if you’re more used to Xamarin Forms or MAUI development. The router specifies found and not found views; the not found view will be shown whenever navigation triggers an HTTP 404.

Another rootcomponent being added is the HeadOutlet. Its selector specifies that the component is added to the head section of the HTML file rather than replacing it. The component itself allows us to easily set the title of the page to be reflected in the browser’s tab or title bar.

After adding the root components, we register an HTTP client as a scoped service into .NET’s dependency injection framework. We are registering our services here so that we can inject them in our Blazor pages later on as we will see in a minute.

Blazor Component System

As we have seen in Listing 5-4, the App.razor component declares a Router. That Router loads MainLayout.razor. MainLayout.razor is a component that can be best viewed as the application’s template. Listing 5-5 shows the default MainLayout.razor.
@inherits LayoutComponentBase
<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>
    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>
        <article class="content px-4">
            @Body
        </article>
    </main>
</div>
Listing 5-5

MainLayout.razor

The layout specifies where the navigation menu lives, what the grid layout of the page is, and so on. The NavMenu element specified in MainLayout is actually another Blazor component living in our project. You can find it in the Shared folder. Blazor components can be embedded in each other in a very similar way as HTML elements are added on a page. We can even add properties to a component, mark them as Parameter with an attribute, and pass data between components this way. An example of this is included in the default template in the Shared folder. Listing 5-6 shows the SurveyPrompt property as parameter declaration.
[Parameter]
public string? Title { get; set; }
Listing 5-6

Adding parameters to a Blazor component

That parameter can be set as if it was a property on an HTML element as demonstrated in Listing 5-7.
<SurveyPrompt Title="How is Blazor working for you?" />
Listing 5-7

Setting a parameter

MainLayout also contains an @Body placeholder. This placeholder is where all our Blazor pages will be inserted into the layout of the application. Choosing what page to have as initial page is as simple as setting a page’s route to “/”. This is often Index.razor, as is the case in the default template. Listing 5-8 shows a snippet of the default Index.razor page.
@page "/"
<PageTitle>Index</PageTitle>
Listing 5-8

Index.Razor’s page declaration

Since the url to this page is the root url of the application, this page will be initially loaded and placed on the @Body part of MainLayout. The PageTitle we specify here is what will go in the HeadOutlet root component we declared in Program.cs and will become the page title.

If we now go back to the counter page we have previously seen, you can clearly see where the navigation menu comes from and what part of the MainLayout is replaced with the code from the counter component.

Creating Blazor Pages

We have mentioned both Blazor pages and Blazor components before. Blazor pages are components with an @page directive, giving them a url for the routing system to navigate to.

Let’s jump to FetchData.razor, starting with the attributes at the top of the file, listed in Listing 5-9.
@page "/fetchdata"
@inject HttpClient Http
Listing 5-9

Attributes in FetchData.razor

We’ve run into the page attribute before. As a reminder, it configures the route for this page; when navigating to https://<hostname:port>/fetchdata, we will be redirected to this page. The inject attribute is Blazor’s version of dependency injection. It will search for the HttpClient type in the ServiceCollection that we’ve seen in Listing 5-3 and set an instance of that type to the Http member.

In Listing 5-6, we see the Razor code of the FetchData class. As mentioned before, Razor can be explained by HTML mixed C# snippets. Take the if statement for example. It is prefixed with an @ sign, signaling that a code statement will follow. The statement is followed by brackets, embedding the code between the brackets in the statement. This specific statement will prevent a potential NullReferenceException on forecasts. The code loops over the forecasts collection, but if that collection is not loaded yet, it will crash. The interesting part here is that that if statement will actually get reevaluated as soon as the forecasts collection is updated. This is because Blazor has an internal method called StateHasChanged. When this method is triggered, Blazor will re-render its current state, take a diff between the old and the new render tree, and apply that diff to the old one that is still on screen. This results in only a partial refresh of the page instead of a full page reload. StateHasChanged can be called manually by us, but Blazor calls it internally whenever a property on a component changes or when an event on a component is triggered. This results in an easy way to show a “loading” message while data is being fetched from an API.

The foreach statement loops over the forecasts collection and lists its content in an HTML table. This is a great example of how flexible Razor can mix HTML elements with .NET data. We can even call member methods, like the ToShortDateString on the DateTime struct.
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}
Listing 5-10

Razor code

The final part of the FetchData file is the code block. Every Razor file can contain a @code block. This contains most of the logic of a Razor component, and its lifecycle methods.
@code {
    private WeatherForecast[]? forecasts;
    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    }
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}
Listing 5-11

Code block in a Razor file

The most interesting part of Listing 5-11 is OnInitializedAsync. This is one of seven lifecycle methods of a Blazor component. The complete list of lifecycle methods is:
  • OnInitialized

  • OnInitializedAsync

  • OnParametersSet

  • OnParametersSetAsync

  • OnAfterRender

  • OnAfterRenderAsync

  • ShouldRender

As you can see, every lifecycle event, except for ShouldRender , has both a synchronous and an asynchronous version. If you are hooking into one of the lifecycle events and you need to call an async method, you need to use the async version of the lifecycle method. We can even use both the async and the sync version of these lifecycle methods at the same time; they will both execute but there is no guarantee that they will always run or finish in the same order. OnInitialized is called after the component is done loading and all UI components are available; this is the perfect lifecycle event to load initial data from a data service. OnParametersSet is called after parameters change value, usually due to another component that updates the parameter on the current component. OnAfterRender is called after everything is done rendering, but other initializations might still be happening inside other components that we are referencing. And finally, ShouldRender is called right before rendering happens. Microsoft has a nice diagram showing the component lifecycle. I have added that diagram for your reference as Figure 5-6.
Figure 5-6

Razor component lifecycle (Source: Microsoft)

Running a Blazor App

So, now we know how Blazor works. But how do we go from entering a URL in our browser to a running Blazor application? Is it a plugin like Silverlight was? Do we need browsers that support Blazor? The answer is simple. Blazor is not plugin based; it runs on WebAssembly. All we need is a browser that supports Wasm, and all modern browsers do. To understand how Blazor is loaded into a browser, we need to step back and look at a basic web server. A web server in its purest form is a server that hosts a bunch of files. Those files get downloaded to the browser of someone who enters the URL that routes to your webserver. Webservers have had a form of conventions, for example, if no specific html file is specified in the URL, the server will, by default, look for index.html or default.html. Other web servers, like IIS, for example, also take this convention into account; index.aspx is still the startpage of an ASP.NET application. A Blazor WASM application is served as a static website; there’s no need for an IIS server to make the calculations or run the .NET code. All we need is a website that can serve static content, in this case HTML, CSS, JS, and DLL files.

Figure 5-7 shows the generated files when publishing a stand-alone Blazor WASM client application without PWA support from Visual Studio. The deploy step took our wwwroot content and copied it to the output folder; it compiled our .NET code and added that to the output as well.
Figure 5-7

Output after publishing a Blazor WASM application

There is our index.html file! There’s HTML and CSS files right there; no need to compile or compute anything; a static file server can deliver these files to a modern browser; the browser will spin up the WASM runtime and load in the Blazor app. We don’t even need to install .NET 6 into our browser; notice that _framework folder in Figure 5-7? That contains the .NET 6 assemblies; it just ships with our application. Figure 5-8 shows part of the contents of that folder.
Figure 5-8

_ framework folder

There are some interesting files in this folder. I have removed a lot of the files from Figure 5-8 for brevity, but the interesting ones are still there. Before we go into the actual files, notice that there are three versions of the files? That is because the output generates every file in a normal way and twice using a different type of compression. The .gz files are gzipped, while the .br files are compressed with Brotli. It is up to the server that serves our application to detect what the optimal type is for the client requesting our files.

Let’s start with the actual .NET 6 runtime. As you can see, the assemblies are there, in part. All the System.* files are part of the .NET 6 assemblies, while all of the Microsoft.* files are specific for Blazor. When publishing a Blazor application, the framework will run a linker process. This process will remove unreachable or unused code from the generated intermediate language files, and it will remove unused binaries from the output (tree shaking). These operations are not perfect. It is very important to perform a complete end-to-end test of your application after publishing. There is a possibility that the linker was too aggressive and that your application suddenly behaves in unexpected ways. To fix this, the linker can be configured to ignore certain modules by adding nodes to the project file. The complete documentation on how to do this can be found at https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained.

The blazor.boot.json file contains information about the project. It contains a list of assemblies, with hashes to prevent tampering, that need to be loaded in order for the application to start.

blazor.webassembly.js is the glue between the web world, where the HTML lives, and the native world, where .NET lives. As mentioned before, we currently cannot directly access the DOM from within the .NET Blazor code. To work around this, Microsoft created this JavaScript file. Since we cannot interact with the elements in the visual tree from within Blazor, but we can call JavaScript functions through JS Interop, this file can serve as a bridge from Blazor to the visual tree.

BlazorWasmDemo.dll contains the application code we have written.

dotnet.wasm is the native Webassembly code that contains the instructions to load up the .NET runtime environment in the Webassembly sandbox.

Loading a Blazor WASM application means downloading all these files and loading them into the WASM sandbox. At first sight, that might look like a lot but the Blazor team has been hard at work to shrink .NET down as much as possible; combined with linking and tree shaking, they managed to get a Blazor WASM application to a reasonable size when compared with other JavaScript-based SPA frameworks. Figure 5-9 shows the browser’s network tab when opening a published Blazor WASM application, hosted on a static Azure website. This is an actual in-house application we are actively using and developing, so no demo application. Do keep in mind that the size on the screenshots is just for demo purposes, the actual size of your application will be different.
Figure 5-9

Launching a published Blazor WASM app

A total of 9.7 MB was downloaded; this was the first launch of that specific application. Figure 5-10 shows the same metric, but after launching the app for a second time.
Figure 5-10

Launching the app again

Launching the app again takes significantly less time and resources. That is because the browser caches as much as possible. There’s even quite a big chance that your users won’t notice the three seconds it takes to load your application. That is because there is a big difference between speed and the perception of speed. As mentioned before, the first thing loaded into the browser is the index.html file. That file contains logic to show “Loading…,” while the WASM and .NET runtimes are being downloaded and started; replace that with a nice loading animation, and your application will be perceived as loading quite fast.

Blazor Server

Blazor Server is a second flavor of Blazor. It looks and feels very similar to Blazor WASM, but the big difference is in the underlying architecture. Instead of running inside web assembly, it actually runs on a server, hence the name. Blazor server uses a SignalR connection to send requests to a server that handles all the instructions and sends back changes to the DOM. Before we go any deeper, let’s see what SignalR is.

SignalR

SignalR has been around for quite some years. It is a framework that allows developers to easily implement real-time communication between clients and servers. It enables server code to directly call methods on clients instead of clients having to poll for data on the server. A simple example of this is a chat application where client A pushes a new message to the server; the server then calls client B with the new message as parameter. All of this is possible due to Websockets, which is the underlying mechanism of SignalR. But it goes one step further. On platforms that don’t support Websockets, SignalR can automatically fall back to older transport protocols. Because of this fallback functionality and a load of abstractions on top of the Websockets, API SignalR has quickly gained a lot of popularity.

Thanks to SignalR we have server–client and client–server communication. By leveraging this, Microsoft built a server-based web framework that does not do page reloads; instead, they receive a piece of DOM through the SignalR connection. We can see this in action by launching the counter page of the default Blazor Server template. The project template is mostly the same as Blazor WASM, from App.razor to MainLayout down to the code of the components. Just like in Blazor WASM, when you inspect the HTML code and click the counter button, you will see that only the element containing the number is updated; the rest of the page is never reloaded.

There are a few differences in how Blazor Server launches compared to Blazor WASM. Let’s start at the main entrance point of the application, Program.cs.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Listing 5-12

Program.cs from a Blazor Server application

Program.cs in .NET 6 contains top-level statements once again, making for a cleaner file without a class declaration. If you’ve used ASP.NET MVC before, the instructions in this file might look very familiar. That is because the SignalR connection is powered by ASP.NET, so we need to bootstrap that framework as well. Similar to Blazor WASM, we start by registering services in the built-in dependency injection (DI). After registering services, we have basic boilerplate code to enable HTTPS, set up routing, and start the app.

There are two Blazor-specific calls in this file. The first one is builder.Services.AddServerSideBlazor();. This call registers Blazor-specific services into the DI container, services like the NavigationManager for navigation between Razor components or the IJSRuntime to enable JavaScript interop. The exact code of this method can be found on GitHub https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs. The second Blazor-specific call is app.MapBlazorHub();. This call opens the connection to the Blazor SignalR hub. A SignalR hub is a class that contains communication logic between clients and a SignalR server. In this case, the hub will contain the logic that enables receiving instructions and data from a client, generating a DOM diff and sending it back to the client. The SignalR part of Blazor Server is also open sourced on GitHub at https://github.com/dotnet/aspnetcore/tree/main/src/Components/Server/src/BlazorPack.

Another difference from Blazor WASM is the absence of the index.html page from the wwwroot folder; instead, there is a _Host.cshtml file in the Pages folder of the project. Files with the cshtml extension are ASP.NET webpages; since Blazor Server has need for a webserver that supports .NET, we can leverage the power of ASP.NET as well. The _Host.cshtml file is mostly HTML 5 with a namespace and a routing attribute. But it does contain an interesting line that impacts the entire application.
<component type="typeof(App)" render-mode="ServerPrerendered" />
Listing 5-13

Rendering mode in Blazor Server

The component tag specifies where in the host file our actual Blazor application will get rendered. The type parameter sets the startup type of the app. The render mode will specify where the application is rendered and how dynamic it can be. There are three options.
  • Static: All components are rendered into static HTML, meaning that there is no connection to a Blazor SignalR server and pages have no Blazor functionality.

  • Server: The webserver builds the HTML, connects to SignalR, and activates all Blazor functionality. After the server is finished with all that, the browser will receive the HTML and render everything. This is the slowest option but with the most consistent results.

  • ServerPrerendered: This option uses a technique called hydration. Hydration is a known pattern in most popular SPA frameworks; it takes the best of both static and server render modes to find a middle ground between performance and functionality. Hydration is a two-step process; the first step renders static HTML which gives users the illusion of a fast page load. At this point, the page has appeared on screen but there is no Blazor functionality. In the second step, there is a piece of JavaScript in blazor.server.js that will open the connection to the SignalR hub and hydrate the already rendered page with functionality; the page basically re-renders invisible to the user. The blazor.server.js file is a file included in the Microsoft.AspNetCore.Components.Server.dll assembly. It gets injected in your application’s output automatically.

ServerPrerendered is the default option, and looking from an end-user perspective, it is the most interesting one performance vs. functionality-wise. However, do be careful with automated tests. We have run into issues where the test runner is clicking a button before the re-rendering has taken place. The re-rendering usually happens fast, from a human perspective. Automated tests are executed by machines and can happen faster than the re-rendering.

As soon as _Host.cshtml is finished loading, we are in Blazor land. From here on out, everything works exactly the same as Blazor WASM, development-wise. Feel free to compare the code of the Razor components that are in the default templates for server and WASM projects; they are the same components. Since we’ve already discussed Razor components in the Blazor WASM section of this chapter, we won’t go over it again.

Blazor Desktop

Back in Chapter 4, I briefly mentioned that Blazor was also available for building desktop applications. Using Blazor in WinForms or WPF applications is possible, thanks to a control called the BlazorWebView. The BlazorWebView is a component that hosts a WebView2 control, which in its turn is a component that can render HTML based on Microsoft’s Edge browser. The BlazorWebView also knows how to initialize and run Blazor.

Unfortunately at the time of writing, there are no project templates available for WPF or WinForms that include the Blazor setup, so for now we will have to do it manually. For this demo, we will start from a WinForms project. The procedure for WPF is very similar so it shouldn’t be a problem to follow along.

We will start with a new .NET 6-based WinForms application. Once that is created, we start by editing the project file and changing our target SDK from Microsoft.NET.Sdk to Microsoft.NET.Sdk.Razor. In the end, your project file should look like Listing 5-14.
<Project Sdk="Microsoft.NET.Sdk.Razor">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
</Project>
Listing 5-14

The modified project file

Once the SDK is changed, we need to add a NuGet reference to Microsoft.AspNetCore.Components.WebView.WindowsForms. There is a different package for when you are following along for a WPF project; make sure to select the correct one. Figure 5-11 shows both options.
Figure 5-11

WPF and WinForms versions of the WebView package

Note that at the time of writing both of these packages were still in Prerelease. That is because the Blazor Desktop efforts are part of MAUI, Microsoft’s cross-platform mobile framework which we will talk about in Chapter 6. MAUI was supposed to be released together with .NET 6 but they missed that mark. Instead it will launch somewhere in 2022.

Once the NuGet package is installed, we can start adding Blazor files. We start by creating a wwwroot folder in the WinForms project and adding in an index.html and app.css. I have copied these files over from a Blazor WASM project. Next I have copied the Counter.razor component that we have seen before in this chapter. In the end, your project should look like Figure 5-12.
Figure 5-12

Project structure for WinForms with Blazor

An important step is changing the Copy to output property for every file in the wwwroot folder to Copy if newer as demonstrated in Figure 5-13.
Figure 5-13

Changing the copy to output properties

The final step in the process is creating the BlazorWebView and adding it to a form. This can be done through the editor; the BlazorWebView will show up in the toolbox, or it can be done by code. Listing 5-15 shows adding the BlazorWebView by code.
public BlazorForm()
{
    InitializeComponent();
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddBlazorWebView();
    var blazor = new BlazorWebView
    {
        Dock = DockStyle.Fill,
        HostPage = "wwwroot/index.html",
        Services = serviceCollection.BuildServiceProvider(),
    };
    blazor.RootComponents.Add<Counter>("#app");
    Controls.Add(blazor);
}
Listing 5-15

Adding the BlazorWebView to a form

We are adding the BlazorWebView from the constructor. First we create a new instance of ServiceCollection, which is needed for dependency injection inside the Blazor part. We call the AddBlazorWebView extension method on the ServiceCollection to wire up all of the Blazor framework-related services. Next we create an instance of the BlazorWebView WinForms component; we let it dock to all sides so that the BlazorWebView will take up all available space on the form. The HostPage is the index.html file we have copied over from another Blazor project into the wwwroot folder. The Services property is the ServiceCollection where all Blazor services are registered.

Just like before, we add a rootcomponent to the div with ID app. In this case, we add the only component our application currently has, the counter component. We are free to add as many components as we want; we can copy an entire Blazor application in here with the MainLayout, the routing mechanism, and so on.

The final step is adding the BlazorWebView to our form’s list of controls. Once this is done, we can launch the application and we will see that the Blazor counter component is loaded and visible on a native Windows Forms application. Figure 5-14 shows the running application.
Figure 5-14

Blazor running inside a WinForms application

Since this is basically a control running on a form, we are free to mix Blazor components with WinForms controls to create fully hybrid applications.

Wrapping Up

As we have seen, Blazor is Microsoft’s answer to popular client-side frameworks like Angular and React. It allows us to build powerful, dynamic, and beautiful web applications using .NET instead of JavaScript. Blazor comes in different flavors, fully client-side, thanks to the power of WebAssembly of client-server, thanks to real-time communication over SignalR.

Besides being a framework for building web applications, Microsoft has been investigating bringing it to different platforms. The result of that can be found in the Prerelease version of the BlazorWebView. With the BlazorWebView, we can share Blazor components from the web to WPF and WinForms and eventually even to mobile applications with MAUI.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.116.146