© Peter Himschoot 2019
Peter HimschootBlazor Revealedhttps://doi.org/10.1007/978-1-4842-4343-5_3

3. Components and Structure for Blazor Applications

Peter Himschoot1 
(1)
Melle, Belgium
 

In the previous chapter on data binding, you built a single monolithic application with Blazor. After a while, it will become harder and harder to maintain.

In modern web development, we build applications by constructing them from components, which typically are built from smaller components. A Blazor component is a self-contained chunk of user interface. Blazor components are classes built from Razor and C# with one specific purpose (also known as the principle of single responsibility) and are easier to understand, debug, and maintain. And of course, you can use the same component in different pages.

What Is a Blazor Component?

To put it in a simple manner, each CSHTML file in Blazor is a component. It’s that simple! A Razor file in Blazor contains markup and has code in the @functions section. Each page you in the MyFirstBlazor project is a component! And components can be built by adding other components as children.

Open the MyFirstBlazor project in Visual Studio (or Code) and let’s have a look at some of the components in there.

Open index.cshtml (Listing 3-1).
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
Listing 3-1

The Index Page

See SurveyPrompt? It is one of the components of the Blazor template. It takes one parameter, Title, which you can set where you want to use the component. Let’s have a good look at the SurveyPrompt component.

Examining the SurveyPrompt Component

Open SurveyPrompt.cshtml (see Listing 3-2), which can be found in the Shared folder of the client project.
<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>
    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold"
        href="https://go.microsoft.com/fwlink/?linkid=874928">
            brief survey
        </a>
    </span>
    and tell us what you think.
</div>
@functions {
[Parameter]
string Title { get; set; } // Demonstrates how a parent component can supply parameters
}
Listing 3-2

The SurveyPrompt Component

Look at the Razor markup. This simple component displays an icon in front of the Title, as shown in Figure 3-1, and then displays a link to the survey (which you should take ☺ because it will show Microsoft that you’re interested in Blazor).
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig1_HTML.jpg
Figure 3-1

The SurveyPrompt component

The @functions code section simply contains property Title, which uses one-way databinding for rendering in the component. Note the [Parameter] attribute. It is required for components that want to expose their properties to the parent component. Parameters cannot be public properties, and the compiler will give you an error when you try to make it so.

You might wonder why [Parameter] properties can’t be public. I asked Daniel Roth, who’s on the Blazor team, and this is his answer: “Think of parameters as like parameters to a method or constructor. They are not something you should generally be able to mutate externally to the component after they have been passed in.” Steve Sanderson, who is the key author of Blazor, explains that changing the value of a parameter from code will not behave as expected because change detection will not see the change. Changing the value through data binding shows the change.

Building a Simple Alert Component with Razor

Let’s build a simple Blazor component that will show a simple alert. Alerts are used to draw the user’s attention to some message, for example a warning.

Creating a New Component with Visual Studio

Open the MyFirstBlazor solution. Right-click the Pages folder and select Add > New Item. The Add New Item window should open, as in Figure 3-2.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig2_HTML.jpg
Figure 3-2

The Add New Item window

Select Razor View and name it Alert.cshtml. Click the Add button.

Creating a New Component with Code

Right-click the Pages folder of the client project and select New File. Name it Alert.cshtml.

Implement the Alert Component

Remove all existing content from Alert.cshtml and replace with Listing 3-3.
@if (Show)
{
  <div class="alert alert-secondary mt-4" role="alert">
    @ChildContent
  </div>
}
@functions {
[Parameter]
bool Show { get; set; }
[Parameter]
RenderFragment ChildContent { get; set; }
}
Listing 3-3

The Alert Component

The Alert component will display whatever content you nest in it (using bootstrap styling).

The default Blazor templates use Bootstrap 4 for styling. Bootstrap ( http://getbootstrap.com ) is a very popular CSS framework, originally build for Twitter, providing easy layout for web pages. However, Blazor does not require you to use Bootstrap, so you can use whatever styling you prefer. If you so, you must update all the Razor files in the solution to use the other styles, just like in regular web development. In this book, we will use Bootstrap.

The @ChildContent will hold this content and needs to be of type RenderFragment because this is the way the Blazor engine passes it (you will look at this later in this chapter).

Go back to Index.cshtml and add the Alert element. Visual Studio is smart enough to provide you with IntelliSense (see Figure 3-3) for the Alert component and its parameters! Visual Studio Code unfortunately (at the time of writing this chapter) does not offer IntelliSense yet.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig3_HTML.jpg
Figure 3-3

Visual Studio IntelliSense support for custom Blazor components

Complete the Alert and add a button as in Listing 3-4.
<Alert Show="@ShowAlert">
  <span class="oi oi-check mr-2" aria-hidden="true"></span>
  <strong>Blazor is soo cool!</strong>
</Alert>
<button class="btn btn-default" onclick="@ToggleAlert">
  Toggle
</button>
@functions {
public bool ShowAlert { get; set; } = true;
public void ToggleAlert()
{
  ShowAlert = !ShowAlert;
}
}
Listing 3-4

Using the Alert Component

Inside the <Alert> tag is a <span> displaying a checkmark icon and a <strong> element displaying a simple message. They will be set as the @ChildContent property of the Alert component. Build and run your project. When you click the <button>, it calls the ToggleAlert method, which will hide and show the Alert, as shown in Figure 3-4.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig4_HTML.jpg
Figure 3-4

The simple Alert component before clicking the Toggle button

Separating View and View-Model

You might not like this mixing of markup (view) and code (view-model). If you like, you can use two separate files, one for the view using Razor and another for the view model using C#. The view will display the data from the view model, and event handlers in the view will invoke methods from the view model. Some people prefer this way of working because it’s more like the MVVM pattern. Let’s try this!

Creating a DismissableAlert Component

If you haven’t done this yet, open the MyFirstBlazor solution. With Visual Studio, right-click the Pages folder and select Add ➤ New Item. The Add New Item dialog should open as shown in Figure 3-2. This time select Razor Page and name it DismissableAlert. With Visual Studio Code, right-click the Pages folder, select New File, and name it DismissableAlert.cshtml. Do this again to create a new file called DismissableAlert.cshtml.cs.

A DismissableAlert is an alert with a little x-button that the user can click to dismiss the alert. Replace the markup in the CSHTML file with Listing 3-5.
@if (Show)
{
<div class="alert alert-warning alert-dismissible fade show"
   role="alert">
  @ChildContent
  <button type="button" class="close" data-dismiss="alert"
          aria-label="Close" onclick="@Dismiss">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
}
Listing 3-5

The Markup for DismissableAlert.cshtml

Replace the C# code in DismissableAlert.cshtml.cs with Listing 3-6.
using System;
using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor;
namespace MyFirstBlazor.Client.Pages
{
    public class DismissableAlertViewModel : BlazorComponent
    {
        [Parameter]
        protected bool Show { get; set; } = true;
        [Parameter]
        protected RenderFragment ChildContent { get; set; }
        public void Dismiss()
        {
            Console.WriteLine("Dismissing alert");
            Show = false;
        }
    }
}
Listing 3-6

The Code for DismissableAlert.cshtml.cs

Note that the Show and ChildContent properties are now protected properties. Otherwise you will not be able to reference them from the Razor file. Also important is to inherit here from BlazorComponent. We will come back to BlazorComponent later in this chapter.

The DismissableAlertViewModel class will serve as the base class for the Razor file, which you need to indicate with an @inherits at the top of the markup, which you can find in Listing 3-7.
@inherits DismissableAlertViewModel
@if (Show)
{
<div class="alert alert-warning alert-dismissible fade show"
     role="alert">
  @ChildContent
  <button type="button" class="close" data-dismiss="alert"
          aria-label="Close" onclick="@Dismiss">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
}
Listing 3-7

Making the CSHTML Inherit from the View Model

So instead of putting your code in the @functions section of a Razor file you can put the code in a base class and then inherit from it in the Razor file.

Which model is best? I don’t think either one is better than the other; it is more a matter of taste. Choose the one you like.

Referring to a Child Component

Parent and child components typically communicate through data binding. For example, in Listing 3-8 you use DismissableAlert, which communicates with the parent component through the parent’s ShowAlert property. Clicking the Toggle button will hide and show the alert. You can try this by replacing the contents of Index.cshtml with Listing 3-8.
<DismissableAlert Show="@ShowAlert">
    <span class="oi oi-check mr-2" aria-hidden="true"></span>
    <strong>Blazor is soo cool!</strong>
</DismissableAlert>
<button class="btn btn-default" onclick="@ToggleAlert">Toggle</button>
@functions {
public bool ShowAlert { get; set; } = true;
public void ToggleAlert()
{
    ShowAlert = !ShowAlert;
}
Listing 3-8

Using DismissableAlert

Instead of using data binding in the interaction between the parent and child component, you can also directly interact with the child component. Let’s look at an example. Say you want the alert to disappear automatically after 5 seconds.

Adding a Timer Component

Start by adding a new class called Timer to the Pages folder as shown in Listing 3-9 (the timer will not have any visual part, so you don’t even need CSHTML to build the view). This Timer class will invoke a delegate (Tick) after a certain number of seconds (TimeInSeconds) have expired. The Tick parameter is of type Action, which is one of the built-in delegate types of .NET. An Action is simply a method returning a void with no parameters. There are other generic Action types, such as Action<T>, which is a method returning a void with one parameter of type T.
using System;
using Microsoft.AspNetCore.Blazor.Components;
namespace MyFirstBlazor.Client.Pages
{
  public class Timer : BlazorComponent
  {
    [Parameter]
    protected double TimeInSeconds { get; set; }
    [Parameter]
    protected Action Tick { get; set; }
    protected override void OnInit()
    {
      base.OnInit();
      var timer = new System.Threading.Timer(
        (_) => Tick.Invoke(),
        null,
        TimeSpan.FromSeconds(TimeInSeconds),
        System.Threading.Timeout.InfiniteTimeSpan);
    }
  }
}
Listing 3-9

The Timer Class

Now add the Timer component to the index page, as shown in Listing 3-10. Let’s look at a couple of things. First, you add a reference to the dismissableAlert component using the ref syntax. This will allow you to reference the component from your code.
<DismissableAlert ref="dismissableAlert"
                  Show="@ShowAlert">
    <span class="oi oi-check mr-2" aria-hidden="true"></span>
    <strong>Blazor is soo cool!</strong>
</DismissableAlert>
<Timer TimeInSeconds="5" Tick="@DismissAlert" />
Listing 3-10

Adding the Timer Component to Dismiss the Alert

Be careful using <Timer></Timer>. Any content, even blank spaces, will be seen as ChildContent, and since Timer doesn’t support any you might get compiler errors. It’s better to use a single element <Timer/>.

This requires that you add a field called dismissableAlert of type DismissableAlert to the parent, which will contain the reference to the child component, as you can see in Listing 3-11.
@functions {
  public DismissableAlert dismissableAlert;
  public bool ShowAlert { get; set; } = true;
  public void ToggleAlert()
  {
    ShowAlert = !ShowAlert;
  }
  public void DismissAlert()
  {
    dismissableAlert.Dismiss();
  }
}
Listing 3-11

Using a Field to Refer to the Child Component

Now, when the timer runs out of time, it invokes its Tick method, which calls DismissAlert. DismissAlert calls the Dismiss method on the dismissableAlert reference, which should then hide the alert.

Run the application and wait at least 5 seconds. The alert does not hide itself! Why?!

Using Component-to-Component Data Binding

So why doesn’t your DismissableComponent hide itself after 5 seconds?

Look at the markup, which is in Listing 3-10, for DismissibleAlert again. It shows the component based on the Show parameter, and it gets set through data binding. The problem is that the parent Index component’s ShowAlert stays true. Changing the value of the DismissableAlert local show field will not update the Index component’s ShowAlert property. What you need is two-way data binding between components, and Blazor has that.

With two-way data binding, changing the value of the Show parameter will update the value of the ShowAlert property of the parent, and vice versa.

Open the DismissableAlertViewModel class and change the Show property implementation, as shown in Listing 3-12. Here you add an extra parameter that should be called <<yourproperty>>Changed and should be of type Action<<typeofyourproperty>>.
public class DismissableAlertViewModel : BlazorComponent
{
  private bool show = true;
  [Parameter]
  protected bool Show
  {
    get => show;
    set
    {
      if (show != value)
      {
        show = value;
        ShowChanged?.Invoke(show);
      }
    }
  }
  [Parameter]
  protected Action<bool> ShowChanged { get; set; }
  [Parameter]
  protected RenderFragment ChildContent { get; set; }
  public void Dismiss()
  {
    Show = false;
  }
}
Listing 3-12

The DismissableAlertViewModel Class with Two-Way Binding Support

Now whenever someone or something changes the Show property’s value, the property’s setter triggers the ShowChanged delegate. This means the parent component can inject some code into the ShowChanged delegate property, which will invoke when the property is changed (internally or externally).

Remember to check if the value has changed. This will help you avoid a nasty bug where the child property updates the parent property, which triggers the child property to update, and so on ad infinitum.

Run again. Still, the alert does not disappear. Think about this. You invoke a method asynchronously using a Timer. When the timer fires, you set the ShowAlert property to false. But you still need to update the UI. You could do this by calling StateHasChanged in the DismissAlert method from Listing 3-11. But there is a better way, which is shown in Listing 3-13. Here you call StateHasChanged whenever the ShowAlert property gets a new value.
public bool ShowAlert
{
  get => showAlert;
  set
  {
    if (showAlert != value)
    {
      showAlert = value;
      this.StateHasChanged();
    }
  }
}
Listing 3-13

Updating the UI When ShowAlert Changes Value

Run. Wait 5 seconds.

The alert should automatically hide, as illustrated by Figure 3-5 and Figure 3-6.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig5_HTML.jpg
Figure 3-5

The alert being shown

../images/469993_1_En_3_Chapter/469993_1_En_3_Fig6_HTML.jpg
Figure 3-6

The alert automatically hides after 5 seconds

Building a Component Library

Components should be reusable. But you don’t want to reuse a component between projects by copy-pasting the component between them. In this case, it is much better to build a component library and, as you will see, this is not hard at all! What you will do here is move the DismissableAlert and Timer component to a library and then you will use this library in your Blazor project.

Creating the Component Library Project

For the moment, you cannot create Blazor component libraries from Visual Studio, so you will have to use the command-line prompt.

Open a command prompt or use the integrated terminal from Visual Studio Code (you can use Ctrl-` as a shortcut to toggle the terminal in Code). Change the current directory to the solution folder. Type in following command:
dotnet new blazorlib -o MyFirstBlazor.Components

The dotnet new command will create a new project based on a template. The template you want is the blazorlib template. If you want the project to be created in a subdirectory, you can specify it using the -o subdirectory parameter.

Executing this command should show you output like:
The template "Blazor Library" was created successfully.
Add it to your solution by typing in the next command:
dotnet sln add MyFirstBlazor.ComponentsMyFirstBlazor.Components.csproj

This time you want to change the solution, and dotnet sln add allows you to add a project (which is the last argument) to the solution. When you go back to Visual Studio, it will tell you about a file modification, as shown in Figure 3-7.

Simply press Reload to continue working.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig7_HTML.jpg
Figure 3-7

Visual Studio detected changes made to the solution

Adding Components to the Library

Previously, you built a couple of components. Some of them are very reusable, so you will move them to your library project. Start with Timer.

Drag-and-drop the Timer.cs file from your client project to the components project. You should see a new Timer.cs file, as illustrated by Figure 3-8.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig8_HTML.jpg
Figure 3-8

Copying the Timer.cs file to the Components project

Visual Studio creates a copy of the file, so remove the Timer.cs file from the client project (no need to do this with Code). Right-click the Timer.cs file in the client project and select Delete, as in Figure 3-9.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig9_HTML.jpg
Figure 3-9

Deleting a file from a project

Do the same for DismissableAlert.cshtml. Both components are still using the client’s namespace, so update their namespace to MyFirstBlazor.Components, as shown in Listing 3-14.
@inherits DismissableAlertViewModel
@if (Show)
{
<div class="alert alert-warning alert-dismissible fade show"
     role="alert">
  @ChildContent
  <button type="button" class="close" data-dismiss="alert"
          aria-label="Close"
          onclick="@Dismiss">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
}
Listing 3-14

Dismissing the Alert

Building the solution will still trigger compiler errors from the client project because you need to add a reference from the client project to the component library, which you will fix in the next part.

Refering to the Library from Your Project

Now that your library is ready, you are going to use it in your project. The way the library works is that you can use it in other projects. Hey, you could even make it into a NuGet package and let the rest of the world enjoy your work!

Referring to Another Project with Visual Studio

Start by right-clicking your client project and selecting Add ➤ Reference. Visual Studio will show Figure 3-10.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig10_HTML.jpg
Figure 3-10

Adding a reference to another project

Make sure you check MyFirstBlazor.Components and click OK.

Referring to Another Project with Code

Open the MyFirstBlazor.Client.csproj file and add another <ProjectReference> element to it, as shown in Listing 3-15. It’s the last <ProjectReference> from Listing 3-15 you need to add.
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <LangVersion>7.3</LangVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference
      Include="Microsoft.AspNetCore.Blazor.Browser"
      Version="0.5.1" />
    <PackageReference
      Include="Microsoft.AspNetCore.Blazor.Build"
      Version="0.5.1" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..MyFirstBlazor.SharedMyFirstBlazor.Shared.csproj" />
    <ProjectReference Include="..MyFirstBlazor.ComponentsMyFirstBlazor.Components.csproj" />
  </ItemGroup>
</Project>
Listing 3-15

Adding a Reference to Another Project

Now you have added the component library to your project, but if you want to use the components in your own CSHTML files, you must refer to your component library in your CSHTML files.

Understanding Tag Helpers

ASP.NET Core introduced tag helpers. Tag helpers are custom elements that get converted to standard HTML elements at runtime. If you use regular ASP.NET Core MVC, the server will convert the tag helpers into HTML, and in Blazor the client will convert tag helpers. As a matter of fact, any Blazor component automatically becomes a tag helper. Visual Studio automatically recognizes tag helpers from the current project, but you need to give it a hand for component libraries, and you do that with @addTagHelper.

Open _ViewImports.cshtml and add the @addTagHelper as in Listing 3-16.
@layout MainLayout
@addTagHelper *, MyFirstBlazor.Components
Listing 3-16

Adding Components from a Blazor Library

Here you include all custom components using a wildcard (*) from the MyFirstBlazor.Components library. From now on you can use any component from this library as a HTML tag, for example <Timer/>.

When you build, you will still get a compile error, and this is because you are using the DismissableAlert type in your functions. And just like any other type, you can either refer to it using its full name including the namespace, or you can add a using statement to Index.cshtml as in Listing 3-17.
@page "/"
@using MyFirstBlazor.Components
Listing 3-17

Adding a using Statement to Refer to Types from the Namespace

Build and run your solution. It should look like Figure 3-5. Congratulations. You’ve just built and consumed your first Blazor component library!

Refactoring PizzaPlace into Components

In the previous chapter on data binding you built a web site for ordering pizzas. It used only one component with three different sections. Let’s split up this component into smaller, easier-to-understand components and try to maximize reuse.

Creating a Component to Display a List of Pizzas

Open the PizzaPlace Blazor project from the previous chapter. Start by reviewing index.cshtml. This is your main component, and it has three main sections: a menu, a shopping basket, and customer information.

The menu lists the pizzas and displays each one with a button to order. The shopping basket also displays a list of pizzas (but now from the shopping basket) with a button to remove them from the order. Looks like both have something in common: they need to display pizzas with an action you choose by clicking the button.

Add a new component to the Pages folder called PizzaItem with contents from Listing 3-18. You can copy most of the markup from the Index component with some changes.
@using PizzaPlace.Shared
<div class="row">
  <div class="col">
    @Pizza.Name
  </div>
  <div class="col">
    @Pizza.Price
  </div>
  <div class="col">
    <img src="@SpicinessImage(Pizza.Spiciness)"
         alt="@Pizza.Spiciness" />
  </div>
  <div class="col">
    <button class="@ButtonClass"
            onclick="@(() => Selected(Pizza))">Add</button>
  </div>
</div>
@functions {
[Parameter]
protected Pizza Pizza { get; set; }
[Parameter]
protected string ButtonTitle { get; set; }
[Parameter]
protected string ButtonClass { get; set; }
[Parameter]
protected Action<Pizza> Selected { get; set; }
private string SpicinessImage(Spiciness spiciness)
=> $"images/{spiciness.ToString().ToLower()}.png";
}
Listing 3-18

The PizzaItem Component

The PizzaItem component will display a pizza, so it should not come as a surprise that it has a Pizza parameter. This component also displays a button, but how this button looks and behaves will differ depending on where you use it. And that is why it has a ButtonTitle and ButtonClass parameter to change the button’s look, and it also has a Selected action that gets invoked when you click the button.

You can now use this component to display the menu. Add a new component to the Pages folder called PizzaList.cshtml as in Listing 3-19.
@using PizzaPlace.Shared
<h1>@Title</h1>
@foreach (var pizza in Menu.Pizzas)
{
  <PizzaItem Pizza="@pizza" ButtonTitle="Order"
             ButtonClass="btn btn-success"
             Selected="@((p) => Selected(p))" />
}
@functions {
[Parameter]
protected string Title { get; set; }
[Parameter]
protected Menu Menu { get; set; }
[Parameter]
protected Action<Pizza> Selected { get; set; }
}
Listing 3-19

The PizzaList Component

The PizzaList component displays a Title and all the pizzas from the Menu, so it takes them as parameters. It also takes a Selected action that you invoke by clicking the button next to a pizza. Note that the PizzaList component uses the PizzaItem component to display each pizza, and that the PizzaList Selected action is passed directly to the PizzaItem Selected action. The Index component will set this action, and it will be executed by the PizzaItem component.

With PizzaItem and PizzaList ready, you can use them in Index, which you can find in Listing 3-20.
<!-- Menu -->
<PizzaList Title="Our selected list of pizzas"
           Menu="@State.Menu"
           Selected="@((pizza) => AddToBasket(pizza))"/>
<!-- End menu -->
Listing 3-20

Using the PizzaList Component in Index

Run the application and try to order a pizza. The shopping basket does not display when you click the Order buttons! Why? Because the UI does not get updated. You need to fix this. You have already seen how to do so. Think about it.

Updating the UI after Changing the State Object

Start by changing the AddToBasket method from Index, as in Listing 3-21. After you add an item to the shopping basket, you call StateHasChanged. This method tells Blazor that it should update the UI with new data.
private void AddToBasket(Pizza pizza)
{
  Console.WriteLine($"Added pizza {pizza.Name}");
  State.Basket.Add(pizza.Id);
  StateHasChanged();
}
Listing 3-21

Calling StateHasChanged in AddToBasket

Run and order a couple of pizzas. It works!

Think about this. Components rerender themselves after events, but only themselves. When a component makes a change affecting other components, you need to call StateHasChanged on the affected components.

Showing the ShoppingBasket Component

Add a new component called ShoppingBasket to the Pages folder and change its contents to Listing 3-22.
@using PizzaPlace.Shared
@if (Basket.Orders.Any())
{
  <h1>@Title</h1>
  @foreach (var (pizza, pos) in Pizzas)
  {
    <PizzaItem Pizza="@pizza" ButtonClass="btn btn-danger"
               ButtonTitle="Remove"
               Selected="@(p => Selected(pos))" />
  }
  <div class="row">
    <div class="col"> Total: </div>
    <div class="col"> @TotalPrice </div>
    <div class="col"> </div>
    <div class="col"> </div>
  </div>
}
@functions {
[Parameter]
protected string Title { get; set; }
[Parameter]
protected Basket Basket { get; set; }
[Parameter]
protected Func<int, Pizza> GetPizzaFromId { get; set; }
[Parameter]
protected Action<int> Selected { get; set; }
protected IEnumerable<(Pizza pizza, int pos)> Pizzas { get; set; }
protected decimal TotalPrice { get; set; }
protected override void OnParametersSet ()
{
  base.OnParametersSet ();
  Pizzas = Basket.Orders.Select((id, pos) => (pizza: GetPizzaFromId(id), pos: pos));
  TotalPrice = Pizzas.Select(tuple => tuple.pizza.Price).Sum();
}
}
Listing 3-22

The ShoppingBasket Component

The ShoppingBasket component is similar to the PizzaList component, but there are some big differences. The basket class keeps track of the order using only ids of pizzas, so you need something to get the pizza object. This is done through the GetPizzaFromId delegate. Another change is the OnParametersSet method. The OnParametersSet method gets called when the component’s parameters have been set. Here you override it to build a list of (pizza, position) tuples that you need during data binding, and to calculate the total price of the order.

Tuples are just another type in C#. But modern C# offers a very convenient syntax; for example, IEnumerable<(Pizza pizza, int post)> means you have a type that is a list of pizza and position pairs.

Using the ShoppingBasket component in Index is easy, as you can see in Listing 3-23.
<!-- Shopping Basket -->
<ShoppingBasket Title="Your current order"
                Basket="@State.Basket"
                GetPizzaFromId="@State.Menu.GetPizza"
                Selected="@(pos => RemoveFromBasket(pos))" />
<!-- End shopping basket -->
Listing 3-23

Using the ShoppingBasket Component

Creating a Validation Component Library

The third section of the Index component is about entering and validating details about the customer. You could say that validation is a very common thing, but there is no built-in validation in Blazor so you will create a component library for validation and then use it for building the CustomerEntry component. The Customer class already implements the INotifyDataErrorInfo interface, so this part does not need to change.

Open a command prompt to the folder containing your solution. Type
dotnet new blazorlib -o PizzaPlace.Extensions.Validation

This creates a new Blazor library project.

Then type
dotnet sln add PizzaPlace.Extensions.Validation
PizzaPlace.Extensions.Validation.csproj

This adds the new project to your solution.

Go back to Visual Studio and click Reload. (No need to do this with Code.) Right-click the PizzaPlace.Extentions.Validation project and add a new Razor View called ValidationError and complete is as in Listing 3-24.
@using Microsoft.AspNetCore.Blazor
@using System.ComponentModel
@if (Errors.Any())
{
  <ul class="validation-error">
    @foreach (string error in Errors)
    {
      <li>@error</li>
    }
  </ul>
}
@functions {
[Parameter]
protected object Subject { get; set; }
[Parameter]
protected string Property { get; set; }
public IEnumerable<string> Errors
{
  get
  {
    switch (Subject)
    {
      case null:
        yield return $"{nameof(Subject)} has not been set!";
        yield break;
      case INotifyDataErrorInfo ine:
        if (Property == null)
        {
          yield return $"{nameof(Property)} has not been set!";
          yield break;
        }
        foreach (var err in ine.GetErrors(Property))
        {
          yield return (string)err;
        }
        break;
      case IDataErrorInfo ide:
        if (Property == null)
        {
          yield return $"{nameof(Property)} has not been set!";
          yield break;
        }
        string error = ide[Property];
        if (error != null)
        {
          yield return error;
        }
        else
        {
          yield break;
        }
        break;
    }
  }
}
}
Listing 3-24

The ValidationError Component

You expect the Subject and Property parameters to be set to an object implementing either the IDataErrorInfo or INotifyDataErrorInfo interface. You use this to dynamically build the Error collection, which is then used to list any validation errors.

Remember the validation-error style you added in the previous chapter to change the color of validation errors? Move this CSS to the /content/styles.css file from the component library. This concludes the validation component library.

Adding the CustomerEntry Component

Add a reference to the PizzaPlace.Extensions.Validation library, as you saw earlier in this chapter. Now add a new Razor view called CustomerEntry to the Pages folder, as shown in Listing 3-25.
@using PizzaPlace.Shared
@addTagHelper *, PizzaPlace.Extensions.Validation
<h1>@Title</h1>
<fieldset>
  <p>
    <label for="name">Name:</label>
    <input id="name" bind="@Customer.Name" />
    <ValidationError Subject="@Customer"
                     Property="@nameof(Customer.Name)" />
  </p>
  <p>
    <label for="street">Street:</label>
    <input id="street" bind="@Customer.Street" />
    <ValidationError Subject="@Customer"
                     Property="@nameof(Customer.Street)" />
  </p>
  <p>
    <label for="city">City:</label>
    <input id="city" bind="@Customer.City" />
    <ValidationError Subject="@Customer"
                     Property="@nameof(Customer.City)" />
  </p>
  <button onclick="@(()=>Submit(Customer))"
          disabled="@Customer.HasErrors">Checkout</button>
</fieldset>
@functions {
[Parameter]
protected string Title { get; set; }
[Parameter]
protected string Title { get; set; }
[Parameter]
protected Customer Customer {get; set; }
[Parameter]
protected Action<Customer> Submit { get; set; }
[Parameter]
protected Action<Customer> Submit { get; set; }
}
Listing 3-25

The CustomerEntry Component

The CustomerEntry component uses a label and input element for each customer property. You also use a ValidationError component from your freshly built library to display any validation errors. Now you are ready to complete Index with this last component. Listing 3-26 shows the whole Index.cshtml
@page "/"
<!-- Menu -->
<PizzaList Title="Our selected list of pizzas"
           Menu="@State.Menu"
           Selected="@((pizza) => AddToBasket(pizza))" />
<!-- End menu -->
<!-- Shopping Basket -->
<ShoppingBasket Title="Your current order"
                Basket="@State.Basket"
                GetPizzaFromId="@State.Menu.GetPizza"
                Selected="@(pos => RemoveFromBasket(pos))" />
<!-- End shopping basket -->
<!-- Customer entry -->
<CustomerEntry Title="Please enter your details below"
               bind-Customer="@State.Basket.Customer"
               Submit="@((_) => PlaceOrder())"/>
<!-- End customer entry -->
@functions {
private State State { get; } = new State()
{
  Menu = new Menu
  {
    Pizzas = new List<Pizza>
{
new Pizza(1, "Pepperoni", 8.99M, Spiciness.Spicy),
new Pizza(2, "Margarita", 7.99M, Spiciness.None),
new Pizza(3, "Diabolo", 9.99M, Spiciness.Hot)
}
  }
};
private void AddToBasket(Pizza pizza)
{
  Console.WriteLine($"Added pizza {pizza.Name}");
  State.Basket.Add(pizza.Id);
  StateHasChanged();
}
private void RemoveFromBasket(int pos)
{
  Console.WriteLine($"Removing pizza at pos {pos}");
  State.Basket.RemoveAt(pos);
  StateHasChanged();
}
private void PlaceOrder()
{
  Console.WriteLine($"Placing order {State.Basket.Customer.ToJson()}");
}
}
Listing 3-26

The Index Component

Build and run the PizzaPlace application. Things should work like before, except for one thing. Remember the debugging tip from the previous chapter? When you change the name of the customer, this tip does not update correctly. Let’s fix this. The problem is as follows: when you change the name of the customer, the CustomerEntry component does change the customer’s name, but the Index component does not see this change. You can fix this by registering for changes in the customer. To register for changes in objects .NET has the INotifyPropertyChanged interface, which has been part of .NET since .NET 2.0 so you might be familiar with it. This interface is shown in Listing 3-27 and it only has the PropertyChanged event.
namespace System.ComponentModel
{
    public interface INotifyPropertyChanged
    {
        event PropertyChangedEventHandler PropertyChanged;
    }
}
Listing 3-27

The INotifyPropertyChanged Interface

Whenever a property of customer changes, it should trigger this event. Make the Customer class implement this interface, like in Listing 3-28.
public class Customer : INotifyDataErrorInfo,
                        INotifyPropertyChanged
Listing 3-28

The Customer Class Implements INotifyPropertyChanged

Now change the Customer's class Name property, as shown in Listing 3-29.
using System.Runtime.CompilerServices;
...
private string name;
 public string Name
 {
     get { return name; }
     set { name = value; OnPropertyChanged(); }
 }
Listing 3-29

The Customer Class’ Name Property

Whenever the property is modified, you trigger the PropertyChanged event using the OnPropertyChanged method from Listing 3-30.
private void OnPropertyChanged(
             [CallerMemberName] string propertyName = "")
{
  PropertyChanged?.Invoke(this,
    new PropertyChangedEventArgs(propertyName));
}
Listing 3-30

The OnPropertyChanged Method

I’ll explain the implementation a bit. You should pass the name of the property in the PropertyChanged event. You could pass this name as a string to the OnPropertyChanged method, but when you change the name of the property there is a large chance you will forget to update this string. It’s better to pass nothing and have the compiler figure out the name of the property. This can be done using the CallerMemberName attribute, which will make the compiler figure out the name of the caller, in this case the name of the property!

Which kind of code is the most maintainable and bug-free code you can write? Code you did not write!

Implement the Street and City properties in the same way.

Almost there. Open Index.cshtml. Add an OnInit method as in Listing 3-31. This method registers for changes in the customer and calls StateHasChanged, which will update the UI. This way you don’t need to worry about calling StateHasChanged when a Customer property gets modified.
protected override void OnInit() {
  this.State.Basket.Customer.PropertyChanged +=
    (sender, e) => this.StateHasChanged();
}
Listing 3-31

The OnInit Method

Build and run. Now when you make a change to the customer the debugging tip will update. You might think, “So what?” The customer could also be used by another component that needs to see changes.

Component Lifecycle Hooks

Every component has a couple of methods you can override to capture the lifecycle of the component. In this section, you will look at these lifecycle hooks because it’s very important to understand them very well. Putting code in the wrong lifecycle hook will likely break your component.

OnInit and OnInitAsync

When your component has been completely initialized, the OnInit and OnInitAsync methods are called. Implement one of these methods if you want to do some extra initialization after the component has been created, such as fetching some data from a server, like the FetchData component from the MyFirstBlazor project.

Use OnInit for synchronous code, as shown in Listing 3-32.
protected override void OnInit()
{
}
Listing 3-32

The OnInit Lifecycle Hook

Use OnOnitAsync (Listing 3-33) to call asynchronous methods, for example making REST calls (you will look at making REST calls in the next two chapters).
protected override async Task OnInitAsync()
{
}
Listing 3-33

The OnInitAsync Lifecycle Hook

OnParametersSet and OnParametersSetAsync

When you need one or more parameters for initialization, use OnParametersSet or OnParametersSetAsync instead of the OnInit/OnInitAsync methods. These methods get called after the component has been initialized and after the parameters have been data-bound. For example, you could have a DepartmentSelector component that allows the user to select a department from a company, and another EmployeeList component that takes the selected department as a parameter. The EmployeeList component can then fetch the employees for that department in its OnParametersSetAsync method.

Use OnParametersSet (Listing 3-34) if you are only calling synchronous methods.
protected override void OnParametersSet()
{
}
Listing 3-34

The OnParametersSet Method

Use OnParametersSetAsync (Listing 3-35) if you need to call asynchronous methods.
protected override async Task OnParametersSetAsync()
{
}
Listing 3-35

The OnParametersSetAsync Method

OnAfterRender and OnAfterRenderAsync

The OnAfterRender and OnAfterRenderAsync methods are called after Blazor has completely rendered the component. This means that the browser’s DOM has been updated with changes made to your Blazor component. You can use these methods to invoke JavaScript code that needs access to elements from the DOM (which we will cover in the JavaScript chapter).

Use OnAfterRender (Listing 3-36) to call synchronous methods, for example in JavaScript.
protected override void OnAfterRender()
{
}
Listing 3-36

The OnAfterRender Lifecycle Hook

Use OnAfterRenderAsync (Listing 3-37) to call asynchronous methods, for example JavaScript methods that return promises.
protected override async Task OnAfterRenderAsync()
{
}
Listing 3-37

The OnAfterRenderAsync Lifecycle Hook

IDisposable

If you need to run some cleanup code when your component is removed from the UI, implement IDisposable. You can implement this interface in Razor using @implements, as shown in Listing 3-38. Normally you put the @implements at the top of the CSHTML file.

Most of the time, dependency injection will take care of calling Dispose, so generally you won’t need to implement IDisposable if you only need to dispose your dependencies.
@implements IDisposable
Listing 3-38

Implementing the IDisposable Interface in a Component

The IDisposable interface requires you to implement a Dispose method, which you put in @functions, as in Listing 3-39.
@functions {
  public void Dispose()
  {
    // Cleanup resources here
  }
}
Listing 3-39

Implementing the Dispose Method

If you’ve separated the view and view model, you implement this interface on the view model.

Using Templated Components

Components are Blazor’s building blocks for reuse. Blazor also supports templated components where you can specify one or more UI templates as parameters, making templated components even more reusable! For example, your application could be using grids all over the place. You can now build a templated component for a Grid taking the type used in the grid as a parameter (very much like you can build a generic type in .NET) and specify the UI used for each item separately! Let’s look at an example.

Creating the Grid Templated Component

Open the MyFirstBlazor project you have been using. Now add a new component (a Razor view) to the MyFirstBlazor.Client project’s Pages folder and name it Grid as in Listing 3-40. This is a templated component because it states the TItem as a type parameter using the @typeparam TItem syntax. This is like a generic type stated in C# with public class List<T> where T is a type parameter.

You can have more than one type parameter. Simply list each type parameter using the @typeparam syntax.
@typeparam TItem
<table border="1">
  <thead>
    <tr>@Header</tr>
  </thead>
  <tbody>
    @foreach (var item in Items)
    {
      <tr>@Row(item)</tr>
    }
  </tbody>
  <tfoot>
    <tr>@Footer</tr>
  </tfoot>
</table>
@functions {
[Parameter]
RenderFragment Header { get; set; }
[Parameter]
RenderFragment<TItem> Row { get; set; }
[Parameter]
RenderFragment Footer { get; set; }
[Parameter]
IReadOnlyList<TItem> Items { get; set; }
}
Listing 3-40

The Grid Templated Component

The Grid component has four parameters. The Header and Footer parameter are of type RenderFragment, which represents some HTML that you can specify when you use the Grid component (you will look at an example right after exploring the Grid component further). Look for the <thead> element in Listing 3-40 in the Grid component. Here you use the @Header razor syntax to tell the Grid component to put the HTML for the Header parameter here (same thing for the Footer).

The Row parameter is of type RenderFragment<TItem>, which is a generic version of RenderFragment. In this case you can specify HTML with access to the TItem allowing you access to properties and methods of the TItem. The Items parameter is an IReadOnlyList<TItem> which can be data-bound to any class with the IReadOnlyList<TItem> interface. Look for the <tbody> element in Listing 3-40. You iterate over all the items (of type TItem) of the IReadOnlyList<TItem> and you use the @Row(element) Razor syntax to apply the Row parameter, passing the current item as an argument.

Using the Grid Templated Component

Now let’s look at an example of using the Grid templated component. Open the FetchData.cshtml component in the MyFirstBlazor.Client project. Replace the <table> (comment the <table> because you will come back to it in later chapters) with the Grid component in Listing 3-41.

The FetchData component uses a couple of things such as @page and @inject. I will discuss them in later chapters, so bear with the example.

The FetchData component uses the Grid component, specifying the Items parameter as the forecasts array of WeatherForecast instances. The compiler is smart enough to infer from this that the Grid’s type parameter (TItem) is the WeatherForecast type.
@using MyFirstBlazor.Shared
@page "/fetchdata"
@inject HttpClient Http
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <Grid Items="@forecasts">
    <Header>
      <th>Date</th>
      <th>Temp (Celcius)</th>
      <th>Summary</th>
    </Header>
    <Row Context="forecast">
      <!-- by default called context-->
      <td>@forecast.Date</td>
      <td>@forecast.TemperatureC</td>
      <td>@forecast.Summary</td>
    </Row>
    <Footer>
      <td colspan="3">Spring is in the air!</td>
    </Footer>
  </Grid>
  @*<table class="table">
  ...
  </table>*@
}
@functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
  forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
Listing 3-41

The FetchData Component

Now look at the <Header> parameter of the Grid component in Listing 3-41. This syntax binds whatever is inside the <Header> to the Grid’s Header parameter. In this example, you specify some table headers. The Grid puts them inside the <tr> element from Listing 3-40. Again, the <Footer> is similar.

Examine the <Row> parameter in Listing 3-41. Inside the <Row> you want to use the current item from the iteration in Listing 3-40. But how should you access the current item? By default, Blazor will pass the item as the context argument (of type TItem), so you access the date of the forecast instance as @context.Date. But you can override the name of the argument, and this is what you do with the Context parameters (provided by Blazor) using <Row Context="forecast">. Now the item from the iteration can be accessed using the forecast argument.

Run your solution and select the Fetch data link from the navigation menu. Admire your new templated component, shown in Figure 3-11!
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig11_HTML.jpg
Figure 3-11

Showing forecasts with the Grid templated component

Now you have a reusable Grid component that you can use to show any list of items by passing the list to the Items parameters and specifying what should be shown in the Header, Row, and Footer parameters! But there’s more!

Specifying the Type Parameter’s Type Explicitly

Normally the compiler can infer the type of the type parameter, but if this does not work as you expect you can specify the type explicitly. Simply specify the type of your type parameter by specifying it when you use the component, as shown in Listing 3-42.
<Grid Items="@forecasts" TItem="WeatherForecast">
Listing 3-42

Explicitly Specifying the Type Parameter

Razor Templates

You can also specify a RenderFragment or RenderFragment<TItem> using Razor syntax. A Razor template is a way to define a UI snippet, for example @<Row>...</Row>. In this case, you specify a RenderFragment without any arguments. But if you need to pass the argument to the Razor template, you use a lambda function. Let’s look at an example. Start by adding a new component called ListView, as shown in Listing 3-43. This will show an unordered list of items (of type TItem) using <ul> and <li> HTML elements.
@typeparam TItem
<ul>
  @foreach (var item in Items)
  {
    @ItemTemplate(item)
  }
</ul>
@functions {
[Parameter] RenderFragment<TItem> ItemTemplate { get; set; }
[Parameter] IReadOnlyList<TItem> Items { get; set; }
}
Listing 3-43

The ListView Templated Component

Now update the FetchData component as in Listing 3-44. Here you specify the <ListView>’s <ItemTemplate>, which is of type RenderFragment<TItem>, using a Razor template. Look at the forecastTemplate in Listing 3-44. It uses a lambda function, taking the forecast as an argument, which returns a RenderFragment<TItem> using the @<li>...</li> Razor syntax. In the <ListView> component’s <ItemTemplate> you simply invoke the lambda function.
@using MyFirstBlazor.Shared
@page "/fetchdata"
@inject HttpClient Http
@{
  RenderFragment<WeatherForecast> forecastTemplate =
    (forecast) => @<li>@forecast.Date.ToLongDateString() - @forecast.Summary</li>;
}
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <Grid Items="@forecasts" TItem="WeatherForecast">
  ...
  </Grid>
  <ListView Items="@forecasts" TItem="WeatherForecast">
    <ItemTemplate>
      @forecastTemplate(context)
    </ItemTemplate>
  </ListView>
}
@functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
  forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
Listing 3-44

Using Razor Templates to Specify the Render Fragment

Razor templates are a great way to reuse a UI snippet because you can invoke it in different components.

You can also call a Razor template directly in your component as in Listing 3-45.
@forecastTemplate(new WeatherForecast {
                    Date = DateTime.Now,
                    TemperatureC = 26,
                    Summary = "Nice!"
                 })
Listing 3-45

Invoking a Razor Template in Your Component

The Blazor Compilation Model

Every Razor (CSHTML) file gets compiled into C# code and it is very interesting to have a look at them. These files get generated in the obj subfolder of your project, and you can look at these generated files from Visual Studio. Select the PizzaPlace.Client project in Solution Explorer and click the Show All Files button shown in Figure 3-12.
../images/469993_1_En_3_Chapter/469993_1_En_3_Fig12_HTML.jpg
Figure 3-12

The Show All Files button

Now open the obj/Debug/netstandard2.0/Pages folder in Solution Explorer. Open PizzaItem.g.cs, which you can find in Listing 3-46. (I have left out some of the less important details.)
namespace PizzaPlace.Client.Pages
{
  public class PizzaItem : BlazorComponent
  {
    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
      base.BuildRenderTree(builder);
      builder.OpenElement(0, "div");
      builder.AddAttribute(1, "class", "row");
      builder.AddContent(2, "   ");
      builder.OpenElement(3, "div");
      builder.AddAttribute(4, "class", "col");
      builder.AddContent(5, "     ");
      builder.AddContent(6, Pizza.Name);
      builder.AddContent(7, "   ");
      builder.CloseElement();
      builder.AddContent(8, "   ");
      builder.OpenElement(9, "div");
      builder.AddAttribute(10, "class", "col");
      builder.AddContent(11, "     ");
      builder.AddContent(12, Pizza.Price);
      builder.AddContent(13, "   ");
      builder.CloseElement();
      builder.AddContent(14, "   ");
      builder.OpenElement(15, "div");
      builder.AddAttribute(16, "class", "col");
      builder.AddContent(17, "     ");
      builder.OpenElement(18, "img");
      builder.AddAttribute(19, "src", SpicinessImage(Pizza.Spicyness));
      builder.AddAttribute(20, "alt", Pizza.Spicyness);
      builder.CloseElement();
      builder.AddContent(21, "   ");
      builder.CloseElement();
      builder.AddContent(22, "   ");
      builder.OpenElement(23, "div");
      builder.AddAttribute(24, "class", "col");
      builder.AddContent(25, "     ");
      builder.OpenElement(26, "button");
      builder.AddAttribute(27, "class", ButtonClass);
      builder.AddAttribute(28, "onclick", Microsoft.AspNetCore.Blazor.Components.BindMethods.GetEventHandlerValue<Microsoft.AspNetCore.Blazor.UIMouseEventArgs>(() => Selected(Pizza)));
      builder.AddContent(29, ButtonTitle);
      builder.CloseElement();
      builder.AddContent(30, "   ");
      builder.CloseElement();
      builder.AddContent(31, " ");
      builder.CloseElement();
    }
[Parameter]
protected Pizza Pizza { get; set; }
[Parameter]
protected string ButtonTitle { get; set; }
[Parameter]
protected string ButtonClass { get; set; }
[Parameter]
protected Action<Pizza> Selected { get; set; }
private string SpicinessImage(Spiciness spiciness)
=> $"images/{spiciness.ToString().ToLower()}.png";
  }
}
Listing 3-46

The PizzaItem.g.cs Generated File

As you can see, the bulk of the generated code is the BuildRenderTree method. This method creates elements, attributes, content, and event handlers. For example, the original CSHTML file contains Listing 3-47, which gets generated as Listing 3-48.
<div class="col">
  @Pizza.Name
</div>
Listing 3-47

The Original Razor

builder.OpenElement(3, "div");
builder.AddAttribute(4, "class", "col");
builder.AddContent(5, "     ");
builder.AddContent(6, Pizza.Name);
builder.AddContent(7, "   ");
builder.CloseElement();
Listing 3-48

The Generated Code from Razor

If you really want, you can directly inherit from BlazorComponent and override the BuildRenderTree method and generate your custom HTML directly here. This is only interesting in some very advanced scenarios which I don’t cover in this book.

Summary

In this chapter, you explored building Blazor components and component libraries. You also learned how components can communicate with each other through parameters and data binding. You applied this learning by dividing the monolithic Index component of the PizzaPlace application into smaller components. You also saw that in Blazor you can build templated components, which resemble generic classes. These templated components can be parameterized to render different UIs, which makes them quite reusable! Finally, you had a look at component lifecycle hooks (which you will need in further chapters) and how Razor components get compiled into good old C# code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.47.230