loading...
Cover image for Blazor WebAssembly Full Stack Bootcamp

Blazor WebAssembly Full Stack Bootcamp

_patrickgod profile image Patrick God ・18 min read

This tutorial series is also available as an online video course. You can watch it on YouTube or enroll on Udemy. Or you just keep on reading. Enjoy! :)

Introduction

Blazor WebAssembly is turning the web development world upside down.

With Blazor you can keep coding every part of your web application - meaning front end and back end - with the programming language and the framework you love - C# and .NET.

No need for JavaScript anymore and you can even use the same classes and methods you write for the server as well as for the client.

In this tutorial series, we will dive right into the code by first having a look at the standard example project of Blazor WebAssembly and then we already build the main project of this tutorial series, which is a classic online browser game, where users can create an army of fighters and send them into battle against other users.

Together with some customization options and climbing the leaderboard, this application will teach you how to use Blazor WebAssembly with Razor components in a playful way.

We will have a look at data binding and event handling, communication between components, forms with their built-in components and validation options, how to use views only authorized users can see, how to make calls to a web service, and much more.

Additionally, you will learn how to build the back end of the browser game with a Web API and Entity Framework to store all the data in a SQLite database.

By the end of this tutorial series, you will have what it takes to call yourself a full stack Blazor web developer.

With your new skills, you are ready to conquer any upcoming .NET web development project you want to build yourself or any project that is requested by a recruiter.

Are you ready? Let's start!

Tools

The only tools you need in the beginning are the .NET SDK and Visual Studio.

Depending on when you’re watching this course, you can choose to download the .NET Core 3.1 SDK, the preview or release candidate of .NET 5 - which will combine .NET Core with the older .NET framework, or .NET 5 has already been released, then you’re safe to choose this SDK.

This course will use .NET Core 3.1 because, by the time of writing these lines, this version has been the latest stable release. So to be absolutely safe, please choose this version as well - but there shouldn’t be many differences between .NET Core and .NET 5 if any.

Regarding Visual Studio, you can use the Community Edition of Visual Studio 2019. It’s totally free and provides all the functions we need. If you decide to use a preview version of a .NET SDK, you probably also need a preview of Visual Studio. Just keep that in mind. Otherwise, the latest released version of Visual Studio is perfect.

If you already want to get the necessary tools for the back end, you can download and install Postman, which we will use to test our web service calls later on. Sometimes it’s just nice to test a call to the Web API before you build the corresponding front end.

Additionally, you could already download the DB Browser for SQLite, which enables you to, well, browse through a SQLite database.

But, again, these tools are used later in this tutorial series.

For now, please download and install the .NET SDK of your choice and Visual Studio 2019 Community Edition.

Git Repository on GitHub

One last thing before we start with creating our Blazor WebAssembly project.

You can get the complete code you’ll see during this tutorial series on GitHub.

Here’s the link to the repository: https://github.com/patrickgod/blazorbattles

As you can see, the commits in this repository match the structure of this tutorial series.

Git Commits

So if you’re struggling with your code, please have a look at this repository. I hope it helps to find a solution.

Or just grab the code and build your own browser game with it. Whatever you like.

Anyways, if you still have any problems with the code, you can also reach out to me, of course.

Now let’s create the project.

Jumpstart

Create an ASP.NET Core Hosted Blazor WebAssembly Project

Alright, when you start Visual Studio, choose Create a new project first.

Create a new project

From the templates, we choose a Blazor App. If you can’t find it, just use the search bar on top and look for blazor for instance.

Blazor App

Let’s give this thing a suitable name like BlazorBattles for instance and click Create.

Project name

Now it’s getting interesting. We have a bunch of options available here.

First the .NET SDK version. I’d like to use .NET Core 3.1.

Then we can choose between a Blazor Server App and a Blazor WebAssembly App. Well, according to the title of this tutorial series, we should choose Blazor WebAssembly.

If you don’t know the difference, just real quick: A Blazor Server App runs completely on the server. There are no actual web service calls like you might be used to with a typical web application.

The description says that user interactions are handled over a SignalR connection, meaning over web sockets. So the user doesn’t really download the client app to the browser, which is great.

Blazor Server vs. Blazor WebAssembly

But the big disadvantage is that the server has to handle everything. So if your app has a lot of users, the server has to manage all the interactions of every user, all current states, and so on.

With a Blazor WebAssembly application, the server has a lot less to do, because the client handles the interactions which might be faster for the user - speaking of user experience and offline functionality, for instance - and which is the typical structure of a modern web application.

Maybe you have already built a web application with a .NET back end and a JavaScript framework like Angular, React, or VueJS for the front end. These frameworks handle user interactions as well and make web service calls to the back end.

With Blazor WebAssembly it is the same thing, but you don’t have to write JavaScript.

You can keep writing C# code and even use the same classes for the client and the server. I just love that fact.

So, long story short, we choose the Blazor WebAssembly App.

We don’t need authentication for now. We will implement authentication later by ourselves, which is a great way to learn.

Configure for HTTPS is fine and then comes an important checkbox, we select ASP.NET Core hosted.

Configuration of new project

With that selection, we already get a server project and a shared project additionally to our client project.

We’ll have a look at these projects in the next chapter. For now, it’s just important to know that this checkbox provides a solution where we can already make use of a Web API. So, a full-stack web application in one solution with C# and .NET only.

That’s it.

At the bottom right you can double-check the used .NET (Core) version and then click Create.

Finally create the project

And there’s our new solution. Great!

Let’s open the Solution Explorer on the right and then see what has been created for us.

Solution Overview

So, in the Solution Explorer of Visual Studio, you see three projects, Client, Server, and Shared.

Solution Explorer

The Client project represents the front end. Here’s where all the Blazor WebAssembly magic will happen.

The Server project will be the home of the Web API and Entity Framework.

And the Shared project will be used to share classes between the client and server projects. This means, building a model once and using it for both the client and the server. You can already see the WeatherForecast model here for instance, but we’ll talk about the example application in the next chapter.

Let’s have a look at the client project first.

The Program.cs file with the Main() method is the starting point. We will mainly use this method to register new services we write by ourselves or services that will be added by new packages.

namespace BlazorBattles.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            await builder.Build().RunAsync();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can already see that something is happening here with the HttpClient class, and above that line, a root component is added, which would be the App component we can find in the App.razor file.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
Enter fullscreen mode Exit fullscreen mode

A component in Blazor is a razor file. That's why it's also called a Razor component.

So the App component is the root component where you actually see the use of further components like the Router, the Found and NotFound component, and the RouteView for instance.

The Router component that wraps the other ones, in this case, decides if a route that has been entered in the address bar of the browser is correct or not.

If it is correct, the Found component will be used to display the content of the entered route, if not, the NotFound component will be used. Both components, in turn, use further components, a RouteView and a LayoutView.

And these two make use of the MainLayout. But let’s stick to the LayoutView first. It uses the MainLayout, but the content can already be seen here. It’s a simple text wrapped by a standard HTML paragraph tag.

<LayoutView Layout="@typeof(MainLayout)">
    <p>Sorry, there's nothing at this address.</p>
</LayoutView>
Enter fullscreen mode Exit fullscreen mode

The RouteView though uses the MainLayout but with the content of the actual page, the user wants to see by entering a particular route. And this page can be found in the Pages folder.

We’ll get to that in a second.

Let’s have a quick look at the _Imports.razor file. It’s quite simple, you will find global using directives here. That’s it. If you don’t want to add a reference in a single component, add it here instead.

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorBattles.Client
@using BlazorBattles.Client.Shared
Enter fullscreen mode Exit fullscreen mode

Okay, next, let’s have a look at the MainLayout in the Shared folder.

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Looks like standard HTML at first glance, right?

Well, the only difference is that it inherits from the LayoutComponentBase class. And when you look at this class, you can see that it has a property called Body of type RenderFragment which is used in the MainLayout with @Body. And that’s where the pages will be rendered.

namespace Microsoft.AspNetCore.Components
{
    //
    // Summary:
    //     Optional base class for components that represent a layout. Alternatively, components
    //     may implement Microsoft.AspNetCore.Components.IComponent directly and declare
    //     their own parameter named Microsoft.AspNetCore.Components.LayoutComponentBase.Body.
    public abstract class LayoutComponentBase : ComponentBase
    {
        protected LayoutComponentBase();

        //
        // Summary:
        //     Gets the content to be rendered inside the layout.
        [Parameter]
        public RenderFragment Body { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Additionally, you can see another used component, the NavMenu, but we’ll get to that soon.

Do you already see how these things work together?

A component can be used by adding a tag with its exact name. And any page will be rendered in the @Body part of a Layout.

You could build a custom layout if you like and use that one instead of the MainLayout in the App.razor component. Totally up to you.

Anyways, let’s have a look at the pages, for instance, the Index.razor.

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />
Enter fullscreen mode Exit fullscreen mode

The crucial part is the @page directive with a string that already is the route for that page. You don’t have to specify that route anywhere else. Just create a component, add that @page directive, and you’re done.

@page "/"
Enter fullscreen mode Exit fullscreen mode

It’s the same with the FetchData and the Counter pages.

Before we have a deeper look at the code, a quick word about the other projects.

The server project first consists of a Program.cs and a Startup.cs file.

In the Startup class we find the ConfigureServices() and the Configure() method.

The ConfigureServices() configures the app’s services, so a reusable component that provides app functionality.

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllersWithViews();
            services.AddRazorPages();
        }
Enter fullscreen mode Exit fullscreen mode

We will register services in the future in this method, so they can be consumed in our web service via dependency injection for instance, similar to the Program.cs of the client project.

Please don’t mind all these buzzwords right now…

The Configure() method creates the server app’s request processing pipeline, meaning the method is used to specify how the app responds to HTTP requests.

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebAssemblyDebugging();
            }
            else
            {
                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.UseBlazorFrameworkFiles();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapFallbackToFile("index.html");
            });
        }
Enter fullscreen mode Exit fullscreen mode

As you can see we’re using HttpRedirection, Routing, and so on. With all these Use...() extension methods, we’re adding middleware components to the request pipeline. For instance UseHttpRedirection() adds middleware for redirecting HTTP requests to HTTPS.

Now the Startup class is specified when the app’s host is built. You see that in the Program class in the CreateHostBuilder() method. Here the Startup class is specified by calling the UseStartup() method.

namespace BlazorBattles.Server
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
Enter fullscreen mode Exit fullscreen mode

Regarding the appsettings.json files we only need to know that we can add and modify some configurations here.

More interesting and often used throughout the back end part of this course is the Controllers folder.

Controllers folder

The first controller you see here is the generated WeatherForecast demo controller. We’ll get to the details of controllers later. For now, it’s only important to know that we can already call the Get() method here which is actually done by the FetchData component.

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 7).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
Enter fullscreen mode Exit fullscreen mode

I’d say, we do that next.

Example Project Explained

Let’s run the application first and then talk about some files we haven’t talked about yet.

The great thing about Visual Studio and .NET Core hosted Blazor WebAssembly applications is that you can simply start this whole package with that little play button on top. Let’s do exactly that.

Start application with play button

The actual starting project is the Server project and Visual Studio will fire up IIS Express with a browser of your choice to start the app.

When you have a close look, you see the text “Loading…”. Where does that come from, you might ask?

Loading...

Well, the client project has got this folder called wwwroot which is the root of the actual website, and here next to some cascading style sheets and a favicon you can find the actual index.html. And here you find the loading text.

<app>Loading...</app>
Enter fullscreen mode Exit fullscreen mode

Any resources you want to add later, like icons, images, other stylesheets, and so on, have to be added to this wwwroot folder. We will use this place later.

The crazy thing now is, that you can actually debug the client and the server at the same time.

You see, that the counter page increases a counter.

Increase counter

Let’s have a look at the code.

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
Enter fullscreen mode Exit fullscreen mode

It seems like there’s not much to see, but actually, there’s already happening a lot.

You already know the @page directive.

Then you can use the @currentCount variable.

<p>Current count: @currentCount</p>
Enter fullscreen mode Exit fullscreen mode

This integer variable can also be found in the @code block at the bottom.

private int currentCount = 0;
Enter fullscreen mode Exit fullscreen mode

So in this @code block, you’re free to write C# code and use that code above within your HTML code. With the @currentCount variable you already see how data binding is done in Blazor WebAssembly.

And in the button, you can already see event handling.

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Enter fullscreen mode Exit fullscreen mode

The onclick event, marked with an @ calls the IncrementCount() method. Simple as that.

By the way, if you don’t like having your C# code together with HTML, you can create a code-behind file for your C# code instead.

Now let’s set a breakpoint inside the IncreaseCount() method.

Breakpoint in IncreaseCount() method

We hit the Click me button and hit the breakpoint. Crazy.

Hitting the breakpoint

Now, what’s up with the service? Let’s have a look at the FetchData page.

This page actually makes a call to the Web API of the server project. We can see that in the network tab of the console.

WeatherForecast call in network tab

Looking at the code, we see a lot of new stuff.

@page "/fetchdata"
@using BlazorBattles.Shared
@inject HttpClient Http

<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>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

}
Enter fullscreen mode Exit fullscreen mode

First the using directive. We’re referencing the Shared project here, because we’re using the WeatherForecast class.

Then we inject the HttpClient which enables us to make web service calls.

@using BlazorBattles.Shared
@inject HttpClient Http
Enter fullscreen mode Exit fullscreen mode

Inside the table, we see a foreach loop that uses the forecasts received from the service.

        <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>
Enter fullscreen mode Exit fullscreen mode

We’ll do all this later by ourselves, so please don’t mind my pacing for now if it’s a bit too fast.

In the @code block we see a new method called OnInitializedAsync(). This thing is part of the component lifecycle and is called, as the name may imply, on the initialization of the component.

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }
Enter fullscreen mode Exit fullscreen mode

And in this method we’re using the injected HttpClient to make a Get() call to the WeatherForecast route which would be the WeatherForecastController of the server project, which is, by the way, also using the WeatherForecast class of the Shared project.

Let’s move to that controller and set a breakpoint in the Get() method and then run the app again.

WeatherForecast breakpoint

When we open the FetchData page, the breakpoint in the service is hit. Fantastic!

WeatherForecast breakpoint hit

Now, although being able to debug client and server at the same time is great, I like to run the application in another way.

Because with the way I just showed you, you have to stop and run the project by yourself every single time you made some changes to your code. This slows down development.

So instead, I like to use the Package Manager Console with the dotnet watch run command, which rebuilds the project when any code change has been recognized.

There's only one problem: So far this only works for the server project. If we make any changes to the client project, they won’t be recognized. But, of course, we can fix that.

In the project file of the server, we add a new ItemGroup.

  <ItemGroup>
    <Watch Include="..\Client\**\*.razor" />
  </ItemGroup>
Enter fullscreen mode Exit fullscreen mode

With that little change, the application notices any change happening in any razor file of the client project - additionally to any change in the server project.

Now let’s open the Package Manager Console and then first move to the directory of the server.

Move to Server directory

Now enter dotnet watch run. When the app is running, you already see the URL we have to enter in Chrome.

dotnet watch run

This URL can also be found in the launchSettings.json files of your projects.

Now the breakpoints won’t be hit, but let’s increase the counter by 2 for instance.

private void IncrementCount()
{
    currentCount += 2;
}
Enter fullscreen mode Exit fullscreen mode

The change will be recognized, the app is rebuilt and we just have to reload the page to see the change.

File changed

The same for the server. Let’s return seven forecasts instead of five and reload the page.

    [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 7).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
Enter fullscreen mode Exit fullscreen mode

Now we see seven forecasts.

Seven forecasts

You see, this makes developing your Blazor WebAssembly app a lot faster.

One thing we haven’t talked about yet is the NavMenu. But by now, you may already know how this component works.

You see some data binding and event handling with the ToggleNavMenu() function and the NavMenuCssClass variable.

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorBattles</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<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>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
Enter fullscreen mode Exit fullscreen mode

New though is the NavLink component. This is a built-in component for navigation links.

            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
Enter fullscreen mode Exit fullscreen mode

The description already says it all: It’s a component that renders an anchor tag, automatically toggling its active class based on whether its href matches the current URI. And the href would be the string that is used with the @page directives of your pages.

One last thing I want to show you in this example application is the first way of communication between components.

When we switch to the Index.razor again, you see the use of the SurveyPrompt component with a Title.

<SurveyPrompt Title="How is Blazor working for you?" />
Enter fullscreen mode Exit fullscreen mode

This Title is actually a parameter. So the parent component Index can tell the child component SurveyPrompt anything with the help of parameters.

Looking at the SurveyPrompt component, you see the Title property in the @code block, marked by a [Parameter] attribute.

@code {
    // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string Title { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

And then this Title - provided by the parent - is used in the HTML above.

<strong>@Title</strong>
Enter fullscreen mode Exit fullscreen mode

Alright, that should be it for the example application. Already a lot of input I guess.

Let’s make a new Git repository out of this and then start building our browser game with Blazor.

Initialize Git Repository

To initialize a Git repository, on the bottom right of Visual Studio we click on Add to Source Control and choose Git.

Add to source control

And that’s it actually. Now we’ve got a local repository where we can commit our changes and have a history of them.

We could also publish this repository to any remote one or use GitHub or Azure DevOps, for instance. This could also be a private repository. So if you want to save your code to the cloud feel free to do so.

As mentioned earlier, you will find the code of this tutorial series on GitHub as well. So if you have any trouble or just want to play around with the project, feel free to access this project on GitHub, clone the repo, fork it, star it, and so on.

Alright, that’s it for this part of the Blazor WebAssembly Full Stack Bootcamp. Let’s make a game next.


That's it for the first part of this tutorial series. I hope it already was useful to you. To get notified for the next part, simply follow me here on dev.to or subscribe to my newsletter. You'll be the first to know.

See you next time!

Take care.


Next up: Your first Razor component, communication between components with parameters, event callbacks and services, create a new page, and more!

Thumbnail: vector illustration/Shutterstock


But wait, there’s more!

Discussion

pic
Editor guide