Setting up a Blazor WebAssembly application

Microsoft Blazor wallpaper

Welcome to “Setting up a Blazor WebAssembly application” post! In this new post I’ll build a simple project in Blazor and explain the basic Blazor components and interactions. The source code of the project I’m going to create in this post is available on GitHub.

Here the posts I wrote about Blazor that help you to learn this new technology better and faster:

Blazor is a framework for building interactive client-side web UI with .NET:

  • Create rich interactive UIs using C# instead of JavaScript.
  • Share server-side and client-side app logic written in .NET.
  • Render the UI as HTML and CSS for wide browser support, including mobile browsers.
  • Integrate with modern hosting platforms, such as Docker.

Using .NET for client-side web development offers the following advantages:

  • Write code in C# instead of JavaScript.
  • Leverage the existing .NET ecosystem of .NET libraries.
  • Share app logic across server and client.
  • Benefit from .NET’s performance, reliability, and security.
  • Stay productive with Visual Studio on Windows, Linux, and macOS.
  • Build on a common set of languages, frameworks, and tools that are stable, feature-rich, and easy to use.

So, here we’re going to start off by looking at the available templates provided by Microsoft to create a new application. Templates are a great way to get started quickly and provide all of the primary building blocks we need for a working an application. Once we have an understanding of the options, we’ll then choose a template as the base for our Blazor Trails app. We’ll build and run the template so we can get a feel for how it behaves, then we’ll strip out all of the unnecessary parts leaving us with only the key components.

As a result, this is a screenshot of the web application I’m going to explain.

Blazor Trails home page: final - Setting up a Blazor WebAssembly application
Blazor Trails home page: final

Setting up the application

So, in other frameworks setting up a new application involves creating everything manually, from scratch. Generally speaking, .NET applications aren’t created this way. Many, if not all, start life being generated from a template. Using a template has many advantages:

  • Developers can have a working application in seconds
  • Boilerplate code is taken care of and doesn’t need to be written for every new application
  • The template serves as a working example of using the framework
  • The process is repeatable, using a template will give you the same starting point time and time again

Then, Blazor comes with two templates which can be used to create new applications. When choosing a template, we’re essentially making the choice of which hosting model we want to use, either Blazor Server or Blazor WebAssembly. In fact, the two available templates are named Blazor Server and Blazor WebAssembly which makes knowing the hosting model they use pretty straightforward. However, we’re going to be using Blazor WebAssembly to build our Blazor Trails application so that is the template type we’re going to focus on in this post.

Blazor WebAssembly template configurations

So, before we actually create the application, I want to talk about the configuration options available for the Blazor WebAssembly template. This template is the more complex of the two available because you can configure it in two modes: hosted or standalone.

The left side shows the projects created when configuring the template in hosted mode. The right shows the project created when configuring the template in standalone mode - Setting up a Blazor WebAssembly application
The left side shows the projects created when configuring the template in hosted mode. The right shows the project created when configuring the template in standalone mode.

Standalone mode

In the standalone mode, which is the default configuration, you will end up with a single Blazor WebAssembly project in the solution. This template is great if you’re looking to build an application which doesn’t need any kind of backend or server element to it, or perhaps you already have an existing API.

Hosted mode

Hosted mode is a little bit more complex. If you enable the ASP.NET Core Hosted option when creating the template, you will end up with three projects in the solution:

  • Blazor WebAssembly project
  • ASP.NET Core WebAPI project
  • .NET Standard class library

In this configuration you are getting a full stack .NET application. A fully functioning backend (ASP.NET Core WebAPI), a place to put code which is shared between the frontend and backend project (.NET Standard class library), and the frontend application (Blazor WebAssembly).

So, I want to highlight that using this configuration does require a .NET runtime to be installed on the host server. You may remember in my previous post I mentioned an advantage of using Blazor WebAssembly was it didn’t require a .NET runtime on the server, that benefit doesn’t apply when you’re using the hosted configuration. This is because there is a full ASP.NET Core WebAPI project in the solution which does need a .NET runtime on the server to function.

Creating the application

So, I want to create a Standalone mode application. There are two way to create a new application using a template, the dotnet CLI (Command Line Interface) or via an IDE (Integrated Development Environment) such as Visual Studio. To create the application, open Visual Studio and follow these steps (there may be slight differences in wording or order of screen on other IDEs):

  1. File > New Project.
  2. From the project templates list select Blazor WebAssembly App.
  3. The next screen allows us to set the name of the project and the solution as well as where the files will be saved on disc. Enter the details and then click Next to move to the next step.
  4. Select .NET5 as Target Framework
Create a new Blazor WebAssebly app - Setting up a Blazor WebAssembly application
Create a new Blazor WebAssebly app
Configure your new Blazor project - Setting up a Blazor WebAssembly application
Configure your new Blazor project
Additional information - Setting up a Blazor WebAssembly application
Additional information

Be sure, ASP.NET Core hosted and Progressive Web Application are not checked.

This will create a new application with the same configuration and folder structure we setup using Visual Studio. At this point you’ve created your first Blazor application, congratulations! Now we have our shiny new application we’re going to look at how we can build and run it.

Building and running the application

When it comes to running .NET applications there are 3 steps that need to happen:

  1. Restore any packages (also referred to as dependencies)
  2. Compile or build the application
  3. Fire up a web server and serve the application

In previous versions of .NET Core these steps needed to be performed manually so you would need to first restore any packages, then build the code, and finally run the app. However, this is no longer the case, we can now jump straight to running the application and either Visual Studio or the .NET CLI will take care of performing the first two steps, if they’re required.

However, it’s always good to understand how to perform these steps yourself manually if the need arises. When using Visual Studio, you can restore packages by right clicking on the solution and selecting Restore NuGet Packages from the context menu. If you’re using the .NET CLI then you can execute the dotnet restore command.

To perform a build from Visual Studio, select Build > Build Solution from the top menu. You can also use a keyboard shortcut to perform the same task, Ctrl+Shift+B. From the .NET CLI you can use the dotnet build command. Performing a build will also perform a package restore, if it’s required, both when using Visual Studio or the CLI. So, having to manually restore packages shouldn’t be an issue.

All that’s left is to run the application. From Visual Studio this can be done in several ways. First, you can press the play button found in the main toolbar. You can also use a keyboard shortcut which is F5. Finally, you can select Debug > Start Debugging from the top menu. Any of the above will run the application and Visual Studio will fire up a browser and load the application automatically.

Local SSL certificate

Depending on what types of application you’ve created and run before, you could see an extra step which asks if you want to trust the development SSL certificate. Answering yes to this will install the development certificate on your machine and this allows the application to be run over https rather than http. I would recommend trusting and installing the development SSL certificate as running sites over https is best practice, even in development as it mimics the live environment.

First run of a boilerplate Blazor app - Setting up a Blazor WebAssembly application
First run of a boilerplate Blazor app

Key components of a Blazor application

While the template has generated a fair few files, some of these files are more important to know and understand than others. In this section, we’re going to look at each of those key files to understand what they do and why they’re important. Then we’re going to remove all of the other files from our project to give us a clean base ready to start building Blazor Trails.


This is one of the most important components of a Blazor WebAssembly application. It can be found in the wwwroot directory of the project and it’s the host page for the Blazor application.

<!DOCTYPE html>

    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="BlazorTrailsWA.styles.css" rel="stylesheet" />

    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    <script src="_framework/blazor.webassembly.js"></script>

  • 8: the tag base is used by Blazors router to understand which routes it should handle
  • 15: the app tag is where the Blazor application will load
  • 19: this div is displayed automatically by Blazor when an unhandled exception occurs
  • 22: Blazors JavaScript runtime which downloads and initializes the application

More details about Index.html

The key element in the index.html file is the link to the Blazor JavaScript runtime, found near the bottom of the page. As we saw previously, this is the file which downloads the .NET WebAssembly based runtime as well as the application and any of its dependencies. Once this is complete it also initializes the runtime which loads and runs the application.

When the application runs its content needs to be outputted somewhere on the page and, by default, this is outputted to the app tag. This is configurable and is setup in the Program.cs file which we will look at in a second. Any default content which exists in the tag will be replaced at runtime with the output from the application. This has a useful benefit; initial content can be used as a placeholder which will be displayed to the user until the application is ready.

If there is ever an unhandled exception caused inside the application, then Blazor will display a special UI which signals to the user that something has gone wrong. This is defined here in the index.html. This can be customized however you would like but the containing element much have an id attribute with the value blazor-error-ui. The default message states there has been a problem and offers the user a button which will cause a full page reload. This is the only safe option at this point as the application will be in an unknown state.

Exception bar in a Blazor application
Exception bar in a Blazor application

Base tag

The final key piece to the index.html file is the base tag. This is an important tag when it comes to client-side routing. The reason this tag is important is that is tells Blazors router what URLs, or routes, are in scope for it to handle.

If this tag is missing or configured incorrectly then you may see some unexpected or unpredictable behavior when navigating your application. By default, the tag is configured with a value of /. This means that the application is running at the root of the domain (for example The router should handle all navigation requests within that domain.

However, if the application was running as a sub-application for example, then the base tag would need to reflect this with a value of /blazortrails/. This would mean the router will only handle navigation requests which start with /blazortrails/.


Just like other ASP.NET Core applications, Blazor apps start off as .NET console apps. What makes them a Blazor application is the type of host they run. In the case of Blazor WebAssembly it runs a WebAssemblyHost. The purpose of the code contained in this file is to configure and create that host, figure 2.9 shows the default configuration of the Program class.

public class Program
    public static async Task Main(string[] args)
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

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

        await builder.Build().RunAsync();
  • 17: create an instance of WebAsseblyHostBlazor
  • 18: define the root component for the application
  • 20: configure and register services with the IServiceCollection
  • 24: build and run an instance of WebAssemblyHost using the configuration defined with the WebAssemblyHostBuilder

More details about Program.cs

There are two critical pieces of configuration happening, the root component for the application is defined and any services are configured and added to the IServiceCollection.

When defining the root components – there can be more than one although that is usually not the case in most applications – we are actually giving the builder two pieces of information.

The first is the type of the root component for the application. By default, this is the App component (which we will look at next). However, you can configure this to be any component you wish.

The second is the place in the host page where we want to inject the application. The standard setup has the application being injected into the app element we looked at previously on the index.html page. But again, you can configure this to be injected anywhere you wish. The argument the builder.RootComponents.Add method takes is a CSS selector which is used to identify the target element where the component will be injected. Specific elements can be targeted such as app or elements with a specific ID, for example, #root-component, or any other valid CSS selector.

Dependency injection

The next line shows the HttpClient being configured and registered with the IServiceCollection making it available to classes and components via dependency injection (DI). Blazor uses the same DI container as other ASP.NET Core apps and allows registering of services using one of 3 lifetimes:

  1. Transient – A new instance is provided each time it’s requested from the service container. Given a single request, if two objects needed an instance of a transient service, they would each receive a different instance.
  2. Scoped – A new instance is created once per request. Within a request you will always get the same instance of the service across the application.
  3. Singleton – An instance is created the first time it’s requested from the service container, or when the Program.Main method is run, and an instance is specified with the registration. The same instance is used to fulfil every request for the lifetime of the application.

The last thing that the Main method does is to take all of the configuration specified with the WebAssemblyHostBuilder and call its Build method. This will create an instance of a WebAssemblyHost which is the heart of your Blazor app. It contains all of the application configuration and services needed to run your app.


This is the root component for a Blazor application, and we saw how this was configured in the Program.Main method in the previous section. This doesn’t have to be the case however; you can configure a different component to be the root component if you wish, or even have multiple root components, you just need to update the configuration in the Program.Main method.

Router component

The App component contains a vital component for building multi-page applications, the Router component. The Router component is responsible for managing all aspects of client-side routing. When an application first starts up, the router will use reflection to scan the applications assemblies for any routable components. Then, it stores information about them in a routing table and whenever a link is click or navigation is triggered programmatically, the router will look at the route which has been requested and try and find a routable component which handles that route. If a match is found, then it will load that component, otherwise it will load a not found template which is specified inside the Router component.

wwwroot folder & _imports.razor

Now, I’m going to cover both of these files in this section as there is not a huge amount to say about them. In fact the _imports.razor file is the one component on this list which is not required to run a Blazor application, but it makes things a lot easier if you do use it.

By convention all ASP.NET Core applications have a wwwroot folder which is used to store public static assets. This is the place where you can put things such as images, CSS files, JavaScript files or any other static files you need. Anything you put in this folder will be published with your application and available at runtime. As I mentioned earlier, this is also where the index.html file is kept.

The _imports.razor file is optional and not required when building a Blazor application, however, it’s really useful to have at least one of these files. The _imports.razor file has a simple job, it contains using statements. What is really great about the way this file works is that it makes those using statements available to all of the components in that directory and any sub-directories. This saves you having to add common using statements to every component in your application.

As I alluded to, you can also have multiple version of this file at different points in your folder structure. So, if you had a structure of BlazorTrails > Features > Home, and you only wanted specific using statements to be applied to components in the Home folder. Then you could add a _Imports.razor file in the Home folder with those using statements and they would only apply there but would still inherit any using statements from _Imports.razor files higher in the structure.

Writing your first components

We’ve had a look at the app created by the template, and we’ve covered each of the key files and, at a high level, what they do. Now it’s time to write some code of our own. As I said at the start of the chapter, we’re going to be building the foundations of the Blazor Trails application.

List of trails
List of trails

The first thing we’ll do is take about how the application files are going to be organized. Next, we’ll remove all of the unneeded files which were generated by the template. This will give us a clean base to start building from. We’ll then define several new components to create what you see, a layout component, a page component and a couple of regular components.

Organizing files using feature folders

Before we start adding our own code, we need to remove all of the unnecessary files generated by the template. By default, the app structure used by the template divides files by responsibility. There’s a Pages folder for routable components. There is a Shared folder for anything which is used in multiple places or is a global concern.

This kind of separation doesn’t scale well and makes adding or changing functionality much more difficult as files end up being spread out all over the place. Instead we’re going to use a system called feature folders to organize our application.

When using feature folders all of the files relating to that feature are all stored in the same place. The has two major benefits, first, when you go to work on a particular feature all of the files you need are in the same place making everything easier to understand and more discoverable.

The second is that it will scale well, every time you add a new feature to the app you just add a new folder, and everything goes in there. You can also arrange each feature with sub-features if they contain a lot of files.

Routable component

The other little thing I like to do when using this organization system with Blazor is to append any routable component with the word Page. The reason is when a feature has several components in it it’s almost impossible, at a glance, to see which one is the routable component. The only real way to know is to open the file and check for the @page directive at the top.

So, start by deleting the Pages and Shared folders along with their contents, then delete the sample-data folder from the wwwroot folder. Also delete most of the contents of the app.css, just leave the import statement for the open iconic styles and the class called #blazor-error-ui and #blazor-error-ui .dismiss.

We also need to delete the last using statement from the _Imports.razor file, @using BlazorTrailsWA.Shared.

Add a new folder at the root of the project called Features, then inside that add a folder called Layout and another called Home. Inside Layout, add a new Razor Component called MainLayout.razor. Inside Home add a new Razor Component called HomePage.razor. Once you’ve done that head back over to the _Imports.razor and add the following using statements.

@using BlazingTrails.Web.Features.Home 
@using BlazingTrails.Web.Features.Layout

Defining the layout

Blazor borrows the concept of a layout from other parts of ASP.NET Core and essentially it allows us to define common UI which is required by multiple pages. Things such as the header, footer and navigation menu are all examples of things you might put in your layout. We also add a reference to a parameter called Body where we want page content to be rendered. This comes from a special base class which all layouts in Blazor must inherit from called LayoutComponentBase. The following image shows an example of what might be defined in a layout along with where the rendered page content would be displayed.

An example layout defining shared UI
An example layout defining shared UI

You don’t have to stick with a single layout for your whole application either, you can have multiple layouts for different parts of your app. So, if you wanted a particular layout for the public facing pages but a different one for the admin pages, you can do that. In Blazor the default layout is defined on the Router component. This will automatically be applied to all pages in the application.

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

In line 3 and 6 you see the default layout is defined by passing the type of the component you wish to use. If you want to use a different layout on certain pages, you can specify an alternative by applying the @layout directive. This goes at the top of the page and you pass the type of the component you wish to use. For example, if we had an alternative layout called AdminLayout, our layout directive would look like this: @layout AdminLayout.

Main layout

We’re going to be updating the MainLayout component. To start with we are going to do two things; First, we’re going to use the @inherits directive to inherit from the LayoutComponentBase class. This will mark this component as a layout component and will give us access to the Body parameter. Second, we’re going to define where our page content is rendered using the Body parameter.

@inherits LayoutComponentBase
<main class="container">

The only thing we’re missing from our layout now is the header. We’re going to define this as a separate component and as it’s part of the overall Layout feature it will go in the Layout feature folder next to the MainLayout component. As we did before, add a new Razor Component called Header.razor. Then, we’re going to add the markup shown which adds a Bootstrap navbar displaying the text.

<nav class="navbar navbar-light bg-light border-bottom mb-4">
    <span class="navbar-brand mb-0 h1">Blazing Trails</span>

That’s all we need in the Header component at the moment; we can now add that to the MainLayout by declaring it as we would any normal HTML element.

@inherits LayoutComponentBase
<Header />
<main class="container">

That’s it for the layout, if you try and run the application at this point you will be able to see the header we’ve just created but there will be a message saying “Sorry, there’s nothing at this address”. That’s because we haven’t defined any routable components (pages) yet.

The Blazor Trails home page

We already created the HomePage component. It still has the boilerplate code which comes with a new component. We need to update this code to make the component routable. Once we have that done, we’re going to define a class which represents a trail. We can then define some test data to use to build out the rest of the UI. Finally, we’re going to load the test data into the HomePage and loop over it to display the various trails via a reusable TrailCard component that we’ll create.

As we talked about earlier, to make a component into a routable component we need to use the @page directive and a route template which specifies the route it will be responsible for. At the top of the HomePage.razor file, add the directive along with a route template of “/”, which tells the Router that this page is the root page of the application. You can run the application at this point, if you wish, to check that the HomePage’s content is being displayed.

We need a way of representing a trail in our code, to do that we’re going to add a new class called Trail to the Home feature folder. Inside this class we need to add a few properties which represent the various data about a trail.

public class Trail
    public string Name { get; set; }
    public string Image { get; set; }
    public string Location { get; set; }
    public string Time { get; set; }
    public int Length { get; set; }

Prepare the data

Now, we have a definition for a trail we’re going to define some test data to use. At the moment our app doesn’t have a backend, there is no API we can call to retrieve or save data from and to, but later on there might be. In order to simulate making an HTTP call to load data from an API we’re going to define our test data in a json file. So, this is a great way to develop frontend applications which don’t currently have a useable server element. We can still use a HttpClient to load the data from the JSON file in the same way we’d load data from an API. Then once the server element is established, the HTTP call just needs to be updated to point at the API endpoint instead of the JSON file.

Now, in the wwwroot folder create a directory called trails. Inside that folder add a new json file called trail-data.json. You have the full json on GitHub.

Then, with our test data in place we’ll return to the HomePage where we need to load it. We’re going to load the data using the HttpClient, but in order to use it we need to get an instance of it using dependency injection. Blazor makes this really easy by providing an inject directive that has the following format, @inject [TYPE] [NAME], where [Type] is the type of the object we want and [Name] is the name we’ll use to work with that instance in our component.

The first injection

So, under the page directive add the following code which will give us an instance of the HttpClient to work with: @inject HttpClient Http.

Before we can use the HttpClient, we need somewhere to store the results the call will return. Our JSON tests data is an array of trails and as we’re not going to be modifying what’s returned, just listing it out, we can create a private field of type IEnumerable<Trail>.

@page "/"
@inject HttpClient Http
@code {
    private IEnumerable<Trail> _trails;

Read the data

Now, we have somewhere to store our test data we can make the call to retrieve it. A great place to do this kind of thing is the OnInitialized life-cycle method. This method is provided by ComponentBase, which all Blazor components inherit from, and it one of three primary lifecycle methods; The other two are OnParametersSet and OnAfterRender, they all have async versions as well. OnInitialized is only run once in the component’s lifetime making it perfect for loading initial data like we need to.

In order to retrieve the data from the JSON file, we can make a GET request just like we would if we were reaching out to an API. Except, instead of passing the address of the API in the call, we pass the relative location of the JSON file. As the file is in the wwwroot folder it will be available as a static asset at runtime just like the CSS file, this means the path we need to pass in the GET request is simply, “trails/trail-data.json”.

JsonAsync methods

So, a great productivity enhancement which ships with Blazor is the addition of some extension methods for the HttpClient:

  • GetFromJsonAsync
  • PostAsJsonAsync
  • PutAsJsonAsync

Under the hood, these methods are using the new System.Text.Json library. The first method will deserialize a successful response containing a JSON payload to a type we specify. The second and third will serialize an object to JSON to be sent to the server. All three of these methods do this in a single line. No more having to manually serialized and deserialize objects or check for success codes, making everything much cleaner and removing a lot of boilerplate.

Also, one thing to be aware of when using these new methods is that when a non-success code is returned from the server, they’ll throw an exception of type HttpRequestException. This means that it’s generally a good practice to wrap these calls in a try catch statement so non-success codes can be handled gracefully.

@code {
    private IEnumerable<Trail> _trails;
    protected override async Task OnInitializedAsync()
            _trails = await Http.GetFromJsonAsync<IEnumerable<Trail>>("trails/trail-data.json");
        catch (HttpRequestException ex).
            Console.WriteLine($"There was a problem loading trail data: {ex.Message}");

Great! We now have our data being loaded into our component, but we need to do something with it. It would be nice to have a message displayed to the user to let them know that when we’re loading the data, just in case it takes a while.

Waiting for the data

We can use a simple if statement in our markup to check the value of the _trails field. If it’s null then we can surmise that the data is still being loaded, excluding an error scenario of course. If the value is not null, then we have some data and we can go ahead and display it.

@if (_trails == null)
    <p>Loading trails...</p>
    <div class="row row-cols-1 row-cols-md-2">
        @foreach (var trail in _trails)
            <div class="col mb-4">
                <div class="card" style="width: 18rem;">
                    <img src="@trail.Image" class="card-img-top" alt="@trail.Name">
                    <div class="card-body">
                        <h5 class="card-title">@trail.Name</h5>
                        <h6 class="card-subtitle mb-3 text-muted"><span class="oi oi-map-marker"></span> @trail.Location</h6>
                        <div class="d-flex justify-content-between">
                            <span><span class="oi oi-clock mr-2"></span> @trail.Time</span>
                            <span><span class="oi oi-infinity mr-2"></span> @trail.Length km</span>

At the point you should be able to build the app and run it, if all has gone to plan you should see the trails displayed. Now, we could finish here but there’s one little refactor I think we should do first.


While it’s all perfectly valid as is, wouldn’t it be nice to encapsulate it all in a component instead? This would make the code in the HomePage component much easier to read.

So, create a new component called TrailCard.razor in the Home feature folder. Then replace the boilerplate code with the markup for the card from the HomePage, be careful not to copy the outer div with the class’s col mb-4. That was pretty painless, but now we have a problem. How do we get access to the current trail data? The answer is parameters.

Now, we can pass data into components via parameters, you can think of these as the public API for a component and they work one way, from parent to child. We can define them in the code block by creating a public property and decorating it with the Parameter attribute. Then, we pass data into them from the parent using attributes on the component tag.

For our TrailCard component we’re going to create a parameter which will allow us to pass in the current trail from the parent. We can then update the razor code to use this parameter.

<div class="card" style="width: 18rem;">
    <img src="@Trail.Image" class="card-img-top" alt="@Trail.Name">
    <div class="card-body">
        <h5 class="card-title">@Trail.Name</h5>
        <h6 class="card-subtitle mb-3 text-muted"><span class="oi oi-map-marker"></span> @Trail.Location</h6>
        <div class="d-flex justify-content-between">
            <span><span class="oi oi-clock mr-2"></span> @Trail.Time</span>
            <span><span class="oi oi-infinity mr-2"></span> @Trail.Length km</span>
@code {
    [Parameter] public Trail Trail { get; set; }

All that’s left now is to update the HomePage component to use the new TrailCard component.

@page "/"
@inject HttpClient Http
@if (_trails == null)
    <p>Loading trails...</p>
    <div class="row row-cols-1 row-cols-md-2">
        @foreach (var trail in _trails)
            <div class="col mb-4">
                <TrailCard Trail="trail" />
@code {
    private IEnumerable<Trail> _trails;
    protected override async Task OnInitializedAsync()
            _trails = await Http.GetFromJsonAsync<IEnumerable<Trail>>("trails/trail-data.json");
        catch (HttpRequestException ex)
            Console.WriteLine($"There was a problem loading trail data: {ex.Message}");


Finally, we did it! We finished setting up a Blazor WebAssembly application and it works. If you have any problem, you have the source code on GitHub. If you have any question, please use the forum.

Happy coding!