Skip to content
Home » Blazor – My Portfolio – Part 2 – Starting the Project

Blazor – My Portfolio – Part 2 – Starting the Project

Spread the love

In part 1 of this series I briefly introduced the project we’re going to develop. My Portfolio is going to be a Blazor WebAssembly app that runs exclusively in the browser, so no server is required. Instead of a backend and a database, we’ll use data stored in a file. This will do for our purposes because there isn’t going to be much data.

And now let’s open Visual Studio 2022 and create the project. To do that, click on Create a new project.

Then find Blazor in the dropdown list (A), select the Blazor WebAssemblyApp project template (B) and hit the Next button (C).

Set the project name to MyPortfolio (A), select a location for your project (B) and hit Next (C).

We’re going to target .NET 7.0, so make sure it’s selected in the dropdown list (A). If we leave ASP.NET Core Hosted selected, three projects will be created in the solution (client, server and shared). As we only need the client project, make sure to uncheck this box (B). Then hit Create (C).

This will create the project for you. Now let’s have a look at what has been created for us.

Anatomy of a Blazor WebAssembly Project 

If you look at the Solution Explorer, you’ll see there is just one project in the solution.

Here you can see some folders and top-level files. Let’s have a closer look at some of them. So, to start with, there are two top-level files with a .razor extension. These files contain Razor code. We’re going to talk about Razor code soon, but for simplicity’s sake you can think of it as a mix of HTML and C#. Now, let’s have a look at these files.

The App.razor file File 

Let’s start with the App.razor file. If you open the file in the editor, you will see the following code:

<Router AppAssembly="@typeof(App).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 role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

We’re going to talk about Razor syntax in detail later in the series, but if you know HTML, this should look very familiar to you. Files with the .razor extension are called Blazor components, Razor components or just components for short, and we’ll be using them extensively in our app. So, the App.razor file contains the App component. The App component is the root component of the application. In Razor, just like in HTML, elements are used in opening and closing tag pairs and can be nested inside other elements. By elements I mean the ordinary HTML tags, but also other components. In the code above you can see examples of both. So, here we have an ordinary HTML tag:

<p role="alert">Sorry, there's nothing at this address.</p>

This is the paragraph element. As a quick reminder, it starts with the opening tag <p> and ends with the closing tag </p>. The content of the element is between the two tags.

And here’s an example of an element, which is not part of HTML, but rather is a Blazor component:

<Router AppAssembly="@typeof(App).Assembly">
    ...
</Router>

Here we have the Router component. Again, there’s an opening tag and a closing tag.

As you can see in both the examples above, there may be some stuff inside the angled brackets of the opening tag. These are attributes. But the actual content of the element starts after the closing angled bracket of the opening tag. So, in case of the paragraph element the content is the text Sorry, there’s nothing at this address. and in case of the Router element the content is the whole hierarchy of embedded components.

It may also happen that an element doesn’t have any content at all. Then, instead of using a pair of opening and closing tags with nothing in between, you can use a self-closing tag, like for example here:

<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

You just have to put the slash before the closing angled bracket of the opening tag.

But let’s leave the syntax for later. Now let’s focus on what we have in this file. As you can see, there’s the Router component, which we use to set up the routing for the app. Inside the Router component we have the Found and NotFound components. The former is used if the route is found. If this is the case, the embedded RouteView component receives data through RouteData and renders the specified component in the indicated layout, which in this case is MainLayout. If the route is not found, the NotFound component is used and the text specified in the paragraph inside the LayoutView is rendered, using the same MainLayout component (but you could use a different layout for the not found scenario if you wanted).

Fine, let’s move on to the _Imports.razor file.

The _Imports.razor File 

This is what you’ll see if you open the _Imports.razor file:

@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.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using MyPortfolio
@using MyPortfolio.Shared

Just a bunch of Razor directives. You can tell it’s Razor directives by the @ symbol at the beginning. These are directives for the namespaces that can be shared between multiple Razor components, so that you don’t have to import the same namespaces in each component individually. These directives only apply to Razor files, but not to C# files.

What’s more, you can have multiple _Imports.razor files in your project. This one is the top-level one, so it applies to all Razor files in the project. But you can define similar files inside other folders and then they will apply to those folders and their subfolders only.

We just covered the two top-level Razor files in the project, but these are not all the Razor files that you get out of the box when you create a new project. More Razor files are hidden inside some of the folders. So, let’s have a look at them now. We’ll start with the Pages folder.

The Pages Folder 

Expand the Pages folder.

There are three Razor files. The Counter.razor and FetchData.razor files contain some sample code that you can use to see what a basic Blazor page looks like, but eventually we’ll remove these files and create our own. Let’s start with the Index.razor file, though. Here’s the code it contains:

@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

This is Razor code as well, but what is different is the @page directive at the top. Components with this directive, also referred to as pages, are routable and the string following the @page directive determines the route that will take you to them. In this case the route consists of just the slash symbol (which each route must start with), which means this route will take you to the home page.

Below the @page directive we have the PageTitle component, an HTML h1 tag with a greeting, some text and a SurveyPrompt component, which is defined in a different part of our project, as you’ll see in a minute. Anyway, before we move on, let’s run the app and see what the home page looks like. This is the page you will automatically be routed to when you start the app. So, in Visual Studio hit the Play button. Alternatively you can hit F5 to start debugging.

When you do that, a console window will open with the following information:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7227
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5259
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\Projects\MyPortfolio

As we’re running our app using the https protocol, the port that is listening for our app is defined in the https section. It says port 7227 on localhost is used. Simultaneously, a new browser window should open with your app running in it. If you check the address bar, it indeed says localhost:7227.

If for some reason the browser window doesn’t open, you can enter this address manually in the address bar. Don’t close the console window while the app is running. You can minimize it to get it out of your way.

If you now look at the tab and the right-hand part of the browser window, you will see the elements defined in the Index.razor file. In the tab you can see the page title (A). Then, in the right-hand part of the window you can see the HTML h1 header with the text Hello, world! (B). Below is the text (C) and finally, in the gray box is the SurveyPrompt component defined in a different folder.

But where does the content on the left-hand side come from? We didn’t define it in the Index.razor page. Well, it comes from the layout and we’ll see how it works in a moment.

Now, if you look at the address bar again, you see that there is nothing after the port number. This means it’s the home page of our project. In the part on the left that comes from the layout you can see that Home is highlighted. This also means you’re on the home page. And now click on the Counter link below. You will immediately see some changes:

So, first of all now the page navigated to is highlighted (A), which is also reflected in the page title (B). And, of course, the whole content of the page has been replaced (C) because you’re on the Counter page now. Besides, if you look at the address bar, you will see it changed to localhost:7227/counter. So, now that we know what the Counter page looks like, let’s see how it’s implemented in the code.

Open the Counter.razor file:

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

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

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Here’s something new. You can see that the code is divided into three sections. The topmost section is the directives section. Here it contains just the @page directive, but there may be more directives or none (in non-routable components).

Next is the Razor section that contains Razor code. This section is present in the Index.razor file as well. Actually, this is the only section that is mandatory.

Finally there’s the optional code section which starts with @code. It contains pure C# code and is used only if you need some logic for your component.

Let’s have a look at the three sections in more detail.

The directives section contains the @page directive followed by the route. As you can see, this very route is reflected in the address bar of your browser. The route is what comes after the port number and always starts with a slash.

In the Razor section the page title is defined, then the h1 header saying Counter, followed by a paragraph and a button. If you use a button, you usually want to add some logic to it in case someone clicks it. This is what the @onclick event is for (we’re going to cover events later in the series).

Finally, we have the code section with the logic for the click event. Go ahead and click the button several times. You will see the text in the paragraph above the button display the number of clicks.

In a similar way, if you now select the Fetch data link in the navigation bar on the left, you will be taken to the FetchData page:

Again, the page title, the address of the page and its content have changed. Now open the FetchData.razor file:

@page "/fetchdata"
@inject HttpClient Http

<PageTitle>Weather forecast</PageTitle>

<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[]>("sample-data/weather.json");
    }

    public class WeatherForecast
    {
        public DateOnly Date { get; set; }

        public int TemperatureC { get; set; }

        public string? Summary { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

There’s more code than before, but there are still the same three sections: the directives section (which contains two directives here, one of which is @inject, which we are going to talk about later in the series), the Razor section with a table (don’t worry about the syntax if something seems weird, we’re going to talk about Razor syntax in detail), and the code section with C# code.

Fine. You remember I mentioned the parts that you can see in your browser on the left-hand side of the screen (A) that were not defined in the pages. There’s also the About link (B) that you won’t find in the FetchData.razor page.

I then said these elements of the GUI are defined in the layout. So, let’s now have a look at the layout and the other files in the Shared folder.

The Shared Folder

There are three files in the Shared folder.

As you can see, the first two are expandable. Click on the triangle symbols to the left of their names to expand them.

Turns out some CSS files are hiding there. These are style files that are associated with these particular components, so MainLayout.razor.css is only available in the MainLayout.razor file and NavMenu.razor.css is only available in NavMenu.razor. This is how we use styles with component scope, but we’ll be talking about styles in much more detail later in the series.

For now let’s focus on the MainLayout.razor file. If you open it in the editor, you will see the following code:

@inherits LayoutComponentBase

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

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

Here we have the @inherits directive in the directives section, but we don’t have the @page directive. This is because this is not a routable component. The @inherits directive just tells us which class the component inherits from.

Then we have the Razor section which mostly consists of regular HTML tags. But between these tags you can see the elements of the layout that were displayed irrespective of which page we were on. Let’s have a look at them.

So, first we have the following div:

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

This div contains the NavMenu component, which, as we are just about to see, is implemented in the NavManu.razor file. This code is responsible for displaying the navigation menu on the left-hand side of the window, in the sidebar.

Then we have the following anchor tag:

<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>

This is pure HTML. It’s responsible for displaying the About link in the upper right corner of the window.

Finally, there’s the article tag with the @Body property:

<article class="content px-4">
    @Body
</article>

This is where the actual content of your page will be displayed. So, the elements defined in the layout file will be fixed and @Body acts as a placeholder that will be replaced by the actual page, be it the Counter page, the FetchData page or any other page that we create.

Naturally, we can have multiple layouts and particular pages can use one or another, but we get just this one out of the box when we create the project.

Now, as I mentioned before, the layout contains the NavMenu component. Let’s inspect the NavMenu.razor file:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">MyPortfolio</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </div>
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

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

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

First of all, we don’t have any directives here. In particular, we don’t have the @page directive, so this component can’t be navigated to, or, in other words, used as a page. Let’s run the app again and have a closer look at how this component is rendered.

The upper part contains the MyPortfolio link and below it are some NavLink components that enable us to navigate to all the different pages. Again, don’t worry if something is not clear in the code above at this stage. We’ll be talking about navigation in Blazor later in the series. Some of the elements in the Razor section raise events, which are handled in the code section below.

Finally, let’s move on to the SurveyPrompt.razor file. Here’s the code:

<div class="alert alert-secondary mt-4">
    <span class="oi oi-pencil me-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
    </span>
    and tell us what you think.
</div>

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

This code is responsible for displaying the following part of the home page:

If you’re wondering where the text How is Blazor working for you? comes from, just go to the Index.razor file where the component is used and you will see it with the Title parameter set to this very string:

<SurveyPrompt Title="How is Blazor working for you?" />

This is how parameters work in Blazor, enabling you to pass information from one component to another. Naturally, we’re going to cover this topic in much more detail in due time.

Looks like we’ve covered all the Razor files that were created. But where does the app actually start? The Program.cs file is the entry point of the application. Let’s have a look at it.

The Program.cs File

Here’s the code that you can see in the Program.cs file:

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MyPortfolio;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

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

await builder.Build().RunAsync(); 

Here’s the code responsible for the basic configuration of the application. You can see the builder object that we use to add services and build the app.

OK, so if you run the app, you can see a page displayed using a specific font, with specific styles applied, maybe with some images, and so on. But where does all this stuff come from?

The wwwroot Folder

Well, the answer is in the wwwroot folder.

This is the application’s web root. This folder contains public static resources like style sheets, images, an icon, the index.html file or sample data. It can also contain other types of files, like for instance JavaScript files.

Go ahead and open the index.html file:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>MyPortfolio</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <link href="MyPortfolio.styles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">
        <svg class="loading-progress">
            <circle r="40%" cx="50%" cy="50%" />
            <circle r="40%" cx="50%" cy="50%" />
        </svg>
        <div class="loading-progress-text"></div>
    </div>

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

</html>

This file is the root page of the application. The head element contains the links to the CSS files and specifies the base path for the app. The body element contains some vector graphics to display the loading progress and a message that appears if an unhandled error occurs. It also contains the blazor.webassembly.js file, which downloads and initializes the .NET runtime, and also downloads your application’s assemblies and dependencies.

There are several CSS files in the css folder:

They are all referenced in the head element. We’ll be talking about them in more detail when we talk about styles.

Finally, there’s the sample-data folder. In it you will find the weather.json file that contains the sample weather data in json format that is used by the FetchData component.

We’re going to remove this file eventually, but this is how we’ll be storing data for our project as well.

The Other Folders

The three remaining folders in our project hierarchy are Connected Services, Dependencies and Properties.

We’re not going to work with these folders, at least for now, so don’t worry about them.

So, now we’ve discussed the project hierarchy that is created for us when we start a new project. But now it’s time to start working on the project itself and adding components and functionality to it. This is what we are going to start doing in the next part of the series.


Spread the love

Leave a Reply