"," "," Note
The term Single-Page Application (SPA) seems a bit weird when you open the app and start
navigating from one page to another. However, technically SPAs consist of a single HTML page
and Blazor or any other JavaScript framework (ReactJS, Angular, etc.) dynamically updates the
content of that page. As you have seen, in the wwwroot folder, there is only one HTML page
in our whole application, called index.html.","The Pages folder","Pages is the default folder where Microsoft puts its default pages that come with the template. You","can use Pages as the container for your app’s page components. However, it’s not mandatory to put","your components in this folder and you can even delete it. Pages comes with three sample pages","that cover different concepts, such as calling a shared component, updating the DOM using a button,","and fetching data from a data source:"," • Counter.razor: This is a Razor component that you can access through the route /
counter. This is also an example of calling a C# method from an HTML button and binding
an HTML

element’s content to a C# variable to show the result.
• FetchData.razor: You can access this component by navigating to /fetch-data. This
page contains an HTML table that shows weather forecast data and demonstrates how to fetch
data using the HttpClient. By injecting the object and making an HTTP GET request to
grab the JSON data from the weather.json file in the wwwroot folder, the data is rendered
within the component.
\f10 Understanding the Anatomy of a Blazor WebAssembly Project"," • Index.razor: This is the default component that you will see when you run the project.
It shows you how to call other shared components such as SurveryPrompt and pass the
Title parameter to it."," The Shared folder"," As the name indicates, this folder holds some shared components across the app. You can override"," this structure, but we are going to follow the same structure through the rest of this book and will use"," this folder to contain the components that will be shared across all the pages (app layout, nav menu,"," authentication components, etc.). By default, the Blazor template comes with three shared components:"," • MainLayout.razor: This comprises the default layout of the project and is a special kind
of component. It defines what the app looks like and allocates the content components (pages)
to the right place.
By default, this component references the NavMenu component and contains a

HTML
tag that renders the @Body property. This property holds the content of the pages based on
the navigated route (more about layout components will be covered in Chapter 3, Developing
Advanced Components in Blazor).
• NavMenu.razor: This component contains the menu items of the app (a set of URLs to
navigate between the pages mentioned in the Pages folder).
• SuveryPrompt.razor: This is a simple component to demonstrate how to create a reusable
component that accepts parameters. The Index page references this component and passes
a value for its Title parameter."," The _Imports.razor file"," In C# classes, you always need to use classes existing in different namespaces. So, basically, we reference"," a namespace by declaring a using statement at the top of the C# file so we can reference the required"," classes and nested namespaces. The _Imports.razor file is there for this purpose, and you can"," use it to reference common namespaces across the majority of your components. So, there is no need"," to add a using statement at the top of each Razor file to access the required code. By default, the"," component references some common .NET namespaces that you will use across your components,"," such as System.Net.Http and the namespace of your assembly and the shared folder."," The following is the code for the _imports.razor file with the shared referenced namespaces:"," @using System.Net.Http"," …"," @using Microsoft.JSInterop"," @using BooksStore"," @using BooksStore.Shared","\f Dependency injection in Blazor WebAssembly 11","The App.razor file","The App component is the parent and the main component of the Blazor app. It basically defines","the infrastructure required for your application to function, such as the Router component, which","matches the URL entered in the browser and renders the associated component in your app. The App","component also defines the default layout component (MainLayout, mentioned in the Shared","folder). More about layout components will be covered in Part 2, Chapter 3, Developing Advanced","Components in Blazor. Further, the App component contains the NotFound section, which you can","use to render specific content if the requested address was not found.","The Program.cs file","In any C# application, there is an entry point and Program.cs represents the entry point of your","Blazor app. This entry point sets up the Blazor WebAssembly host and maps the App component","to the corresponding div within the index.html file (by default, the div with the ID app). We","also use the file to register any service that we need to use throughout the application’s lifetime in the","DI container.","Now you should be able to navigate easily within the solution after understanding what every file does","and why it’s there; the next step in this chapter is looking at the concept of DI.","Dependency injection in Blazor WebAssembly","Modern software development is all about scaling, separation, and testing, so when you write the code,","you should feel confident about its behavior and reliability. To achieve that goal, you need to keep the","SOLID principles in mind throughout your development journey. The principle represented by the","letter D is dependency inversion. This basically refers to hiding the implementation of your services","behind interfaces. For example, for each service class you have, you can create an interface that contains","the methods of that class, and make the class implement the interface. Then, while consuming this","service, you reference the interface instead of the class directly. This strategy decouples the components","or code pieces that use a service from its implementation by depending on the service’s abstract layer,","which is the interface.","All this is amazing, but there is one missing part: how to initialize the object of a service that will","be used in more than one place. This is where DI comes into play. It’s simply a technique that allows","you to register your services that are used in other services or components in a centralized container,","and that container will be responsible for serving those objects to the components that require them.","\f12 Understanding the Anatomy of a Blazor WebAssembly Project"," How dependency injection works"," The following diagram shows the flow of a ConsoleLoggingService class:"," Figure 1.7 – The flow of the dependency injection container and associated services"," In the preceding figure, the ConsoleLoggingService class implements the ILoggingService"," interface. Then, in the DI container, we register an instance of ILoggingService with a new object"," of its implementation: ConsoleLoggingService. Whenever Component A requires logging logic,"," it uses ILoggingService (the abstraction layer of ConsoleLoggingService)."," Instead of initializing a new object instance ourselves, the DI container is serving ILoggingService"," to Component A, which adds many benefits as it keeps the code separated. Further, the implementation"," logic for the full service could be changed at any time without touching Component A, which depends"," on the base interface of that service. For example, if we want to log the data to a server instead of the"," console to start using the new service, we can just write a new implementation of ILoggingService"," and register that instance in the DI container, without changing anything in Component A or any"," other client code."," Note
To learn more about the SOLID principles and software development, check out the following
link: https://en.wikipedia.org/wiki/SOLID
\f Dependency injection in Blazor WebAssembly 13","Using dependency injection in Blazor WebAssembly","Blazor WebAssembly comes with a DI container out of the box. You can start by registering your services","in the Program.cs file and then inject those services into the client components or services as well.","Let’s implement or write our first service, ILoggingService, as mentioned in the preceding","section, with the Log method, and create an implementation to log the messages to the console","window of the browser. Then, we will inject this service into the FetchData component and log","the count of the weather data that the FetchData component gets. To implement the service, go","through the following steps:"," 1. In Solution Explorer, right-click on the project, and click on Add | New Folder. Name the
folder Services."," Figure 1.8 – Adding a new folder through Solution Explorer","\f14 Understanding the Anatomy of a Blazor WebAssembly Project"," 2. Right-click on the newly created folder and choose Add | New Item.
3. From the dialog that shows up, choose Interface as the item type, and give it the name
ILoggingService. Then click Add.
4. The interface has been created, and the goal is to create the Log method. As you know, in the
interfaces, we only define the signature of the method (its return data type and its parameter),
so we will define that method as follows:
public interface ILoggingService
{
     void Log(string message);
}"," 5. After creating the interface, we should create the implementation that logs that message to the
console, so repeat step 2, but instead of creating an interface, let’s create a class and give it
the name ConsoleLoggingService.
6. After creating the class, let’s implement the ILoggingService interface and write the logic
of that method as follows:
public class ConsoleLoggingService : ILoggingService
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}"," The Log method calls the WriteLine method in the Console class, but in Blazor WebAssembly
the Console.WriteLine method doesn’t act the same as it does in other .NET apps by
printing a string in a console app. It prints the string in the console window within the developer
tools of the browser.
You can access the developer tools in your browser as follows:
‚ Microsoft Edge: F12 in Windows or ⌘ + ⌥ + I for Mac
‚ Other browsers: Ctrl + Shift + I
The last thing to get this service ready to be injected into other components is registering it
in the DI container.
7. Go to the Program.cs file and register the service using the AddScoped method:
...
using BooksStore.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddScoped\f Dependency injection in Blazor WebAssembly 15"," ConsoleLoggingService>();
await builder.Build().RunAsync();","Our first service right now is ready to be used and injected within any other components. The following
example will show you how to inject this service in the FetchData component and achieve the
required target of logging the count of weather forecast items to the console window of the browser:"," 1. Open the _Imports.razor file and add a using statement to the Services namespaces:
@using BooksStore.Services"," We add references to the Services namespace in the imports because those services will
mostly be used across many components, so we don’t have to add that using statement in
every component.
Open the FetchData component in the Pages folder and inject ILoggingService by
using the @inject Razor directive in the component:
@page \"/fetchdata\"
...
@inject ILoggingService LoggingService
Weather forecast

Weather forecast


...."," 2. Our service is ready to be used and we can call the Log function from the object instance in
the C# code to log the count of the items as follows:
....
        
    
}
@code {
    private WeatherForecast[]? forecasts;
    protected override async Task OnInitializedAsync()
    {
        forecasts = await
          Http.GetFromJsonAsync
           (\"sample-data/weather.json\");
        LoggingService.Log($\"Number of items retrieved
                           is {forecasts.Count()}\");
    }
    …
\f16 Understanding the Anatomy of a Blazor WebAssembly Project"," If you run the project and navigate to the Fetch data page after the data is retrieved, you can open the
console window in your browser and you should see the message Number of items retrieved is 5."," Figure 1.9 – A screenshot of the console window with the printed sentence"," Tip
To learn more about the DI concept in Blazor, you can check the following link: https://
learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/
dependency-injection?view=aspnetcore-7.0."," Now we have implemented a full cycle, from creating the service abstract layer to its implementation,
registering the service in the DI container, and finally injecting and consuming that service from a
separate component.
Learning about DI and how it works will help you understand many aspects of the application, such
as writing maintainable, decoupled services. Also, you will be able to consume built-in services such
as the HttpClient to make API calls and the IConfiguration service, which we are going to
use in the next section to read and consume the application configurations."," Creating, storing, and retrieving the app configurations"," Configurations are a set of settings that your application uses throughout its lifetime. App configurations"," help you avoid hard-coding some values within your app that could change from time to time. Also,"," the process is very important when dealing with multiple environments. For example, you can store"," the URL of the API that your app is communicating with. While on the dev machine, the URL is the"," localhost, in the production environment it refers to the online hosted API (more about environments"," will be covered in the next section).","\f Creating, storing, and retrieving the app configurations 17","Blazor WebAssembly already supports configuration out of the box from JSON files that could be created
in the wwwroot folder. The file must be called appsettings.json and to have a configuration
file for each environment, you can specify further, for example, appsettings.{ENVIRONMENT}.
json or appsettings.Development.json."," Important note
You can add additional sources for configuration but appsettings files are enough as the
Blazor WebAssembly app is running fully on the client side. Connecting the app, for example,
to the Azure Key Vault service means the connection string will live on the client and be under
threat of being exposed easily.
So, for general configurations, appsettings is a great choice. Other kinds of secrets are
highly recommended to be stored server-side and we should architect our app in a way that
the client doesn’t need to fetch or use them.","We will create a configuration file and store some settings such as the URL of the API that we will use.
Then, we will print that value with the Index component. Let’s get started:"," 1. Right-click on the wwwroot folder and add a new item of type JSON file and call
it appsettings.json.
2. Add a property called \"ApiUrl\" and give it a value of \"http://localhost:44332/\"
as follows:
{
    \"ApiUrl\": \"http://localhost:44332\"
}","After saving the file, you can access this setting value directly using the IConfiguration service
by injecting it into any component or service and referring to the index of your property name.
In the following example, we will inject the IConfiguration service in the Index component
and print the value of the ApiUrl property:"," 1. Open the Index.razor file within the Pages folder and inject the IConfiguration
service as shown:
@page \"/\"
@inject IConfiguration Configuration
Index
...
\f18 Understanding the Anatomy of a Blazor WebAssembly Project"," 2. After the service is injected, you are ready to access the required value by using the indexer
of the Configuration instance. We are going to add a

tag and display the following:
Api Url: {API_URL_VALUE_IN_SETTINGS}:
...

Hello, world!


Welcome to your new app.

Api Url: @Configuration[\"ApiUrl\"]


..."," After running the application, you should see the following result in your browser window:"," Figure 1.10 – The value of the configuration ApiUrl from appsettings.json"," Configurations are important to you in your learning journey, and while developing our real-world
application we are going to use them from time to time. After the example we have given, you should
have a clear understanding of how to add configurations and how to retrieve their values in the
runtime of the app.
In the next section, we will be introducing new environments to our application, and we will add a
new configuration file that we will use only while running the app locally on the dev machine using
the dotnet run command or by debugging the app from within VS 22."," Managing application environments"," Software development goes through various stages. In the beginning, we may start with pure development"," and testing, but after we start rolling out our application to production, many challenges will appear."," One of these challenges is having different environments for your app run to on."," For example, imagine your application is communicating with an API and this API is developed by"," another developer or team. The application is hosted on Azure App Service with two deployment"," slots (Dev/Production). During development, you are going to use the URL of the dev API, which"," won’t make changes to the production database, and after committing your changes, in the production"," environment, you need to use the URL of the Production slot.","\f Managing application environments 19","In this scenario, it will be tough to change the API URL before you push to production and then
retrieve it after finishing. Thus, many mistakes may be made, which will make the development process
frustrating. This is where managing environments comes into play, where you can write code, render
a specific UI, or retrieve special configurations based on the environment that your app is running in
without making too many mistakes that delay development and production.
By default, when you run the Blazor app through the debugger, it runs within the development
environment, and after publishing it, it runs within the production one.
In this section, we will see how we can set up and retrieve the current environment of the application
and retrieve the configuration based on that.","Creating a configuration file based on the environment","To create a configuration file that will be used only in the development phase, you can create another","appsettings.json file but with the name of the environment before the extension, such as","appsettings.Development.json. To implement this, take the following steps:"," 1. Right-click on the wwwroot folder and click on Add | New Item. Choose a JSON file and give
it the name appsettings.Development.json.
2. Add the same \"ApiUrl\" property that exists in appsettings.json and the value
\"Development\" as follows:
{
    \"ApiUrl\": \"Development\"
}","When you run the application, you will notice that the value of Development will show up on the
screen instead of http://localhost:44332. You should follow the practice of separating the
settings based on the environment from the very beginning, so it makes the development process
smoother and easier.","Reading the environment within the components","In your development process and where there are many environments for your app (development,","preview, and production, for example), you need a specific piece of UI or certain logic to make the","app behave differently in different environments. A major implementation to achieve this kind of","responsiveness is showing a specific feature in preview but hiding it in production.","To achieve the mentioned behavior, you need to fetch the value of the current environment in your","code and use an if condition to determine how the UI or logic behaves within that environment.","\f20 Understanding the Anatomy of a Blazor WebAssembly Project"," In the following example, we are going to use IWebAssemblyHostEnvironment by injecting"," it within the Index component and remove the SurveyPrompt component if the app is running"," on the development environment:"," 1. Open the I n d e x component within the P a g e s folder and inject the"," IWebAssemblyHostEnvironment service within the component, but of course, you need"," to reference the namespace that contains the service, which is Microsoft.AspNetCore."," Components.WebAssembly.Hosting, as shown in the following snippet:"," @page \"/\""," @using Microsoft.AspNetCore.Components.WebAssembly.Hosting"," ..."," @inject IWebAssemblyHostEnvironment Host"," Index"," 2. The next step is to use the IsDevelopment method, which returns a bool if the current
environment is equal to Development:
...

Api Url: @Configuration[\"ApiUrl\"]

"," @if (!Host.IsDevelopment())
{
           Title=\"How is Blazor working for you?\" />
}"," After running the application on your machine in debugging mode, the SuveryPrompt component
will be shown in the production environment. This example demonstrates the responsiveness of
environment features, which is very useful."," Tip
IWebAssemblyHostEnvironment contains multiple methods like IsDevelopment().
It also contains IsStaging() and IsProduction(). You can also customize the name
by using IsEnvionment(\"CUSTOM_ENVIORNEMNT_NAME\")."," One of the topics to be covered is setting the current environment explicitly. We are going to cover this
in Chapter 14, Publishing Blazor WebAssembly Apps, after publishing the project, but you can go deeper
by reading and discovering more about environments at https://learn.microsoft.com/
en-us/aspnet/core/blazor/fundamentals/environments?view=aspnetcore-7.0.
\f Summary 21","Summary
Over the course of this chapter, we have learned the fundamentals of each Blazor WebAssembly
application, from setting up the development tools to creating the first application using the .NET
CLI and Visual Studio 2022. We also discovered the anatomy and the structure of the application by
defining the purpose of each file and why it’s there. We looked at DI, created our first service, injected
it within a component, and explored the reasons why it’s important in modern software development.
Configurations are an essential part of each application. To avoid hard-coded values within your code,
you need to keep the environment that your application is running on in consideration. This extra care
can make your development experience easier, especially after publishing an application to production
while other features are still under development. Managing the environments and configurations
helps with overcoming inconsistencies in development and managing the part under development.
In the next chapter, you will start the real action with Blazor by exploring and developing the basics
of app components, which are the main building blocks of a Blazor app.","Further reading
• Application structure: https://learn.microsoft.com/en-us/aspnet/core/
blazor/project-structure?view=aspnetcore-7.0
• Dependency injection: https://learn.microsoft.com/en-us/aspnet/core/
blazor/fundamentals/dependency-injection?view=aspnetcore-7.0
• Configuration in Blazor: https://learn.microsoft.com/en-us/aspnet/core/
blazor/fundamentals/configuration?view=aspnetcore-7.0
• Application environments in Blazor WebAssembly: https://learn.
microsoft.com/en-us/aspnet/core/blazor/fundamentals/
environments?view=aspnetcore-7.0
\f\f 2
Components in Blazor
Components are the main building blocks of each Blazor application. Each component is a self-
contained piece of the user interface (UI) and its related logic, and comprises a dynamic part of the
application UI. A Blazor application is basically a set of components placed together, each of which
has its own responsibility within the UI, and the interaction of these components builds up the full
app that we are aiming for in this book.
In this chapter, we are going to understand the concept of components from scratch, create our first
component, and then use it. We will discover the available mechanisms to pass data between components
by introducing parameters, cascading parameters, and event callbacks.
After that, we will look at the component life cycle events and how we can leverage them during the
component’s lifetime. Finally, we will add some CSS styling for a better look.
In this chapter, we will cover the following topics:"," • Understanding the concept of components"," • Moving data among components"," • Discovering the component life cycle"," • Styling the components using CSS","Technical requirements
You can find the source code for this chapter at https://github.com/PacktPublishing/
Mastering-Blazor-WebAssembly/tree/main/Chapter_02.","Understanding the concept of components","A Blazor application basically consists of a set of components working together to form rich dynamic","applications. Blazor components are formally known as Razor components in which C# and HTML","code are combined and the file extension .razor is used. Components can be nested within each","other, shared, and reused by a different project.","\f24 Components in Blazor"," While developing your Blazor application, you should think about it as an organization, where each
component is an employee responsible for a certain task. The collaboration of those employees together
is what represents the workflow of the organization. The same concept applies to a Blazor app in which
every component should have a clear and specific role in the app UI. At the same time, each component
should be able to receive corresponding data from other components to get its part of work done.
The following screenshot shows a sample podcast application (Productive+, which I developed for
tracking how I use my time) built with Blazor:"," Figure 2.1 – An application I have built, which is split into small components that make up the full UI.
The source code for this sample app is available at https://github.com/aksoftware98/productivity-plus"," The preceding figure demonstrates how a specific application UI contains multiple components
that together provide us with an advanced, rich UI. The page itself is also a component with nested
components contained within it. Finally, all components are hosted together within the App.razor
component, which is the root of each application.
\f Understanding the concept of components 25","Now, before we get started creating our first component, let’s understand the Razor file and syntax
that we use to build the components.","Introduction to Razor
Blazor components are Razor files that give us a super powerful engine to build absolutely dynamic
and interactive pieces of UI.
Component files must have a name starting with an uppercase letter, for example, AlertMessage.
razor. If you instead name a component alertMessage.razor, you will face a compile-time
error, which will prevent the compiler from building the application.
If we look at the pre-built Counter.razor component that comes with new Blazor projects (the
component results in a default page existing in every new Blazor app and is called Counter) in the
following code snippet, we can notice the syntax of the Razor component, in addition to the mix of
the C# code and HTML:
@page \"/counter\"
Counter

Counter


Current count: @currentCount



"," Now we have a fixed piece of HTML, but this can be reused in many components, and that’s
what we are going to achieve in the last step.
4. To render a component in another component, all we need to do is reference the name of that
component within double tags, <>. To achieve that in our example, let’s open the Index.
razor component within the Pages folder and reference our book card there.
The updated file should look like the following:
...
Index","

Hello, world!


Welcome to your new app.","
...
\f30 Components in Blazor"," Now we are ready to see our first reusable Razor component by running the project using the start
button in Visual Studio. The Index component will automatically open as it’s the default component,
and we should see our book card there, as shown in the following screenshot:"," Figure 2.2 – The result of the rendered BookCard component on the Index page"," Congratulations! You have successfully created and used your first Razor component after learning
the basics of components in general and Razor specifically. We can now take a step ahead by making
our component a bit dynamic via the different methods to send data from the parent components to
the children, and in the opposite direction, by using Parameters, CascadingParameters,
and EventCallbacks."," Moving data among components"," Methods in C# work by accepting parameters to process and return other types of data post processing."," Blazor components work according to the exact same concept. Because each component represents"," a piece of UI alongside its logic, there is always the need to give the component some data for it to"," function according to the set logic. So functionally, a Blazor component either renders the data that"," is input in a certain way, or responds based on the set logic."," A Blazor component, in some cases, requires some data as an input either to render the data in the"," UI or to control its internal logic based on the values provided. Also, in many scenarios, components"," have the ability to send data back to the parent. The effective collaboration of the app components by"," communicating with each other provides us with the clean, well-functioning, and effective app that"," we are trying to build."," Luckily, Blazor provides us with powerful mechanisms to allow the transfer of data between components"," and in different directions (from parent components to children, and vice versa).","\f Moving data among components 31","Now it’s time to discover these mechanisms and apply them to the component we created in the
preceding section.","Parameters
A Blazor component can accept parameters just like any C# method, but the definition of parameters
in Blazor is way different. Parameters in Blazor components are C# properties decorated with the
[Parameter] attribute, and we then set their values by passing them as attributes while calling
the component.
So, let’s get started adding some parameters to our BookCard component to parametrize the data
of it by accepting this data as parameters instead of hardcoded values in the HTML.
To achieve this, we need to add the @code section to our component and define the following
parameters (Title, PublishingDate, and Author), but the properties of the object (book)
could change from time to time during the application development cycle. So instead of passing each
property as a parameter, we can create a Book class and then pass the book object to the component.
This practice will help the maintainability of your Blazor components because when you add new
properties to your model, you don’t need to add the corresponding parameter to the component or
even to multiple components using the same model. There is also no need to add those new properties
as attributes when calling the component. To achieve all of this, we need to do the following:"," 1. Right-click on the project, then choose Add | New Folder and give it the name Models; the
folder will contain the models that we will use in our project.
2. Add a new C# class called Book within the Models folder by right-clicking on the folder,
choosing Add | New Item, adding a C# class, and giving it the name Book.cs.
3. Next, we need to add the properties of the book to the created class. The properties we need
are Title, PublishingDate, and AuthorName, so the class file should look like this:
namespace BooksStore.Models;
public class Book
{
    public string? Title { get; set; }
     public string? AuthorName { get; set; }
     public DateTime PublishingDate { get; set; }
}
\f32 Components in Blazor"," 4. Before we move to the BookCard component, we just need to add the namespace BooksStore.
Models to the _Imports.razor file so we don’t need to add the using statement every
time we want to reference a model:
...
@using BooksStore.Shared
@using BooksStore.Services
@using BooksStore.Models"," 5. Open the BookCard.razor file, and let’s modify it to accept a Book object as a parameter
by adding the @code { } directive and parameter of the type Book. The following code
snippet shows the result of this step:
    ...
    
"," @code
{
    [Parameter]
    public Book? Book { get; set; }
}"," 6. After adding the parameter, we can reference the value of the properties of the Book object
using the implicit Razor expressions in HTML via @ and the name of the property within the
parameter object (we will also use the nullable operator (?) just in case the parameter value
was null) as the next code snippet shows:

    
@Book?.Title

    

Author: @Book?.AuthorName


    

Publishing date: @Book?.PublishingDate


    

..."," 7. The last step is to provide the value of the Book parameter to the component when we call it.
Go back to the Index.razor file and add a new @code {} directive that will include the
declaration of a new Book object:
...
@if (!Host.IsDevelopment())
{
           you?\" />
}
\f Moving data among components 33"," @code
{
    private Book _firstBook = new Book
        {
            AuthorName = \"John Smith\",
            PublishingDate = new DateTime(2022, 08,
                                          01),
            Title = \"Mastering Blazor WebAssembly\"
        };
}
..."," 8. Modify the line where it calls the BookCard component by passing the value of the Book
parameter using an attribute:
...

...","So, if we run the project right now, we should see the exact same component but with different data
rendered from the previous one. This data is passed using parameters when calling the component,
as shown in the following screenshot:"," Figure 2.3 – Rendered component after passing the parameters","The same component can now be used just as we have seen all over the application and whenever your
app needs to show this piece of UI. The Book parameter is used to pass data to be rendered within
the component, but now we will create another parameter that will control the rendering process and
the functionality of the component.
\f34 Components in Blazor"," The second parameter we will add will be responsible for showing or hiding the Add to Cart button
as, in some places in the app, we just need to see the info of the book without the need to be able to
add it to the shopping cart.
To achieve this behavior, we will create another parameter of the Boolean type called WithButton
and directly initialize the True value in the BookCard component, as the following code shows:
...
[Parameter]
public bool WithButton { get; set; } = true;
..."," Now, after creating the parameter, we can wrap the button with an if condition to show it only if the
value of this button is true, as in the following code snippet:
...

Publishing date: @Book?.PublishingDate

","     @if (WithButton)
    {
        
    }

..."," Now we are going to call this component one more time in the Index component in the Index.
razor file, but providing the false value to WithButton:
...
"," ","

Api Url: @Configuration[\"ApiUrl\"]

"," ..."," You may notice, after running the application one more time, that the book card is present twice, but
the second one doesn’t contain an Add to Cart button.
\f Moving data among components 35"," Figure 2.4 – Rendering two book cards, the second one without a button","Parameters – special cases","Blazor also has some other complementary attributes that provide some additional functionality and","define some specifications for the parameters of your components. Here are some special cases and","attributes that you will mostly use while developing your application:"," • [EditorRequired] attribute: If you decorate a Blazor parameter with this attribute, the parameter
will be required, and the compiler will show some warnings if the value of the parameter is
not provided.
We can make our Book parameter required in the BookCard component by adding it as follows:
...
@code
{
    [Parameter]
    [EditorRequired]
    public Book? Book { get; set; }
}"," Or it could be added in the same line:"," [Parameter, EditorRequired]"," public Book? Book { get; set; }","\f36 Components in Blazor"," • Capture arbitrary attributes: Sometimes you need to pass some attributes to your components
that are not declared as parameters. This is most likely the case when you want to capture the
HTML element attributes such as class, style, and so on.
You can capture those undefined attributes and assign them to an element in your component
using the @attributes attribute for your HTML tag.
The value of this attribute is Dictionary. The key of each dictionary
element represents the name, and the value of type object represents the value of that
property.
Back in our BookCard component, we want to improve our component by adding the ability
to capture attributes such as style and class, and apply them to the parent div of the card.
To achieve this, in BookCard.razor, we need to create a parameter called UserAttributes
of the Dictionarytype, and set the CaptureUnmatchedValues
= true property for the Parameter attribute, as the following code snippet shows:
...
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary? UserAttributes
     { get; set; }
..."," Now we can assign the value of this parameter to the @attributes directive in the div
tag of the card as follows:
    …
attributes=\"UserAttribues\">
    
@Book?.Title

    ..."," Now while calling the component, we can pass attributes such as class and onclick to the
parent div without defining them as parameters explicitly.
As you have noticed in the preceding example when we added another BookCard, there is no
margin between the two cards in the Index component. We will add some margin by adding
the class attribute and giving it the bootstrap class mt-3, which will add some margin
from the top as follows:


\f Moving data among components 37"," The class attribute now will be applied to the div wrapper of the card and will add a margin
at the top:"," Figure 2.6 – The second book card after adding a margin at the top","EventCallback
EventCallback is another smart technique provided by Blazor that allows the child component to
send data to its parent by assigning a function to the child component that it will call when a certain
event occurs.
The actual value of EventCallback is a function that either takes no or one parameter if we use
the generic version of it (EventCallback).
The functions to be used as an EventCallback value should either return void or Task. The
technique doesn’t support returning other types."," Tip
EventCallback is wrapped around the concept of delegates in C#, with some added
functionality to handle the updated state of the component.
A delegate is basically a C# type and its value represents a function with a predefined signature
(return type and parameters).
To learn more about delegates, see https://docs.microsoft.com/en-us/dotnet/
csharp/programming-guide/delegates/.
\f38 Components in Blazor"," Now we will extend the functionality of the BookCard by adding an EventCallback that takes
Book as a parameter, and this event will be fired when the user clicks on the Add to Cart button.
So, in BookCard.razor, to add an event callback with the Book type for its parameter,
we need to create a Blazor parameter of the EventCallback type, and give it the
name OnAddToCartClicked:
...
[Parameter]
public EventCallback OnAddToCartClicked { get; set; }"," The preceding code snippet means the BookCard component can accept a function that returns
void or Task and takes a parameter of the Book type.
Now to fire this event, we will create a simple function called AddToCart that will fire the
EventCallback and pass the book of the card as a parameter:
...
[Parameter]
public EventCallback OnAddToCartClicked { get; set; }"," private void AddToCart()"," {","     OnAddToCartClicked.InvokeAsync(Book);"," }"," To fire the AddToCart() function, we need to use the @onclick event for the button that will be
triggered when we click the Add to Cart button as follows:
...
@if (WithButton)
{
    
}
..."," After adding this EventCallback, the BookCard now allows its parents to assign a function that
takes Book as a parameter. That means when that function in the parent is triggered in BookCard,
it will pass the Book object from the child to the parent via the parameter of the function, where
it can do some processing outside the scope of the BookCard component that is responsible for
rendering the book only.
\f Moving data among components 39","The next phase requires us to modify Index.razor so it will show a list of books instead of one,
and when the user clicks Add to Cart, the given title will be added to the user’s cart in the UI using
the following steps:"," 1. To deal with the books in our project (retrieve, add, edit, etc.), we will create a service with its
interface, register it in the DI container, and then inject it whenever we need to.
To achieve that, create a new interface called IBooksService in the Services folder and
add a method inside it called GetAllBooksAsync:
using BooksStore.Models;
namespace BooksStore.Services;
public interface IBooksService
{
    Task> GetAllBooksAsync();
}"," 2. Next, also in the Services folder, create a new class with the name LocalBooksService.
cs. The class will have an implementation for the interface we created in step 1. The
LocalBooksService class will provide methods to deal with book objects stored in the
local memory as follows:
using BooksStore.Models;
namespace BooksStore.Services;
public class LocalBooksService : IBooksService
{
    static List _allBooks = new List
    {
         new Book
         {
             AuthorName = \"John Smith\",
             PublishingDate = new DateTime(2021, 01,
                                           12),
             Title = \"Blazor WebAssembly Guide\"
         },
         new Book
         {
             AuthorName = \"John Smith\",
             PublishingDate = new DateTime(2022, 03,
                                           13),
             Title = \"Mastering Blazor WebAssembly\",
         },
         new Book
         {
             AuthorName = \"John Smith\",
             PublishingDate = new DateTime(2022, 08,
\f40 Components in Blazor","                                            01),
             Title = \"Learning Blazor from A to Z\"
         }
    };","     public Task> GetAllBooksAsync()","     {","         return Task.FromResult(_allBooks);","     }"," }"," 3. The GetAllBooksAsync returns Task> because later, in Chapter 8,"," Consuming Web APIs from Blazor WebAssembly, we will create another implementation for"," IBooksService that will require awaitable calls for an online service to fetch the books"," instead of retrieving them from a static in-memory list."," 4. To be able to inject IBooksService, we need to register it in the DI container with its"," implementation. So, in Program.cs, you can add the following line:"," builder.Services.AddScoped();"," 5. Open the Index.razor file and inject IBooksService using the @inject directive
at the top:
...
@inject IBooksService BooksService"," 6. In the @code section, create a variable of the Listtype, override the"," OnInitializedAsync method, and then call GetAllBooksAsync from"," BooksService as follows:","     private List _books = new List();","     protected override async Task OnInitializedAsync()","         _books =","           await BooksService.GetAllBooksAsync();"," 7. To show this list of books, we need to iterate over it using a foreach loop and render the
BookCard component in each iteration. In addition to that, we will create a div wrapper
with style as display:flex, so the books will be rendered horizontally:
...
","

Available Books:



\f Moving data among components 41","     @foreach (var book in _books)","     {","         ","     }","
"," ..."," The result of the previous work should be something like this:"," Figure 2.6 – List of books","8. After having the list, we can leverage the OnAddToCartClicked event callback. So, create
a list of titles in the Index, and every time the user clicks Add to Cart, the title of that book
gets added to that list and rendered in the UI. In the code, create a List object and
call it _booksCart:
private List _booksCart = new List();","9. The following is the books list in the UI. We will iterate over _booksCart and print the title
of each book in the cart:
...

My Cart



        @foreach (var item in _booksCart)
        {
            
  • @item.Title

  •     }

..."," Initially the list will be empty, but in the next step, we will create the functionality to add a book
to the cart every time the user clicks Add to Cart.
10. Now create a new function called AddToCart that takes a Book object as a parameter. This
function will be passed to the event callback of the BookCard component. The BookCard
will trigger this function and the book of the card will be passed to this function as a parameter
so we can process it. Our processing, for now, is adding the book to the cart list:
    private void AddToCart(Book selectedBook)
    {
\f42 Components in Blazor","         _booksCart.Add(selectedBook);
    }"," 11. The last step is just assigning the AddToCart function to the OnAddToCartClicked
event callback of the BookCard component as follows:

    @foreach (var book in _books)
    {
                   OnAddToCartClicked=\"AddToCart\" />
    }
"," Please note that when you assign the function, don’t use the parentheses as in AddToCart(),
because here we are not calling the function. Instead, we are assigning it just like any other
variable.
Now, after running the project and clicking on the Add to Cart button below a book in the Available
Books list, you will notice that the title of the book will be added to the My Cart list:"," Figure 2.7 – Cart list after clicking on Add to Cart"," The power of EventCallback, which allows the child component to call a function from the parent
component and pass data for it, gives us a very flexible way to communicate while keeping the code
separated and clean."," Cascading values and parameters"," Cascading values and parameters comprise another way to make data flow from the parent components"," to their children. However, the difference between cascading parameters and normal component"," parameters is that cascading parameters pass the data to any number of descendent components in","\f Moving data among components 43","the hierarchy. Unlike the component parameters, cascading values and parameters don’t need an
assignment through the attribute; their values are populated automatically.
And because the cascading values can go down in the hierarchy of the components, any child component
can access a cascading value from a parent component despite the number of levels between them.
The following code snippet is a basic setup for the cascading value:

    
","To start using the cascading values and parameters, we first have the CascadingValue component
that allows us to set a cascading value in a parent component, and the [CascadingParameter]
attribute is to make use of the cascading value provided by an ancestor component.
In the following example, we are going to use the MainLayout file in the Shared folder to add a
cascading value component that wraps the @Body property, which holds all the child components
within the layout. (More about layouts will be covered in Chapter 3, Developing Advanced Components
in Blazor.) The cascading value will be a basic string variable that represents the major style of the
background for the interested child components. And that will unify the background style for all the
cards that we will have:"," 1. Open the MainLayout.razor file in the Shared folder, create a string variable called
_backgroundStyle, and initialize it with a basic style:
...
@code
{
    private string _backroundStyle =
      \"background-color:#f2f2f2\";
}"," 2. Within the article HTML tag, wrap the @Body property render with the CascadingValue
component and assign the Value property to @_backgroundStyle:
...
        

            
                @Body
            

        

..."," Now all child components have access to a cascading parameter of type string that holds the
basic style for the background and could be used by any component within the hierarchy.
\f44 Components in Blazor"," 3. Back in the BookCard component, we need to make use of the value provided by the parent
component, which is MainLayout. To access that value, you need to add a C# property of
type string and decorate it with the [CascadingParameter] attribute as follows:
...
@code
{
    [CascadingParameter]
    public string? BackgroundStyle { get; set; }
..."," 4. Now, the value from the main layout will automatically be populated in the BackgroundStyle
parameter, so we can append it to the div’s style attribute as follows:
BackgroundStyle\" @attributes=\"UserAttributes\">
    
@Book?.Title

..."," Now after running the project, note that all the BookCard components on the Index page have
the same background color, and changing the value in MainLayout will update all components
using this value."," Figure 2.8 – Unified background style for all the BookCard components in the UI
\f Moving data among components 45","IsFixed parameter for cascading values","The CascadingValue component has an optional parameter called IsFixed of bool type; its","value is false by default. IsFixed instructs the recipient components of the cascading value to either","subscribe to the value change or not.","It’s recommended, when possible, to set its value to true, as that will lead to no subscriptions receiving","updates about cascading value changes, resulting in better performance, especially when there are a","large number of recipients for the value.","You can set the IsFixed parameter as follows:"," ","     "," ","Multiple cascading values and parameters","As you have seen in the preceding example, we could use the cascading value because it was the only
type string available.
To be able to add multiple cascading values, you need to either have multiple values with multiple
types (string value, int value, Book value, etc.), or the same value but defined with the name of the
value explicitly:"," • Multiple values with different types: The following examples in the MainLayout component
show how we can declare more than one cascading value of different types (string and bool
in the following example):
...

            
                
                    @Body
                

            

        

    

"," @code
{
    ...
    private bool _isBusy = false;
}
\f46 Components in Blazor"," Now, we have two cascading values available in any child component to access by defining their
cascading parameters. One is of type bool and the other is of type string as follows:
[CascadingParameter]
public string? BackgroundStyle { get; set; }"," [CascadingParameter]
public bool IsBusy { get; set; }"," • Multiple values with explicit names: If you have multiple cascading values with the same
types, you can define the name of the value explicitly using the Name parameter, as shown in
the following example:
        

                           Name=\"BackgroundStyle\">
                                   Name=\"ButtonStyle\">
                    @Body
                

            

...
@code
{
    private string _backroundStyle =
      \"background-color:#f2f2f2\";
    private string _buttonStyle =
      \"background-color:orange\";"," Now, when referencing one of those cascading values in the child components, we need to
define the name using the Name property of the [CascadingParameter] attribute as
shown in the BookCard component:
[CascadingParameter(Name = \"BackgroundStyle\")]
public string? BackgroundStyle { get; set; }"," [CascadingParameter(Name = \"ButtonStyle\")]
public string? ButtonStyle { get; set; }"," With that, we have discovered three different ways to enable communication between our application
components: parameters, cascading parameters, and event callbacks.
\f Moving data among components 47","Two-way data binding in Blazor","Now we know that we can use parameters to pass data from a parent to its children, and event callbacks","to pass data from the child to the parent. Next, we will learn about combining them together.","Blazor provides a neat and compact way to enable two-way data passing between components. Let’s","take a Table component that renders a collection of data as an example. The Table component","allows the user to select an item when clicking on any row. Basically, in the Table component, we","need a parameter called SelectedItem and an EventCallback called OnSelectedItem","that will enable us to pass an already selected item to the table. The EventCallback will also let","us receive the new selected item when the user picks it.","To get the update from the child component, we need to create a method and assign it to the","EventCallback parameter of the component. This method will accept the passed value from the","parameter, and then assign it to a variable in the very basic shape if there is no processing required.","To send the value of that variable to the component, we need to pass it to the Parameter. With the","@bind directive, we can combine both operations and skip the method that will update the variable","in a single attribute.","To be able to use the @bind with a component, it has to have a parameter of a certain type and","EventCallback of the same parameter type. The EventCallback must have the same name","as the parameter concatenated with the word Changed, as the following example shows:"," [Parameter]"," public string? Message { get; set; }"," public EventCallback MessageChanged { get; set; }","That component accepts a parameter of type string, and it also updates the value of that string
internally and it exposes an EventCallback that could be triggered when that value gets changed.
So, when we use this component in another parent component that has a variable of type string called
_message, this variable will be passed to the child component, and we also need to update it when
the child updates its value internally, so let’s use @bind as follows:
","In Chapter 3, Developing Advanced Components in Blazor, we will use the two-way data binding while
developing a component; also in Chapter 5, Capturing User Input with Forms and Validation, while
using the input components, we will use it heavily and learn more about its advanced techniques.
In the next section, we will discover the life cycle of the component within the Blazor app, and each
stage the component goes through over its lifetime.
\f48 Components in Blazor"," Discovering the component life cycle"," Blazor components go through a set of methods from initialization to rendering and finally being"," disposed of. These methods have synchronous and asynchronous versions that we can leverage to"," perform certain tasks."," The following list shows the methods that the components go through; not all of them are being called."," Being called depends on whether the component is being rendered for the first time:"," • SetParameterAsync: This sets the value of the parameters from the component parent and
the route parameters. (More about route parameters will be covered in Chapter 4, Navigation
and Routing.)
The default implementation of this method sets the values of the parameters and the cascading
parameters available. SetParameterAsync is called only for the first render of the component.
Overriding its implementation allows you to control the process of setting the values of the
parameters and write some logic based on that, as shown in the BookCard.razor file:
// Override the SetParametersAsync to control the
// process of setting the parameters
// using the ParameterView object that holds the
// values of the component parameters
public override async Task SetParametersAsync(ParameterView
parameters)
{
    if (parameters.TryGetValue(nameof(BackgroundStyle),
out var value))
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            BackgroundStyle = \"background-color:white\";
        }
    }
    await base.SetParametersAsync(parameters);
}"," • OnInitialized / OnInitializedAsync: After the component receives its parameter"," values, OnInitialized for synchronous operations and OnInitializedAsync for"," asynchronous operations are invoked, only for the first-time render.","\f Discovering the component life cycle 49"," Overriding the implementation of OnInitialized or OnInitializedAsync allows"," us to take some actions and execute logic when the components get initialized, for example,"," to start fetching data from the API (more on this in Chapter 8, Consuming Web APIs from"," Blazor WebAssembly):"," protected async override Task OnInitializedAsync()"," {","     // Fetch data from the API"," }","• OnParameterSet / OnParameterSetAsync: These two methods get called after"," OnInitialized and OnInitializedAsync in the first render, as well as after one or"," more parameter values get changed by the parent component."," To validate or do some logic when any value of a parameter gets changed, you need to override"," either OnParameterSet or OnParameterSetAsync for asynchronous logic:"," // Validate the Book parameter and throws an exception"," // if it's null"," protected override void OnParametersSet()","     if (Book == null)","         throw new ArgumentNullException(nameof(Book));","• OnAfterRender / OnAfterRenderAsync: These methods are called after the component"," rendering has finished, and any changes here won’t be rendered unless you explicitly use the"," StateHasChanged() method. In this stage, all the elements and the component references"," are fully rendered."," OnAfterRender and OnAfterRenderAsync has a Boolean parameter that determines"," whether this is the first render of the component or not. As these methods get called after the"," rendering process for the component, the process may comprise the first render or a subsequent"," one. Overriding those methods is perfect for making JS calls against the DOM (more on this in"," Chapter 6, Consuming JavaScript in Blazor), as the DOM elements are rendered and available:"," protected async override Task OnAfterRenderAsync(bool"," firstRender)","     // Make a JavaScript call to manipulate the DOM","     // elements","\f50 Components in Blazor"," The following diagram shows the flow of the component life cycle:"," Figure 2.9 – Component lifetime stages"," Tip
If the asynchronous task executed in the life cycle events is not completed, the component will
continue to be rendered. When the task gets completed, the component will be rendered to
reflect the updates in the UI if there are any.
\f Styling the components using CSS 51","Dispose event
One extra set of life cycle events comprises the Dispose and DisposeAsync methods by
implementing either the IDisposable interface for synchronous disposal or IAsyncDisposable
for asynchronous disposal. Disposal will occur when the component is removed from the UI and
helps to clear up unmanaged resources such as files and events.
The following example shows how to implement the Dispose method:
@implements IDisposable
    ...
@code {
public void Dispose()
{
     // Release unmanaged resources like unsubscribe from
     // events
    // Timer.Dispose();
}
...
}"," Tip
You need to implement either IDisposable or IAsyncDisposable as the framework
will call the asynchronous version if both are available.","In this section, we have learned about the life cycle events that each Blazor component has. These
events are used heavily in each application and throughout the projects in this book.
Now, after creating a full Blazor component and populating it with some data, it’s time to make it
pretty by adding some CSS for it.","Styling the components using CSS","CSS is a web utility that allows us to change the look and feel of our elements. In this section, we will deep","dive into the different ways supported by Blazor with which we can add CSS styles to our components.","In Blazor apps, we have four different ways to style our components:"," • Global styles
• Isolated styles (scoped)
• Inline styles
• Embedded styles
\f52 Components in Blazor"," Isolated styles
Blazor provides us with a powerful way to style our components by keeping the styles separated from
each other. This means small, clean styles, scoped for each component.
To reference the isolated styles, you need to add a link tag with a reference using the
syntax {AssemblyName}.styles.css.
Let’s get started using isolated styles to style our BookCard component:"," 1. Create a CSS file in the same folder of the BookCard component following the convention
{ComponentName}.razor.css – in our case, BookCard.razor.css. This will make
Visual Studio create a nested file behind the component file as follows:"," Figure 2.10 – Nested CSS file by Visual Studio"," All the styles within BookCard.razor.css will be scoped for the BookCard component
only.
2. Let’s add some CSS styles to make the book’s card looks prettier. We are going to have two
classes: one for the button and another for div with margin, padding, a little shadow, and
some smooth borders for a modern feel:
.card {
    margin: 7px;
    padding: 7px;
    box-shadow: 0px 3px 8px -3px rgb(0 0 0 / 75%);
    border-radius: 3px;
}"," .main-button {
    background-color: #ec6611;
    border-radius: 3px;
    border: none;
\f Styling the components using CSS 53","     color: white;
    width: 100%;
    padding: 4px;
    margin: 4px;
    font-size: 13px;
    cursor: pointer;
}"," 3. Back in the BookCard.razor file, we can now apply those classes to the div and button
elements and we can remove the inline styles we set earlier:
     class=\"card\">
    
@Book?.Title

...
     @if (WithButton)
    {
        
    }
...","Now when we run the project, we will see that our card has a much better visual appeal than before.
The result should be like the following screenshot:"," Figure 2.11 – The look of the BookCard after adding CSS","\f54 Components in Blazor"," The powerful part of this CSS is that the class names (card and main-button) are only scoped
for the BookCard component and cannot affect other components. The concept of isolation here
gives us clean and short CSS files, in addition to having the styles of each component alongside the
respective component file.
So, now the question is: how does isolated CSS work?
After running the project, open the developer tools and look at the generated HTML and CSS. Note
that Blazor has appended an HTML attribute in the format b-{10-characters-text}. The
following screenshot shows the div element of the card:"," Figure 2.12 – The HTML attribute appended to the div element by Blazor"," And looking at the following CSS window, we can also see that the card class has been renamed,
with the same identifier appended as follows:"," Figure 2.13 – The updated CSS class name with the identifier"," In essence, the CSS isolation happens at build time when Blazor rewrites all the CSS selectors to
target the HTML elements with the unique identifier. Then, it combines them in a static CSS file with
a name using the syntax {AssemblyName}.styles.css. If you view your project in the CSS
window of the browser Dev Tools, you will see the name of the CSS files that contain classes, such
as BooksStore.styles.css:"," Figure 2.14 – Name of the CSS file that contains the isolated CSS selectors
\f Styling the components using CSS 55","And that’s why if we open the Index.html file, we see that Blazor references the BooksStore.
styles.css file, as this file gets generated at the build time and is available in the root folder:


...
    

..."," Note
Isolated CSS will be applied only to the scoped component. You can use the ::deep pseudo-
element, for example, .card::deep, so that styles will be applied to descendent elements
in the child components.","Global styles
Global styles use a .css file for the application referenced in the Index.html file, like the default
app.css file found in the wwwroot/css folder. By default, that file has the styles for the default
Blazor template. You can use this file to set general styles that will be used across your application,
such as text styles.","Inline styles
Inline styles are created when you set the CSS styles directly in the element within the component
using the style attribute. This method is efficient as no cache issues will be faced.
However, this will lead to a long element written in the components, and the styles applied to the
element cannot be reused because they are not wrapped with a class.
The benefit of using the inline styles is to have more control over the CSS properties based on a certain
logic in your component, for example, changing the color of the text if a book is being sold at a discount."," Tip
When working with inline styles, especially those managed by specific behavior, it’s recommended
to use the third-party BlazorComponentUtilities package, which contains the
StyleBuilder class, and helps us write inline styles using C# in a very clean and manageable
way. You can download the package from https://www.nuget.org/packages/
BlazorComponentUtilities/.
\f56 Components in Blazor"," Embedded styles
Embedded styles can be created by using the
..."," The embedded-styles approach is useful if you are using a Blazor UI framework and you want to
override some of the default styles it has. To do this, open the Dev Tools in the browser and discover
the class names. Then, with the