Tuesday, 02 February 2021
Alex Jones
8 minute read
Blazor WebAssembly is a new UI technology from Microsoft, officially released with .NET Core 3.1 and receiving updates in .NET 5. Blazor allows developers to create single-page applications (SPAs) using C# and .NET utilising a component-based architecture. Blazor WebAssembly is a client-side in-browser implementation of Blazor which includes a .NET runtime implemented in WebAssembly.
With the release of Blazor WebAssembly it is now possible to create client-side SPAs using C#. Previously it has been possible to create websites using ASP.NET Core MVC and Blazor Server, with each of these offerings being server-side solutions. If you’re looking to expand your skillset and utilize some new Microsoft technologies, or just have a general interest in WebAssembly, then Blazor WebAssembly is for you.
If you come from a .NET background and want to develop a SPA, then Blazor is a step in the right direction. The benefit of writing a Blazor app is that you can use a lot of the same build tools, ecosystems, and language features that you already use. If you have lots of experience with .NET, then moving to a new language (such as JavaScript or TypeScript) and a new framework (such as Angular or React) requires a lot of investment that might not be feasible.
Following this tutorial, you will learn how to create a SPA in Blazor WebAssembly. It will cover the basics of Blazor and its component-based architecture. Those who are familiar with Razor will recognise some of the template features, and for those that aren’t, this tutorial will cover features like directives and events.
Before starting this tutorial, please ensure that you have the latest .NET 5.0 SDK version installed (version 5.0.100 or greater). If you are using Visual Studio 2019, you will need the latest Visual Studio 2019 update (version 16.8.2 or greater). You can check for updates using the Visual Studio installer.
To create a new Blazor WebAssembly project, use the .NET CLI:
dotnet new blazorwasm
This command creates a working Blazor WebAssembly project with some sample pages. To trigger a rebuild when your code changes, use the following command:
dotnet watch run
The Blazor app will now be running. Because the app loads in the browser, you can open the network tab and view all the files that the Blazor app requests, including the .NET DLLs. For more information on what is included in these requests, and to learn how the app is run from the browser, read this blog post.
Once you start clicking around you will notice that any further requests are only made when you go to the ‘Fetch Data’ page.
Your project should look like this:
@page
directive within them.Program.cs
holds the code that starts your Blazor WebAssembly app and builds up the dependency injection container. For more information on dependency injection, please see the documentation.To add a new page to the app, right click on the ‘Pages’ folder and select the razor component option. In this case we are going to add a page named ‘Movies’ for displaying a list of movies. For Blazor to recognise that this razor component is a page and not a component, add the @page
directive to the top of your file:
@page "/movies"
<h3>Movies</h3>
@code {
}
Routing for this page will now be handled for you, and you will be able to navigate to ‘/movies’ and see your page.
The @page
line you added to your page is known as a directive. Directives are the parts of the Blazor framework that effect the way razor components are compiled. For a more detailed look, visit the ASP.NET Core documentation.
To add a link in the sidebar to your page, we will need to modify the NavMenu.razor file in the Shared folder. In this razor component you will see an html block that looks like this:
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
</ul>
</div>
Within this block we can note a couple of things. The first is the class attribute for the div which contain an expression:
class=”@NavMenuCssClass”
The usage of the @ symbol before the name NavMenuCssClass
indicates that we are using an expression and want to interpret the code following the @ symbol as a piece of C# code. In this case we are referring to a variable in the code block at the bottom of the page.
There is also the @onclick
directive, used to bind a C# method to an onclick event. This means when the html element is clicked, the method to the right of the equals sign is called.
The final thing to note is the use of the NavLink
component. NavLink
isn’t a component that exists within our Shared folder but is provided to us by Blazor. The component is used to render an anchor tag and automatically toggle the active
class when the href of the tag matches the current url.
To add a link to our Movies page, add a new li
element at the end of this html block. Ensure the href
value in the NavLink
component matches our @page
directive value in the Movies component. It should look something like this:
<li class="nav-item px-3">
<NavLink class="nav-link" href="movies">
<span class="oi oi-list-rich" aria-hidden="true"></span> Movies
</NavLink>
</li>
Now when you load your app you should see a new link to your Movies page, and when you click it the browser should successfully navigate to it.
As it stands, there aren’t any movies being displayed and there is no way to add them, so let’s add this functionality. To do this we are going to:
Create a modal component for inputting movie data
Validate an input before we save
If everything is valid, add the movies to a list
To start, add a new razor component to your Shared folder and name it “AddMovieModal”. Within the razor component we need to add:
Some html for inputting data that binds to a model
A button for closing the modal
A button for saving the data
It might look something like this:
<div class="dialog-title">
<h3>Add a movie!</h3>
</div>
<div class="dialog-body">
@if (ShowError)
{
<strong>A movie with that name already exists</strong>
}
<div class="dialog-input">
<label>
Movie Name
</label>
<input @bind="movie.Name" />
</div>
<div class="dialog-input">
<label>
Movie Description
</label>
<input @bind="movie.Description" />
</div>
<div class="dialog-input">
<label>
Movie Rating
</label>
<input @bind="movie.Rating" />
</div>
</div>
<div class="dialog-buttons">
<button class="btn btn-primary" @onclick="Save">Save</button>
<button class="btn btn-secondary" @onclick="@(async () => await Close())">Close</button>
</div>
From top to bottom we have:
An @using directive – References a namespace, meaning we can use the types defined within the namespace
An @if directive – Allows you to write a C# if statement for conditionally displaying html
Three @bind directives – Bind inputs to properties on an object we have initialised in the @code block
Two @onclick events – One references a ‘Save’ method within the @code block, and one uses a lambda expression
NB: The lambda expression within the final onclick()
event illustrates that you don’t have to reference a method but can write inline functions (not recommended).
To provide some functionality to the Movies page, add a code block like the following:
@code {
Movie movie = new Movie();
[Parameter] public List<string> CurrentMovies { get; set; }
[Parameter] public EventCallback OnClose { get; set; }
[Parameter] public EventCallback<Movie> AddMovieToList { get; set; }
public bool ShowError { get; set; }
public async Task Save()
{
if (Exists(movie.Name))
{
ShowError = true;
return;
}
await AddMovieToList.InvokeAsync(movie);
}
public async Task Close()
{
await OnClose.InvokeAsync();
}
public bool Exists(string name)
{
Console.WriteLine($"Checking it exists: {name}");
return CurrentMovies.Any(cm => cm == name);
}
}
There are a couple of new concepts here to go through, mainly revolving around parameters.
The use of the parameter attribute allows you to pass data down to child components and propagate events back up the hierarchy to parent components. If you have used Angular or React before, you will be familiar with this approach. In our case the AddMovieModal component can have the list of existing movie names passed in. When we want to close the modal or update the list, we invoke either the AddMovieToList or the OnClose event. The reason we don’t add a movie to the list from within the component is a design decision, recommended by the Blazor documentation:
A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated.
For your callback event to work, the type of the property that the Parameter attribute is added to must be either a type of EventCallback
or EventCallback<T>
.
Another point worth mentioning is the use of async/await. You don’t need to do anything special to write async functions that can be called from the html; Blazor will be able to handle this out of the box.
One last thing you may notice is the use of Console.WriteLine
that will log the message to your console window in your browser. For other methods of logging and more information, check the Blazor documentation.
To display some movies, we will loop through a collection of movies and conditionally display our newly created child component, AddMovieModal, when we want to add a new movie. The following HTML does just that:
@foreach (var movie in movies)
{
<ul>
<li>@movie.Name</li>
<li>@movie.Description</li>
<li>@movie.Rating</li>
</ul>
}
<button class="btn btn-primary" @onclick="Add">Add a movie!</button>
@if (DisplayModal)
{
<div class="dialog-container">
<div class="dialog">
<AddMovieModal CurrentMovies="MovieNames"
AddMovieToList="AddMovieToList"
OnClose="Close">
</AddMovieModal>
</div>
</div>
}
In this HTML there is a @foreach directive that is used to iterate over a collection, and the @onclick and @if directives we’ve seen before. Within the @if block there is the ‘AddMovieModal’ child component where we pass in our variables and bind our methods for the events.
To add functionality to this page, insert a code block that looks like this:
@code
{
List<Movie> movies = new List<Movie>();
bool DisplayModal { get; set; }
public List<string> MovieNames => movies.Select(m => m.Name).ToList();
public void Add()
{
DisplayModal = true;
}
public void AddMovieToList(Movie movie)
{
movies.Add(movie);
Close();
}
public void Close()
{
DisplayModal = false;
}
}
Now when you go to the /movies page you will be able to click add movies. Some inputs will display, where you can:
Click close to not enter any information
Click add for the movie to be added
The only problem now is that everything looks ugly. To fix this, add a new file in your Pages folder named ‘Movies.razor.css’. In this file, add the following:
.dialog-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.5);
z-index: 2000;
display: flex;
animation: dialog-container-entry 0.2s;
}
.dialog {
background-color: white;
box-shadow: 0 0 12px rgba(0,0,0,0.6);
display: flex;
flex-direction: column;
z-index: 2000;
align-self: center;
margin: auto;
width: 700px;
max-height: calc(100% - 3rem);
animation: dialog-entry 0.4s;
animation-timing-function: cubic-bezier(0.075, 0.820, 0.165, 1.000);
}
::deep .dialog-title {
background-color: #444;
color: #fff2cc;
padding: 1.3rem 2rem;
}
::deep .dialog-title > h2 {
color: white;
font-size: 1.4rem;
margin: 0;
font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;
text-transform: uppercase;
line-height: 1.3rem;
}
::deep .dialog-body {
flex-grow: 1;
padding: 0.5rem 0 1rem 3rem;
}
::deep .dialog-buttons {
height: 4rem;
flex-shrink: 0;
display: flex;
align-items: center;
background-color: #eee;
padding: 0 1rem;
}
::deep .dialog-buttons button {
margin-right: 10px
}
::deep .dialog-body > div {
display: flex;
margin-top: 1rem;
justify-content: space-between;
max-width: 350px;
}
::deep .dialog-body > label {
text-align: right;
width: 200px;
margin: 0 1.5rem;
}
By adding this CSS into the Movie.razor.css file you have isolated your CSS so that it doesn’t interfere with other styles (from components or libraries) and reduces your CSS footprint. The only problem with the styles being isolated within the Movies component is that it cannot style any child components. To be able to style child components, such as our AddMovieModal, add the ::deep
combinator to the rules you need to apply.
Note: Support for CSS preprocessors (such as Sass or Less) aren’t natively supported within CSS isolation, but support can be achieved. More information can be found on how to use them alongside CSS isolation in the documentation.
This tutorial covered some of the fundamentals of Blazor WebAssembly. There are a lot more concepts and features to cover including configuration options, the application lifecycle and route templates. These concepts are all well documented on the ASP.NET Core Blazor documentation.
Last updated: Monday, 19 June 2023
Software Developer
He/him
Alex is a full-stack Software Developer at Rock Solid Knowledge. He works with Angular, SQLServer, and C# on a daily basis.
We're proud to be a Certified B Corporation, meeting the highest standards of social and environmental impact.
+44 333 939 8119