18

A Brief Look into Blazor

In this chapter, we look at Blazor. Blazor is the new kid on the block, enabling full stack .NET. Blazor is a great piece of technology. It is still relatively new, but it improved remarkably between its experimental stage, first official release, and current state. In only a few years, it went from being an idea for the distant future to reality. Daniel Roth, a Principal Program Manager at Microsoft on the ASP.NET team, was most likely the most fervent believer who preached Blazor over that period. For a time, Blazor was the only thing I heard about (or maybe that was the internet spying on me).

Fun fact

Back in the day, we could use server-side JavaScript with classic ASP, making classic ASP the first full stack technology (that I know of).

Blazor is two things:

  • A client-side single-page application (SPA) framework compiling .NET to WebAssembly (Wasm). WebAssembly is a low-level language that runs in the browser at near-native speed. C# compiles to WebAssembly, and then the resulting Wasm code is sent to the browser. This is known as the Blazor WebAssembly hosting model.
  • A client-server link over SignalR that acts as a modern UpdatePanel with superpowers. SignalR enables real-time web features, like server code calling client code over WebSocket. This is known as the Blazor Server hosting model.

    A bit of history

    If you don’t know what an UpdatePanel is, you haven’t missed much. It was an ASP.NET Web Forms control released with .NET Framework 3.5 that helped run AJAX calls “automagically.”

Blazor also comes bundled with Razor components (why not Blazor components? I don’t know). It has some experimental projects revolving around it, a growing ecosystem of libraries accessible through NuGet, and a BlazorWebView control in .NET Multi-platform App UI (MAUI).

Now that I have laid that out, the following topics are covered in this chapter:

  • Overview of Blazor Server
  • Overview of Blazor WebAssembly
  • Getting familiar with Razor components
  • The Model-View-Update pattern
  • A medley of Blazor features

Overview of Blazor Server

Blazor Server is an ASP.NET Core web application that initially sends a page to the browser. Then, the browser updates part of the UI over a SignalR connection. The application becomes an automated AJAX client-server app on steroids. It is a mix of classic web apps and a SPA model, where the client loads the UI pieces to update from the server. So, less processing for the client and more processing for the server. There can also be a short delay (latency) since you must wait for a server response (steps 2 to 4); for example:

  1. You click a button in the browser.
  2. The action is dispatched to the server through SignalR.
  3. The server processes the action.
  4. The server returns the HTML diff to the browser.
  5. The browser updates the UI using that diff.

To make that diff (step 4), the server keeps a graph of the application state. It constructs that graph using components, which translates into Document Object Model (DOM) nodes.

Blazor Server makes stateful applications that must keep track of the current state of all visitors. It may be hard to scale up or would cost a lot of money in cloud hosting. I don’t want you to discard the option just yet; the model may fit your application’s needs. Moreover, paying more for hosting can save development costs, depending on many factors.

On the other hand, a Blazor Server application is smaller than a Blazor WebAssembly one because the server only sends the requested page to the browser instead of the whole source code compiled to Wasm binary code. Blazor Server can be used with clients that do not support Wasm since the code is executed on the server, like a classic ASP.NET Core application. Razor components can be used in both Blazor Server and Blazor WebAssembly; we explore them after the Blazor WebAssembly overview.

Blazor Server can also be used to prerender a Blazor WebAssembly app and speed up the initial load time.

Disclaimer

I have not deployed nor participated in building any Blazor Server applications yet. Nonetheless, it looks like an improved remake of Web Forms. That might just be me, but a “magic” SignalR connection, latency, and everything processed in a stateful server sound like going back to the past. I might be wrong. I recommend you do your experiments and research and judge for yourself. I may even change my mind in the future; the technology is still young.

To create a Blazor Server project, you can run the dotnet new blazorserver command. That’s it for Blazor Server.

Next, we look into Blazor WebAssembly, which is way more promising (once again, my opinion).

Overview of Blazor WebAssembly

Before getting into Blazor WebAssembly, let’s look at WebAssembly itself. WebAssembly allows browsers to run code that is not JavaScript (such as C# and C++). Wasm is an open standard, so it is not a Microsoft-only thing. Wasm runs in a sandboxed environment close to native speed (that’s the goal) on the client machine, enforcing browser security policies. Wasm binaries can interact with JavaScript.

As you may have “foreseen” from that last paragraph, Blazor WebAssembly is all about running .NET in the browser! And the coolest part is that it follows standards. It’s not like running VBScript in Internet Explorer (oh, I don’t miss that time). I think Microsoft’s new vision to embrace open standards, open source, and the rest of the world is very beneficial for us developers.

But how does that work? Like Blazor Server and other SPAs out there, we compose the application using components. A component is a piece of UI that can be as small as a button or as big as a page. Then, when a client requests our application, the following happens:

  1. The server sends a more or less empty shell (HTML).
  2. The browser downloads external resources (Wasm binaries, JS, CSS, and images).
  3. The browser displays the application.

It is the same experience as any other web page so far. The difference is that when a user carries out an action, such as clicking a button, the action is executed by the client. Of course, the client can call a remote resource, as you would using JavaScript in React, Angular, or Vue. However, the important part here is that you don’t have to. You can control your user interface on the client using C# and .NET.

A significant advantage of Blazor Wasm is hosting: the compiled Blazor Wasm artifacts are only static resources, so you can host your web application in the cloud almost for free (provisioning Azure Blob storage and a Content Delivery Network (CDN), for example).

That leads to another advantage: scaling. Since each client runs the frontend, you don’t need to scale that part—only the delivery of static assets.

On the other hand, you can also use a server-side ASP.NET application to prerender your Blazor Wasm app if you prefer. That leads to a faster initial load time for the clients at an increased hosting cost.

Nevertheless, there is one significant disadvantage: it runs on .NET. But why would I have a problem with that? That’s blasphemy, right? Well, the browser must download the Wasm version of the .NET runtime, which is massive. Fortunately, the people at Microsoft worked on a way to trim unused parts, so browsers only download the required bits. Blazor also supports lazy-loading Wasm assemblies, so a client doesn’t need to download everything at once. That said, all in all, the minimum download size is still around 2 MB. With high-speed internet, 2 MB is small and fast to download, but it can take a bit longer for people living in a remote area. So, think about your audience before making a choice.

We can also leverage ahead-of-time (AOT) compilation to optimize performance-intensive applications while making the binaries bigger. So far, every version has improved the performance, size, and capabilities, which makes me hopeful to see Blazor shine in the future (at least for .NET developers).

To create a Blazor Wasm project, you can run the dotnet new blazorwasm command.

Next, we explore Razor components and look at what Blazor has to offer.

Getting familiar with Razor components

Everything is a Razor component in Blazor Wasm, including the application itself, which is defined as a root component. In the Program.cs file, that root component is registered as follows:

builder.RootComponents.Add<App>("#app");

The App type is from the App.razor component (we cover how components work later), and the string "#app" is a CSS selector. The wwwroot/index.html file contains a <div id="app">Loading...</div> element that is replaced by the Blazor App component once the application is initialized. #app is the CSS selector identifying an element that has an id="app" attribute. The wwwroot/index.html static file is the default page served to clients; it is your Blazor app starting point. It contains the basic HTML structure of the page, including scripts and CSS. And that’s how a Blazor application is loaded.

The App.razor file defines a Router component that routes the requests to the right page. When the page exists, the Router component renders the Found child component. It displays the NotFound child component when the page does not exist. Here is the default content of the App.razor file:

<Router AppAssembly="@ typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Pages are Razor components, with a @page "/some-uri" directive at the top, similar to Razor Pages. You can use most, if not all, of the same things as you can with Razor Pages to compose those routes.

A Razor component is a C# class that implements the IComponent interface. You can also inherit from ComponentBase, which implements the following interfaces for you: IComponent, IHandleEvent, and IHandleAfterRender. All of these live in the Microsoft.AspNetCore.Components namespace.

Next, we take a look at how to create Razor components.

Creating Razor components

You can create your components anywhere in your project, unlike Razor Pages and view components. I like to create my pages under the Pages directory, so it is easier to find pages. Then you can create the non-page components wherever you see fit.

There are three ways to create a component:

  • Using only C#
  • Using only Razor
  • Using a mix of C# (code-behind) and Razor

You don’t have to pick only one way for the whole application; you can choose one per component. All three approaches end up compiled into a single C# class. Let’s take a look at those three ways of organizing components.

C#-only components

C#-only components are as simple as creating a class. In the following example, our component inherits from ComponentBase, but we could implement only the interfaces we need.

Here is the first component (CSharpOnlyComponent.cs):

namespace WASM
{
    public class CSharpOnlyComponent : ComponentBase
    {
    [Parameter]
    public string? Text { get; set; }

The Parameter attribute allows setting the value of the Text property when consuming the component. Officially, it becomes a component parameter. We see this in action once we are done with this class.

The BuildRenderTree method, next, is responsible for rendering our component:

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "h1");
        builder.AddAttribute(1, "class", "hello-world");
        builder.AddContent(2, Text);
        builder.CloseElement();
    }
  }
}

By overriding this method, we control the render tree. Those changes are eventually translated into DOM changes. Here, we are creating an H1 element with a hello-world class. In it is a text node that contains the value of the Text property.

Sequence numbers

The sequence numbers (0, 1, and 2 in the BuildRenderTree method) are used internally to generate the diff tree that .NET uses to update the DOM. It is recommended to write those manually to avoid performance degradation in more complex code such as conditional logic blocks. See the ASP.NET Core Blazor advanced scenarios link in the Further reading section for more info.

Now, in the Pages/Index.razor page, we can use that component like this:

@page "/"
<CSharpOnlyComponent Text="Hello World from C#" />

The name of the class becomes the name of its tag. That’s automatic; we have nothing to do to make it happen. We can set the value of the properties identified with the Parameter attribute as HTML attributes. In this case, we set the value of the Text property to Hello World from C#. We could mark more than one property with that attribute and use them as we would any normal HTML attribute.

When rendering the page, our component is rendered to the following HTML:

<h1 class="hello-world">Hello World from C#</h1>

With those few lines of code, we created our first Razor component. Next, we will create a similar component using the Razor-only syntax.

Razor-only components

Razor-only components are created in .razor files. They are compiled into a C# class. The default namespace of that class depends on the directory structure where it is created. For example, a component created in the ./Dir/Dir2/MyComponent.razor file generates the MyComponent class in the [Root Namespace].Dir.Dir2 namespace. Let’s look at some code (RazorOnlyComponent.razor):

<h2 class="hello-world">@Text</h2>
@code{
    [Parameter]
    public string? Text { get; set; }
}

If you like Razor, you probably prefer this one already. That listing is straightforward and allows us to code the same component as the previous section but more concisely and with less code. In the @code{} block, we can add properties, fields, methods, and pretty much anything we would in a regular class, including other classes. We can also override ComponentBase methods there if we need to. We can use this component the same way we did in the previous example; the same goes for parameters.

Next is the page consuming the RazorOnlyComponent (Pages/Index.razor):

@page "/"
<CSharpOnlyComponent Text="Hello World from C#" />
<RazorOnlyComponent Text="Hello World from Razor" />

The rendering is also very similar, but we went for an H2 instead of an H1:

<h2 class="hello-world">Hello World from Razor</h2>

And with those few lines of code, we created our second component. Next, we create a hybrid of the two styles.

Razor and C# hybrid components

This third model can separate the C# code (known as code-behind) from the Razor code. This hybrid counterpart leverages partial classes to achieve the same as the other models and generates a C# class.

For this, we need two files:

  • [component name].razor
  • [component name].razor.cs

Let’s remake our previous component for the third time but render an H3 this time. Let’s begin with the Razor code (CodeBehindComponent.razor):

<h3 class="hello-world">@Text</h3>

This code could barely be leaner; we have an H3 tag with the Text property as its content. The .razor file in this model replaces the BuildRenderTree method. The compiler translates the Razor code into C#, generating the BuildRenderTree method’s content for us.

The Text parameter is defined in the following code-behind file (CodeBehindComponent.razor.cs):

public partial class CodeBehindComponent
{
    [Parameter]
    public string? Text { get; set; }
}

It’s the same code as the previous two samples—we just divided it into two files. The key is the partial class. It allows compiling a single class from multiple files. In this case, there is our partial class and the autogenerated one from the CodeBehindComponent.razor file. We can use the CodeBehindComponent in the same way as the other two.

Next is the page that consumes the CodeBehindComponent (Pages/Index.razor):

@page "/"
<CSharpOnlyComponent Text="Hello World from C#" />
<RazorOnlyComponent Text="Hello World from Razor" />
<CodeBehindComponent Text="Hello World from Code-Behind" />

That renders the same way as the others, but as an H3 with different content:

<h3 class="hello-world">Hello World from Code-Behind</h3>

Using code-behind can be very useful for two things:

  • Keeping your .razor file clean of C# code
  • Getting better tooling support

The tooling for .razor files tends to explode on us from time to time, includes weird bugs, or provides half-support. It seems that handling HTML, C#, and Razor in a single file is not as easy as it sounds. On a more positive note, it is getting better, so I can only see more stable tooling in the future. I could see myself writing all the code of a component in a single .razor file if the tooling was on par with the C# tooling (in many scenarios). That would lead to fewer files and closer proximity of all parts of the component (leading to better maintainability).

Next, we take a look at skinning our components with CSS, but with a twist…

CSS isolation

Like other SPAs, Blazor allows us to create CSS styles scoped to a component. That means that we don’t have to worry about naming conflicts.

Unfortunately, this does not seem to work with C#-only components, so we will skin only two of the three components. Each of them has the same CSS class (hello-world). We are about to change the text color by defining simple .hello-world CSS selectors for both.

To achieve that, we must create a .razor.css file named after our component. The following code represents the content of the RazorOnlyComponent.razor.css file (RazorOnlyComponent):

.hello-world {
    color: red;
}

The following code represents the content of the CodeBehindComponent.razor.css file (CodeBehindComponent):

.hello-world {
    color: aqua;
}

As you can see from those two files, they define the same .hello-world selector with a different color.

In the wwwroot/index.html file, the dotnet new blazorwasm template added the following line:

<link href="[name of the project].styles.css" rel="stylesheet" />

That line links the bundled component-specific styles into the page. Yes, you did read bundled. The Blazor CSS isolation feature also bundles all those styles into a single .css file, so the browser only loads one file.

If we load the page, we see this (without the layout):

Figure 18.1 – Output after loading the page

Figure 18.1: Output after loading the page

So it worked! But how? Blazor autogenerated random attributes on each HTML element and used those in the generated CSS. Let’s first look at the HTML output:

<h1 class="hello-world">Hello World from C#</h1>
<h2 class="hello-world" b-cjkj1dpci4>Hello World from Razor</h2>
<h3 class="hello-world" b-0gygcymdih>Hello World from Code-Behind</h3>

Those two highlighted attributes are the “magic” links. Now, with the following CSS code, you should understand their usage and why they have been generated:

/* /CodeBehindComponent.razor.rz.scp.css */
.hello-world[b-0gygcymdih] {
    color: aqua;
}
/* /RazorOnlyComponent.razor.rz.scp.css */
.hello-world[b-cjkj1dpci4] {
    color: red;
}

If you are not too familiar with CSS, [...] is an attribute selector. It allows you to do all kinds of things, including selecting an element with the specified attribute (as in this case). That’s what we need here.

The first selector means that all elements with the hello-world class and an attribute named b-0gygcymdih should have their color updated to aqua. The second selector is the same, but for elements with an attribute named b-cjkj1dpci4 instead.

With that pattern in place, we can define component-scoped styles with a high level of confidence that they won’t conflict with other components’ styles.

Next, let’s explore the life cycle of those components.

Component life cycle

The components, including the root components, must be rendered as DOM elements for the browser to display them. The same goes for any changes that subsequently occur. Two distinct phases compose the components’ life cycle:

  • Initial rendering, when a component is rendered for the first time.
  • Re-rendering, when a component needs to be rendered because it changed.

During the first rendering, if we get rid of the duplicated sync/async methods, the life cycle of a Razor component looks like this:

Figure 18.2 – Lifecycle of a Razor component

Figure 18.2: Life cycle of a Razor component

  1. An instance of the component is created.
  2. The SetParametersAsync method is called.
  3. The OnInitialized method is called.
  4. The OnInitializedAsync method is called.
  5. The OnParametersSet method is called.
  6. The OnParametersSetAsync method is called.
  7. The BuildRenderTree method is called (the component is rendered).
  8. The OnAfterRender(firstRender: true) method is called.
  9. The OnAfterRenderAsync(firstRender: true) method is called.

During re-rendering, if we get rid of the duplicated sync/async methods, the life cycle of a Razor component is leaner and looks like this:

Figure 18.3 – Re-rendered version of a Razor component life cycle

Figure 18.3: Re-rendered version of a Razor component life cycle

  1. The ShouldRender method is called. If it returns false, the process stops here. If it is true, the cycle continues.
  2. The BuildRenderTree method is called (the component is re-rendered).
  3. The OnAfterRender(firstRender: false) method is called.
  4. The OnAfterRenderAsync(firstRender: false) method is called.

    Note

    Don’t worry if you have worked with Web Forms before and dread the Blazor life cycle’s complexity. It is leaner and doesn’t contain any postback. They are two different technologies. Microsoft is trying to push Blazor as the next logical step to migrate from Web Forms (which makes sense based on the current state of .NET), but the only significant similarity that I see is that the component model of Blazor is close to the control model of Web Forms. So if you moved away from Web Forms, don’t be afraid to look into Blazor; they are not the same — Blazor is all that Web Forms is not, with one similarity: they are both component-oriented.

I created a component named LifeCycleObserver in the WASM project (see https://adpg.link/ntqD). That component outputs its life cycle information to the console, leading to the following trick: Console.WriteLine writes in the browser console, like this:

Figure 18.4 – The browser debug console displaying the life cycle of the LifeCycleObserver component

Figure 18.4: The browser debug console displaying the life cycle of the LifeCycleObserver component

Here is an example (highlighted) from the LifeCycleObserver class:

public class LifeCycleObserver : ComponentBase
{
    public override Task SetParametersAsync(ParameterView parameters)
    {
        Console.WriteLine("LifeCycleObserver.SetParametersAsync");
        return base.SetParametersAsync(parameters);
    }
    // Omitted members
}

Next, we look at event handling and how to interact with our components.

Event handling

So far, we’ve displayed the same component built using three different techniques. Now it is time to interact with a component and see how it works. There are multiple events in HTML that can be handled using JavaScript. In Blazor, we can handle most of them using C# instead.

I slightly modified the FetchData.razor page component that comes with the generated project to show you two different event handler patterns:

  • Without passing an argument
  • With an argument

Both ways call async methods, but the same can be done with synchronous methods as well. Let’s now explore that code. I’ll skip some irrelevant markup, such as H1 and P tags, to focus only on the real code, which starts with this:

@page "/fetchdata"
@inject HttpClient Http

At the top of the file, I left the injection of the HttpClient and the @page directive. These allow us to reach the page when navigating to the /fetchdata URL and query resources over HTTP. The HttpClient is configured in the Program.cs file (the composition root). Then, I added a few buttons to interact with. Here is the first:

<button class="btn btn-primary mb-4" @onclick="RefreshAsync">Refresh</button>

All buttons of this code example have an @onclick attribute. That attribute is used to react to click events, like the HTML onclick attribute and the JavaScript "click" EventListener. That button delegates the click event to the RefreshAsync method:

private Task RefreshAsync() => LoadWeatherAsync();
private async Task LoadWeatherAsync()
{
    forecasts = await Http
        .GetFromJsonAsync<WeatherForecast[]>(_uriList.Next());
}

The refresh method then calls the LoadWeatherAsync() method, which in turn queries a resource returning an array of WeatherForecast. The forecasts are three static JSON files located in the wwwroot/sample-data directory. _uriList is an instance of the Cycle class that cycles through a series of strings. Its code is simple but helps simplify the rest of the page in an OOP manner:

private class Cycle
{
    private int _currentIndex = -1;
    private string[] _uris;
    public Cycle(params string[] uris) => _uris = uris;
    public string Next() => _uris[++_currentIndex % _uris.Length];
}

When the forecasts change (when we click on the Refresh button), the component is reloaded automatically, leading to an updated weather forecast table.

We can also access the event argument like we would in JavaScript. In the case of a click, we have access to the MouseEventArgs instance associated with the event. Here is a quick sample displaying possible usage:

<button class="btn btn-primary mb-4" @onclick="DisplayXY">Display (X, Y)</button>
@code {
    public void DisplayXY(MouseEventArgs e)
    {
        Console.WriteLine($"DOM(x, y): ({e.ClientX}, {e.ClientY}) | Button(x, y): ({e.OffsetX}, {e.OffsetY}) | Screen(x, y): ({e.ScreenX}, {e.ScreenY})");
    }
}

In that code, the @onclick attribute is used the same way as before, but the DisplayXY method expects MouseEventArgs as a parameter. The MouseEventArgs argument is provided automatically by Blazor. Then, the method will output the mouse position in the browser’s DevTools console (F12 on Chromium-based browsers), which looks like this:

DOM(x, y): (921, 175) | Button(x, y): (119, 4) | Screen(x, y): (-999, 246)
DOM(x, y): (809, 197) | Button(x, y): (7, 26) | Screen(x, y): (-1111, 268)

To generate those coordinates, I clicked the top-right corner of the button, then the bottom-left corner. As we can deduce from the negative screen x position, my browser was on my left monitor.

Another possibility is to use lambda expressions as inline event handlers. Those lambda expressions can also call a method. Here is an example:

<button class="btn btn-primary mb-4" @onclick="@(e => Console.WriteLine($"DOM(x, y): ({e.ClientX}, {e.ClientY})"))">Lamdba (X, Y)</button>

That button outputs only the client (x, y) coordinate to improve readability.

That’s it for our overview of event handling. Next, we look into another way to manage the component’s state other than each component doing its own thing.

The Model-View-Update pattern

Unless you’ve never heard of React, you’ve most likely heard of Redux. Redux is a library that follows the Model-View-Update (MVU) pattern. MVU comes from The Elm Architecture. If you don’t know Elm, here is a quote from their documentation:

Elm is a functional language that compiles to JavaScript.

Next, let’s see what the goal behind MVU is.

Goal

The goal of MVU is to simplify the state management of applications. If you’ve built a stateful UI in the past, you probably know it can become hard to manage an application’s state. MVU takes the two-way binding complexity out of the equation and replaces it with a linear one-way flow. It also removes mutations from the picture by replacing them with immutable states, where state updates are moved to pure functions.

Design

The MVU pattern is a unidirectional data flow that routes an action to an update function. An update function must be pure. A pure function is deterministic and has no side effects. A model is a state, which must be immutable. A state must have an initial state. A view is the code that knows how to display a state.

Depending on the technology, there are many synonyms. Let’s get into more details. It may sound confusing at first, but don’t worry, it’s not that bad.

An action is called a command or a request in MediatR. It is called an action in Redux, and a message in Elm. I will use action. An action is the equivalent of the commands that we used in our CQRS examples in Chapter 14, Mediator and CQRS Design Patterns. There is no notion of a query in MVU because a view always renders the current state.

Terminology

MediatR

Redux

Elm

Action

Command

Request

Action

Message

An update function is called a handler in MediatR. It is called a reducer in Redux and an update in Elm. I will use reducer. The reducer is a pure function that always returns the same output for any given input (it’s deterministic). The pure function must have no impact on external actors (having no side effects). So, no mutation of external variables, no mutation of the input value: no side effects. One significant advantage of a pure function is testing. It is easy to assert the value of its output based on a given input since it is deterministic.

Terminology

MediatR

Redux

Elm

Reducer

Handler

Reducer

Update

A view is a component in React and Blazor and is a view function in Elm. I will mostly use view because component can be ambiguous and easily confused with Razor components, view components, or the plain notion of a UI component.

Terminology

MediatR

Redux

Elm

View

Component

Component

View function

A model or state cannot be altered and must be immutable. Every time a state changes, an altered copy of the state is created. The current state then becomes that copy. Elm calls the state a model; it is a state in Redux. We are using the term state as I find it defines the intent better.

Terminology

Redux

Elm

State

State

Model

Here is a diagram that represents this unidirectional data flow:

Figure 18.5 – Unidirectional data flow chart

Figure 18.5: Unidirectional data flow chart

  1. When the application starts, the state is initialized. That initial state becomes the current state.
  2. The current state change triggers the UI to render.
  3. When an interaction occurs, such as the user clicking a button, then an action is dispatched to a reducer.
  4. The reducer creates an instance of the updated state.
  5. That new state replaces the current state.
  6. Go back to step 3 of the current list.

It may be hard to wrap your head around this at first. Like all new things, we must take the time to create new paths in our brains to get something fully. Don’t worry; we are about to see that in action.

All in all, it is straightforward; the flow goes only one way. Whenever the state changes, the component is re-rendered. Since states are immutable, we cannot alter them directly, so we must pass by reducers.

Project – Counter

For this project, we will use an open source library that I created in 2020 while experimenting with C# 9 record classes. Since a record is immutable, it is a perfect candidate to represent an MVU state.

Moreover, it allows our example to be streamlined in a limited amount of space.

Note

There are multiple similar libraries, but they were all created before C# 9, so there’s no direct record support.

Context: We are building a counter page to increment and decrement a value.

I know it does not sound very exciting, but since many MVU libraries showcase one, as well as Blazor itself, I believe this is a good way to compare Blazor with others.

First, we need to install the library by loading the StateR.Blazor NuGet package. In this case, we are using a prerelease version.

Redux DevTools

I also installed the StateR.Blazor.Experiments NuGet package in the project. That project has a few experimental features, including a Redux DevTools connector. Redux DevTools is a browser extension that allows the tracking of states and actions. It also allows time travel between states.

Next, let’s code the Counter feature (Features/Counter.cs):

using StateR;
using StateR.Reducers;
namespace WASM.Features
{
    public class Counter
    {
        public record State(int Count) : StateBase;

The State record is our state. It exposes one init-only property. It inherits from StateBase, which is an empty record class. StateBase serves as a generic constraint to make sure the state class is a record class so we can leverage the with expression. States in StateR must be records; it is mandatory.

        public class InitialState : IInitialState<State>
        {
            public State Value => new(0);
        }

The InitialState class, by implementing the IInitialState<State> interface, represents the initial state of the State record.

        public record Increment : IAction;
        public record Decrement : IAction;

Here, we declare two actions. They are records, but they could have been classes instead. Being a record is not a requirement but a shortcut to writing less code. In StateR, an action must implement the IAction interface.

        public class Reducers : IReducer<Increment, State>, IReducer<Decrement, State>

The Reducers class implements the pure functions that handle the actions. In StateR, a reducer must implement the IReducer<TAction, TState> interface. TAction must be an IAction, and TState must be a StateBase. The interface defines only a Reduce method that inputs a TAction and TState and that outputs the updated TState.

        {
            public State Reduce(Increment action, State state)
                => state with { Count = state.Count + 1 };

The Increment reducer returns a copy of State with its Count incremented by 1.

            public State Reduce(Decrement action, State state)
                => state with { Count = state.Count - 1 };
        }
    }
}

Finally, the Decrement reducer returns a copy of State with its Count decremented by 1.

Using a with expression like that makes very clean code, especially if the State record has more than one property. Moreover, the record classes help enforce the immutability of the states, which is in line with the MVU pattern.

That is all that we need to cover the model (state) and the update (actions/reducers). Now to the view (component) portion. The view is the following Razor component (Features/CounterView.razor):

@page "/mvu-counter"
@inherits StatorComponent
@inject IState<Counter.State> State
<h1>MVU Counter</h1>
<p>Current count: @State.Current.Count</p>
<button class="btn btn-primary" @onclick="() => DispatchAsync(new Counter.Increment())">+</button>
<button class="btn btn-primary" @onclick="() => DispatchAsync(new Counter.Decrement())">-</button>

There are only a few lines, but quite a lot of things to discuss here. First, the Razor component is accessible at the /mvu-counter URL.

Then, it inherits from StatorComponent. This is not required, but it is convenient. The StatorComponent class implements a few things for us, including managing the component’s re-rendering when an IState<TState> property changes.

That leads to the next line, the injection of an IState<Counter.State> interface implementation accessible through the State property. That interface wraps the TState instance and gives access to the current state through its Current property. The @inject directive enables property injection in Razor components.

Next, we display the page. @State.Current.Count represents the current count. Following that are two buttons. Both have an @onclick attribute that calls a lambda expression that represents the action to be executed when a user clicks the button. The DispatchAsync method comes from StatorComponent. As its name implies, it dispatches actions through the StateR pipeline. It is similar to the MediatR Send and Publish methods.

Each button dispatches a different action; one is Counter.Increment and the other is Counter.Decrement. StateR knows the reducers and sends the action to the appropriate reducers.

That code creates a centralized state and uses the MVU pattern to manage it. If we need Counter.State elsewhere, we only need to inject it, as we did here, and the same state would be shared between multiple components or classes. In this example, we injected the state in a Razor component, but we could also use the same pattern in any code.

One more thing: we need to initialize StateR. To do that, in the Program.cs file, we need to register it like this:

using StateR;
using StateR.Blazor.ReduxDevTools; // Optional
// ...
builder.Services
    .AddStateR(typeof(Program).Assembly)
    .AddReduxDevTools() // Optional
    .Apply()
;

The builder.Services property is an IServiceCollection. The AddStateR method creates an IStatorBuilder and registers StateR’s static dependencies.

Then, the optional AddReduxDevTools method call links StateR to the Redux DevTools browser plugin that I mentioned previously. That helps to debug applications from the browser. Other optional mechanisms can be added here. A developer could also code their own extensions to add missing or project-specific features. StateR is DI-based.

Finally, the Apply method initializes StateR by scanning the specified assemblies for every type that it can handle. In this case, we are scanning only the Wasm application assembly (highlighted). The initialization is a two-stage process, completed by the Apply method call.

With that in place, we can run the application and play with our counter. I hope that you liked this little piece of Redux/MVU with StateR. If you did, feel free to use it. If you find missing features, bugs, or performance issues, or want to share your ideas, feel free to open an issue on GitHub (https://adpg.link/Z7Ej).

Conclusion

The MVU pattern uses a model to represent the current state of the application. The view renders that model. To update the model, an action is dispatched to a pure function (a reducer) that returns the new state. That change triggers the view to re-render.

The unidirectional flow of MVU reduces state management’s complexity. Having all state changes flowing in the same direction makes it easier to monitor, trace, and debug data flow errors.

Now let’s see how the MVU pattern can help us follow the SOLID principles:

  • S: Each part of the pattern (states, views, and reducers) has its own responsibility.
  • O: We can add new elements without impacting existing ones. For example, adding a new action does not impact existing reducers.
  • L: N/A
  • I: By segregating responsibilities, each part of the pattern implicitly has a smaller surface (interface).
  • D: This depends on how you implement it. Based on what we did using StateR, we depended only on interfaces and DTOs (state and actions).

Next, we take a quick peek at other Blazor information to give you an idea of what is available if you want to get started.

A medley of Blazor features

Your Blazor journey has just begun, and there are so many more features to Blazor than what we covered. Here are a few more possibilities to give you a glimpse of the options.

You can integrate Razor components with MVC and Razor Pages using the Component Tag Helper. When doing so, you can also prerender your applications (the App component) by setting the render-mode attribute to Static, leading to a faster initial render time. Prerendering can also be used to improve search engine optimization (SEO) and the initial load time of the page. The “drawback” is the need for an ASP.NET Core server to execute the prerendering logic.

Another lovely thing about full-stack C# is sharing code between the client and the server. Say we have a web API and a Blazor Wasm application; we could create a third project, a class library, and share the DTOs (API contracts) between the two.

In our component, we can also allow arbitrary HTML between the opening and closing tags by adding a RenderFragment parameter named ChildContent to that component. We can also catch arbitrary parameters and splat them on an HTML element of the component. Attribute splatting in Blazor means accepting multiple parameters in a single Dictionary<string, object> property and splitting them into multiple HTML properties during rendering. In the following code, we capture any non-specified attributes (CaptureUnmatchedValues = true) and render them as HTML attributes. Here is an example combining those two features (Card.razor):

<div class="@($"card {Class}")" @attributes="Attributes">
    <div class="card-body">
        @ChildContent
    </div>
</div>
@code{
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> Attributes { get; set; } = new()
    [Parameter]
    public string? Class { get; set; }
}

The Card component renders a Bootstrap card and allows consumers to set any attributes they want on it. The content between the <Card> and </Card> tags can be anything. That content is rendered inside the div.card-body. The highlighted lines represent that child content.

The Class parameter is a workaround to allow consumers to add CSS classes while enforcing the card class’s presence. The Attributes parameter becomes a catch-all by setting the CaptureUnmatchedValues property of the Parameter attribute to true.

Next is an example that consumes the Card component (Pages/Index.razor):

<Card style="width: 25%;" class="mt-4">
    <h5 class="card-title">Card title</h5>
    <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
    <a href="#" class="card-link">Card link</a>
    <a href="#" class="card-link">Another link</a>
</Card>

We can see that the Card component (the highlighted lines) is filled with arbitrary HTML (from the official Bootstrap documentation). There are two attributes specified as well, a style and a class.

Here is the rendered result:

<div class="card mt-4" style="width: 25%;">
    <div class="card-body">
        <h5 class="card-title">Card title</h5>
        <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
        <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
        <a href="#" class="card-link">Card link</a>
        <a href="#" class="card-link">Another link</a>
    </div>
</div>

The highlighted lines represent the Card component. Everything else is the ChildContent. We can also notice how the attribute splatting added the style attribute. The Class attribute appended the mt-4 class to card. Here is what it looks like in a browser:

Figure 18.6 – The Card component rendered in a browser

Figure 18.6: The Card component rendered in a browser

The Virtualize component allows the number of rendered items to be reduced to just those visible on the screen. You can also control the number of offscreen elements that are rendered to reduce the frequency at which elements are rendered while scrolling. I’ve left a link to the ASP.NET Core Blazor component virtualization documentation page in the Further reading section, at the end of the chapter.

As we saw in the counter project, Blazor has full support for dependency injection. For me, that’s a requirement. That’s also why I learned Angular 2 when it came out and not React or Vue. Blazor’s DI support is way better than all of the JavaScript IoC containers that I have seen so far, so this is a major benefit.

There are many other built-in features in Blazor, including an EditForm component, validation support, and a ValidationSummary component, as you’d expect in any MVC or Razor Pages application, but client-side.

Quick tip

Should you ever need to force the rendering of a component, you can call the StateHasChanged method from ComponentBase.

As mentioned earlier in this chapter, .NET code can interact with JavaScript and vice versa. To execute JavaScript code from C#, inject and use the IJSRuntime interface. To execute C# code from JavaScript, use the DotNet.invokeMethod or DotNet.invokeMethodAsync functions. The C# method must be public static and decorated with the JSInvokable attribute. There are multiple other ways in which C# and JavaScript can interact, including non-static methods. By supporting this, developers can build wrappers around JavaScript libraries or use JavaScript libraries as is. It also means that we can implement features that Blazor does not support in JavaScript or even write browser-optimized code in JavaScript if Blazor is slower in one area or another. This also allows tapping into browsers’ APIs, like the Fetch API, Storage API, and Canvas API.

You can even write 2D and 3D games using a JavaScript wrapper around a canvas (such as BlazorCanvas) or a full-fledged game engine such as WaveEngine.

The last bit of additional information that I can think of is an experimental project named Blazor Mobile Bindings. That project is a Microsoft experiment that allows Blazor to run in a phone app. It allows native performance by wrapping Xamarin.Forms controls with Razor components. It also supports loading Blazor Wasm in a WebView control, allowing for better reusability between mobile and web apps, but at a performance cost.

I’ve left a long list of links in the Further reading section to complement this chapter’s information.

Summary

Blazor is a great new piece of technology that could bring C# and .NET to a whole new level. In its present state, it is good enough to develop apps with. There are two main models; Server and WebAssembly.

Blazor Server links the client with the server over a SignalR connection, allowing the server to push updates to the client whenever needed (such as when a user carries out an action). Blazor WebAssembly is a .NET SPA framework that compiles C# to WebAssembly using AOT compilation or sends the Intermediate Language (IL) code to the browser where a .NET interpreter implemented in WebAssembly interprets that code. That allows .NET code to run in the browser. We can interact with JavaScript using IJSRuntime and vice versa.

Blazor is component-based, meaning that every piece of UI in Blazor is a component, including pages. We explored three ways to create components: C#-only, Razor-only, and a hybrid that combines C# and Razor in two different files. A component can also have its own isolated CSS without the need to worry about conflicts.

We explored the life cycle of a Razor component, which is very simple yet powerful. We also took a look at handling events and how to react to them.

We then dug into the MVU pattern, which is very well suited for stateful user interfaces like Blazor. We used an open source library and leveraged C# 9.0 record classes to implement a basic example.

Finally, we took a look at the other possibilities that Blazor has to offer.

I will close this chapter with a personal opinion. I would like to see a Blazor-like model become the unified way to build user interfaces in .NET. I appreciate writing Razor way more than writing XAML, to name only one other way of writing UI code.

Questions

Let’s take a look at a few practice questions:

  1. Is it true that Blazor Wasm is compiled to JavaScript?
  2. Out of the three methods explored to create a Razor component, which one is the best?
  3. What are the three parts of the MVU pattern?
  4. In the MVU pattern, is it true that it is recommended to use two-way binding?
  5. Can Blazor interact with JavaScript?

Further reading

Here are a few links to build upon what we have learned in the chapter:

The last time I did 2D/3D development was back when XNA was a thing. I also used Ogre3D in C++ for a school project. That said, I hinted about 2D and 3D games in the chapter, so here are a few resources that I found for those of you who are interested:

An end is simply a new beginning

This may be the end of the book, but it is also the beginning of your journey into software architecture and design. No matter who you are, I hope you found this to be a refreshing view of design patterns and how to design SOLID web apps.

Depending on your goal and current situation, you may want to explore one or more application-scale design patterns in more depth, start your next personal project, start a business, apply for a new job, or all of those at the same time. No matter your goal, keep in mind that designing software is technical but also an art. There is rarely one right way of implementing a feature, but multiple acceptable ways of doing so. Experience is your best friend, so keep programming, learn from your mistakes, and move forward. Remember that we are all born knowing next to nothing, so not knowing something is expected; we need to learn. Please ask your teammates questions, learn from them, and share your knowledge with others.

Now that this book is complete, I’ll get back to writing blog posts, so you can always learn new things there (https://adpg.link/blog). Feel free to hit me up on social media, such as Twitter @CarlHugoM (https://adpg.link/twit). I hope you found the book educational and approachable and that you learned many things. I wish you success in your career.

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

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