Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Web Development with Blazor

You're reading from   Web Development with Blazor A practical guide to start building interactive UIs with C# 11 and .NET 7

Arrow left icon
Product type Paperback
Published in Mar 2023
Publisher Packt
ISBN-13 9781803241494
Length 360 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Jimmy Engström Jimmy Engström
Author Profile Icon Jimmy Engström
Jimmy Engström
Arrow right icon
View More author details
Toc

Table of Contents (22) Chapters Close

Preface 1. Hello Blazor 2. Creating Your First Blazor App FREE CHAPTER 3. Managing State – Part 1 4. Understanding Basic Blazor Components 5. Creating Advanced Blazor Components 6. Building Forms with Validation 7. Creating an API 8. Authentication and Authorization 9. Sharing Code and Resources 10. JavaScript Interop 11. Managing State – Part 2 12. Debugging the Code 13. Testing 14. Deploy to Production 15. Moving from, or Combining, an Existing Site 16. Going Deeper into WebAssembly 17. Examining Source Generators 18. Visiting .NET MAUI 19. Where to Go from Here 20. Other Books You May Enjoy
21. Index

Figuring out the project structure

Now it’s time to look at the different files and how they may differ in different projects. Take a look at the code in the two projects we just created (in the Creating our first Blazor app section) while we go through them.

Program.cs

Program.cs is the first class that gets called. It also differs between Blazor Server and Blazor WebAssembly.

WebAssembly Program.cs

In the WebAssembly.Client project, there is a file called Program.cs, and it looks like this:

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();

Program.cs uses top-level statements without any classes or methods. By using top-level statements, C# understands that this is the application’s entry point. It will look for a div with the ID “app” and add (render) the App component inside the div, and the whole single-page application site will be rendered inside of the App component (we will get back to that component later in the chapter).

It adds a component called HeadOutlet and this component is for handling changing the head tag. Things that are located in the head tag are Title and head tags (to name a few). The Headoutlet gives us the ability to change the title of our page as well as meta tags.

It adds HttpClient as a scoped dependency. In Chapter 3, Managing State – Part 1, we will dig deeper into dependency injection, but for now, it is a way to abstract the creation of objects and types by injecting objects (dependencies), so we don’t create objects inside a page. The objects get passed into the page/classes instead, making testing easier, and the classes don’t have any dependencies we don’t know about.

The WebAssembly version is running in the browser, so it can only get data by making external calls (to a server, for example); therefore, we need to be able to access HttpClient. WebAssembly is not allowed to make direct calls to download data. Therefore, HttpClient is a special implementation for WebAssembly that will make JavaScript interop calls to download data.

As I mentioned before, WebAssembly is running in a sandbox, and to be able to communicate outside of this sandbox, it needs to go through appropriate JavaScript/browser APIs.

Blazor Server Program.cs

Blazor Server projects look a bit different (but do pretty much the same thing). In the BlazorServer project, the Program.cs file looks like this:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run(); 

In .NET 6, Microsoft removed the Startup.cs file and put all the startup code in Program.cs.

There are a few things worthy of mentioning here. It starts with adding all the dependencies we need in our application. In this case, we add RazorPages, the pages that run Blazor (these are the .cshtml files). Then we add ServerSideBlazor, giving us access to all the objects we need to run Blazor Server. Then we add WeatherForcastService, which is used when you navigate to the Forecast page.

It also configures HTTP Strict Transport Security (HSTS), forcing your application to use HTTPS, and will make sure that your users don’t use any untrusted resources or certificates. We also ensure that the site redirects to HTTPS to secure the site.

UseStaticFiles enables downloading static files such as CSS or images.

The different Use* methods add request delegates to the request pipeline or middleware pipeline. Each request delegate (DeveloperException, httpRedirection, StaticFiles, and so on) is called consecutively from the top to the bottom and back again.

This is why the exception handler is the first one to be added.

If there is an exception in any of the request delegates that follow, the exception handler will still be able to handle it (since the request travels back through the pipeline), as shown in Figure 2.15:

Figure 2.15 – The request middleware pipeline

Figure 2.15: The request middleware pipeline

If any of these request delegates handle the request in the case of a static file, for example, there is no need to involve routing, and the remaining request delegates will not get called. There is no need to involve routing if the request is for a static file. Sometimes, it is essential to add the request delegated in the correct order.

NOTE:

There is more information about this here if you want to dig even further: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0.

At the end of the class, we hook up routing and add endpoints. We create an endpoint for the Blazor SignalR hub, and if we don’t find anything to return, we make sure that we will call the _host file that will handle routing for the app. When _host has triggered, the first page of the app will get loaded.

Index/_Host

The next thing that happens is that the Index or _host file runs, and it contains the information to load the necessary JavaScript.

_Host (Blazor Server)

The Blazor Server project has a _Host.cshtml file that is located in the pages folder. It is a Razor page, which is not the same thing as a Razor component:

  • A Razor page is a way to create views or pages. It can use Razor syntax but not as a component (a component can be used as part of a page and inside of another component).
  • A Razor component is a way to build reusable views (called components) that you can use throughout your app. You can build a Grid component (for example, a component that renders a table) and use it in your app, or package it as a library for others to use. However, a component can be used as a page by adding an @ page directive to your component, and it can be called a page (more on that later).

For most Blazor Server applications, you should only have one .cshtml page; the rest should be Razor components.

At the top of the page, you will find some @ directives (such as page, namespace, and addTagHelper):

@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace BlazorServer.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head><component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

There are a couple of aspects of this file that are worth noting. The @ directives make sure to set the URL for the page, add a namespace, add a tag helper, and that we are using a Layout page. We will cover directives in Chapter 4, Understanding Basic Blazor Components.

Then we have the component tag:

<component type="typeof(App)" render-mode="ServerPrerendered" />

This is where the entire application will be rendered. The App component handles that. This is also how you would add a Blazor component to your existing non-Blazor app using the component tag helper.

It will render a component called App. There are five different render modes:

  • The first one is the default ServerPrerendered mode, which will render all the content on the server and deliver it as part of the content when the page gets downloaded for the first time. Then it will hook up the Blazor SignalR hub and make sure your changes will be pushed to and from the server; however, the server will make another render and push those changes over SignalR. Typically, you won’t notice anything, but if you are using certain events on the server, they may get triggered twice and make unnecessary database calls, for example.
  • The second option is Server, which will send over the whole page and add placeholders for the components. It then hooks up SignalR and lets the server send over the changes when it is done (when it has retrieved data from the database, for example).
  • The third option is Static, which will render the component and then disconnect, which means that it will not listen to events and won’t update the component any longer. This can be a good option for static data.
  • The fourth option is WebAssembly, which will render a marker for the WebAssembly application but not output anything from the component.
  • The fifth option is WebAssemblyPrerendered, which will render the component into static HTML and bootstrap the WebAssembly app into that space. We will explore this scenario in Chapter 5, Creating Advanced Blazor Components.

ServerPrerendered is technically the fastest way to get your page up on the screen; if you have a page that loads quickly, this is a good option. If you want your page to have a perceived fast loading time that shows you content fast and loads the data when the server gets the data from a database, then Server is a better option, in my opinion.

I prefer the Server option because the site should feel fast. Switching to Server is the first thing I change when creating a new Blazor Server site; I’d much rather have the data pop up a couple of milliseconds later because the page will feel like it loads faster.

Close to the top of the page, we have the base tag:

<base href="~/" />

The base tag allows Blazor to find the site’s root. Without the base tag, Blazor won’t be able to find any static resources like images, JavaScript, and CSS.

Then we have CSS. By default, there are two static CSS files, one for Bootstrap and one for the site.

<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="BlazorServer.styles.css" rel="stylesheet" />

There is also the one called BlazorServer.styles.css, a generated file with all the isolated CSS. CSS and Isolated CSS is something we will cover more in-depth in Chapter 9, Sharing Code and Resources.

We also have a component that gets rendered. This is the HeadOutlet component, which makes it possible to change the head metadata like the title and meta tags:

<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />

We will use this component in Chapter 5, Creating Advanced Blazor Components, to add metadata and change the title.

There is a small part of the UI that will show if there are any error messages:

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss"></a>
</div>

I would recommend keeping this error UI (or a variation) if we changed the layout completely because JavaScript is involved in updating the UI. Sometimes, your page may break, the JavaScript will stop running, and the SignalR connection will fail. You will get a nice error message in the JavaScript console if that happens. But by having the error UI pop up, you’ll know that you need to check the console.

The last thing we will cover on this page is also where all the magic happens, the JavaScript responsible for hooking everything up:

<script src="_framework/blazor.server.js"></script>

The script will create a SignalR connection to the server and is responsible for updating the DOM from the server and sending triggers back to the server.

Index (WebAssembly)

The WebAssembly project looks pretty much the same.

In the BlazorWebAssembly.Client project, open the wwwroot/index.html file. This file is HTML only, so there are no directives at the top like in the Blazor Server version.

Just like the Blazor Server version, you will find a base tag:

<base href="/" />

When hosting a Blazor WebAssembly site on, for example, GitHub Pages, we need to change the base tag to make the site work since it is served from a subfolder.

Instead of a component tag (as with Blazor Server), you’ll find a div tag here. Instead, there was a line in Program.cs that connects the App component to the div tag (see the previous Program.cs section):

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

You can replace Loading... with something else if you want to – this is the content shown while the app is starting.

The error UI looks a bit different as well. There is no difference between development and production as in Blazor Server. Here, you only have one way of displaying errors:

<div id="blazor-error-UI">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss"></a>
</div>

Lastly, we have a script tag that loads JavaScript. This makes sure to load all the code needed for the WebAssembly code to run:

<script src="_framework/blazor.webassembly.js"></script>

Like how the Blazor Server script communicates with the backend server and the DOM, the WebAssembly script communicates between the WebAssembly .NET runtime and the DOM.

At this point, the app is starting up running the Razor component. These components are the same in both projects.

App

The App component is the same for both Blazor WebAssembly and Blazor Server. It contains a Router component:

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

This file handles the routing, finding the suitable component to show (based on the @page directive). It shows an error message if the route can’t be found. In Chapter 8, Authentication and Authorization, we will make changes to this file when we implement authentication.

The App component also includes a default layout. We can override the layout per component, but usually, you’ll have one layout page for your site. In this case, the default layout is called MainLayout.

MainLayout

MainLayout contains the default layout for all components when viewed as a page. The MainLayout contains a couple of div tags, one for the sidebar and one for the main content:

@inherits LayoutComponentBase
<PageTitle>BlazorServer</PageTitle>
<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>

The only things you need in this document are @inherits LayoutComponentBase and @Body; the rest is just Bootstrap. The @inherits directive inherits from LayoutComponentBase, which contains all the code to use a layout. @Body is where the component will be rendered (when viewed as a page).

Bootstrap

Bootstrap is one of the most popular CSS frameworks for developing responsive and mobile-first websites.

We can find a reference to Bootstrap in the wwwroot/index.html file.

It was created by and for Twitter. You can read more about Bootstrap here: https://getbootstrap.com/.

At the top of the layout, you can see <NavMenu>, a Razor component. It is located in the Shared folder and looks like this:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">BlazorServer</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;
    }
}

It contains the left-side menu and is a standard Bootstrap menu. It also has three menu items and logic for a hamburger menu (if viewed on a phone). This type of nav menu is usually done with JavaScript, but this one is done solely with CSS and C#.

You will find another component, NavLink, which is built into the framework. It will render an anchor tag but will also check the current route. If you are currently on the same route/URL as the nav link, it will automatically add a CSS class called active to the tag.

We will run into a couple more built-in components that will help us along the way. There are also some pages in the template, but we will leave them for now and go through them in the next chapter when we go into components.

CSS

In the Shared folder, there are two CSS files as well : NavMenu.razor.css and MainLayout.razor.css.

These files are CSS styles that affect only the specific component (the first part of the name). We will return to a concept called isolated CSS in Chapter 9, Sharing Code and Resources.

You have been reading a chapter from
Web Development with Blazor - Second Edition
Published in: Mar 2023
Publisher: Packt
ISBN-13: 9781803241494
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image