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

You're reading from   Building Blazor WebAssembly Applications with gRPC Learn how to implement source generators and gRPC in your Blazor apps for better performance

Arrow left icon
Product type Paperback
Published in Nov 2022
Publisher Packt
ISBN-13 9781804610558
Length 196 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Václav Pekárek Václav Pekárek
Author Profile Icon Václav Pekárek
Václav Pekárek
Arrow right icon
View More author details
Toc

Table of Contents (10) Chapters Close

Preface 1. Chapter 1: Introducing Blazor, gRPC, and Source Generators 2. Chapter 2: Creating a Blazor WebAssembly Application FREE CHAPTER 3. Chapter 3: Creating a Database Using Entity Framework Core 4. Chapter 4: Connecting Client and Server with REST API 5. Chapter 5: Building gRPC Services 6. Chapter 6: Diving Deep into Source Generators 7. Chapter 7: Best Practices for C# and gRPC 8. Index 9. Other Books You May Enjoy

Creating Razor components

Razor components are the basic building blocks of our Blazor WebAssembly application. Each Razor component is represented by a class with a name similar to the filename. In the component itself, we can use C#, HTML, and Razor markup. This class is generated automatically for us, but we can create it ourselves. The component is downloaded to the browser as a part of the DLL for the whole application.

Razor components can be anything, from simple standard HTML for a headline, to more complex elements, such as tables that contain data, forms, or anything that can be rendered on the website. Each component can use other components.

Note

A Razor component name must be in Pascal case. Naming a component as pageTitle is not a valid name for the Razor component, because the p part is not a capital letter. PageTitle will be a valid name. The file extension for Razor components is .razor.

To prevent errors, you should also name components differently from existing HTML tags.

A simple component representing the page title can be created with just a single line of HTML code in the Headline.razor file:

Headline.razor

<h1>Page title</h1>

The preceding code shows the simplest type of Razor component. In the background, public partial class Headline is created for us.

Using components in other components

The Headline component itself can show just a small amount of data. We need to use this Headline component in other components. We can do this with standard HTML markup. The class name of the component is also the name of the HTML element. The following code shows how to use our Headline component in different components:

PageHeader.razor

<div class="header">
<Headline />
</div>

The preceding code will create another Razor component using our Headline component as part of the rendered HTML.

The Razor component class is created in the namespace to reflect the current folder structure of the project. If the Headline component exists in a different folder than the PageHeader component, the usage of the component must reflect the namespace of the Headline component:

<Structures.Headline />

In the preceding example, the Headline component is in the Structures folder. The Structures folder is in the same folder as the PageHeader component.

To prevent specifying the component namespace in the markup, we can add the @using directive to the parent component, or create a file called _Imports.razor and add @using directives there. The following code shows how to make the Headline component available for all other components in the same project:

_Imports.razor

@using SampleApp.Components.Structures

This simple component can be used for static HTML markup, such as loaders and icons.

Note

@using directives in C# files (.cs) are not applied to Razor components (.razor) and vice versa. The _Imports.razor file’s @using directives are only applied to Razor components.

When we need to render some dynamic data, we need to load it inside the component itself or pass it to the component from the parent component.

Passing parameters to components

Razor components use parameters to create dynamic content and conditional rendering. Parameters are public properties of the component class. These parameters are decorated with either the Parameter attribute or the CascadingParameter attribute. Parameters can be any type of object in C#, such as simple types, complex types, functions, and so on.

There are two types with special meaning for Razor components:

  • Event callback
  • RenderFragment

We will discuss these two types in the Creating Razor components section.

Let’s modify our Headline.razor component from a simple component with static text to a more dynamic component that can show data from parameters:

<h1>Page @Title</h1>
@code {
[Parameter]
public string Title { get; set; }
}

To use the preceding component in our PageTitle component, we will need to modify the line with component initialization using the following syntax:

<Headline Title="About us" />

The preceding code shows a parameter called Title that’s been added to our component. This parameter can be used to pass any string value from the parent component. The value is then used in the HTML markup. The Headline component will render Page About us text on the screen.

Note

At the time of writing this book, Razor syntax does not support the required attributes on the component parameters. In the latest version of Visual Studio, the EditorRequired attribute was introduced to help developers with this problem. The EditorRequired attribute tells the IDE that the marked parameter should be filled in when using the component. The RZ2012 warning is generated, but it will not prevent building the application. The following warning will be generated if we mark the Title parameter of our Headline component with the EditorRequired attribute and don’t fill in the parameter: Component ‘Headline’ expects a value for the parameter ‘Title’, but a value may not have been provided.

The component can have more parameters and can also read parameter values from its route (URL address) or query values. We will learn more about this in the Understanding page routing in Blazor section. The component should not update any of the Parameter or CascadingParameter properties by itself. To update the parameter, the event callback should be triggered.

In the next section, we will look at the RenderFragment parameter before covering event callbacks.

Creating components with child content

A common scenario when creating Razor components is the need to pass some additional content from the parent component. This content can be HTML inside the button tag, a figure element to wrap the image with its description, or a more complex HTML structure.

We can tell our component to render child content by providing a parameter whose type is RenderFragment and whose name is ChildContent. The following example renders complex HTML with content from the parent component:

Product.razor

<div class="product">
@ChildContent
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
}

The preceding code shows a Product component, which is a simple HTML div element with a class attribute and ChildContent parameter. This component can be used in other components to render a unified structure:

List.razor

…
<Product>
<div class="image">
<img src="demo_image.jpg" alt="Demo image" />
</div>
<div class="name">Product name</div>
<div class="info">Description & Price</div>
</Product>
…

The preceding code creates the List component using the Product component. The List component can contain much more code and render the Product component in the loop.

In this example, we can see that we do not need to specify the ChildContent attribute when using the Product component. Razor will take any content between the opening and closing tags of the component and pass it as a ChildContent parameter automatically.

In the List component, we need to write all the HTML that we want to render inside and repeat that for each product we want to render. This can lead to potential mistakes when reusing the component as it is easy to mistype any class name.

To help solve this, we can define as many RenderFragment parameters as we want. However, these parameters must be named by the caller component. In the following example, we are modifying the code of the Product component to allow us to use three sections – Image, Name, and Info:

Product.razor

<div class="product">
<div class="image">@Image</div>
<div class="name">@Name</div>
<div class="info">@Info</div> 
</div>
@code {
[Parameter]
public RenderFragment Image { get; set; }
[Parameter]
public RenderFragment Name { get; set; }
[Parameter]
public RenderFragment Info { get; set; }
}

Now that we have defined three parameters with the RenderFragment type, we can use them from the parent component by using the parameter name as an HTML tag nested in our component:

List.razor

…
<Product>
<Image>
<img src="demo_image.jpg" alt="Demo image" />
</Image>
<Name>Product name</Name>
<Info>Description & Price</Info>
</Product>
…

The preceding example is using all of the parameters from the Product component. You can omit any of them and use only the ones you need. The Product component can have conditions to render only the RenderFragment parameters, which are not null.

Interesting fact

The component can have both named RenderFragment parameters and ChildContent parameters. If the parameter is not specified, ChildContent is used. Using at least one of the named RenderFragment parameters leads to the need to name the ChildContent parameter as well.

Now that we know how to pass parameters downstream to child components, let’s learn how to update data in the parent component. This is where event callbacks will serve their purpose.

Communicating with the parent component

There are a lot of situations where we need to notify the parent component about some change. These changes can include events such as a click on the element, a change in the element state, double-clicking the element, and dragging and dropping the element. For that purpose, there is an EventCallback or EventCallback<T> property, which can be defined on our component to allow the parent component to react to the events.

The event callback can be without arguments, or with an argument type defined by the T type parameter. The following is a list of predefined argument classes with supported DOM events:

Argument Class

DOM Event

ClipboardEventArgs

oncut, oncopy, onpaste

DragEventArgs

ondrag, ondragstart, ondragenter, ondragleave, ondragover, ondrop, ondragend

ErrorEventArgs

onerror

EventArgs

This argument class is supported by most of the events and holds basic event data.

General

onactivate, onbeforeactivate, onbeforedeactivate, ondeactivate, onfullscreenchange, onfullscreenerror, onloadeddata, onloadedmetadata, onpointerlockchange, onpointerlockerror, onreadystatechange, onscroll

Clipboard

onbeforecut, onbeforecopy, onbeforepaste

Input

oninvalid, onreset, onselect, onselectionchange, onselectstart, onsubmit

Media

oncanplay, oncanplaythrough, oncuechange, ondurationchange, onemptied, onended, onpause, onplay, onplaying, onratechange, onseeked, onseeking, onstalled, onstop, onsuspend, ontimeupdate, ontoggle, onvolumechange, onwaiting

FocusEventArgs

onfocus, onblur, onfocusin, onfocusout

ChangeEventArgs

onchange, oninput

KeyboardEventArgs

onkeydown, onkeypress, onkeyup

MouseEventArgs

onclick, oncontextmenu, ondblclick, onmousedown, onmouseup, onmouseover, onmousemove, onmouseout

PointerEventArgs

onpointerdown, onpointerup, onpointercancel, onpointermove, onpointerover, onpointerout, onpointerenter, onpointerleave, ongotpointercapture, onlostpointercapture

WheelEventArgs

onwheel, onmousewheel

ProgressEventArgs

onabort, onload, onloaded, onloadstart, onprogress, ontimeout

TouchEventArgs

ontouchstart, ontouchend, ontouchmove, ontouchenter, ontouchleave, ontouchcancel

Table 2.1 – Event callback predefined argument classes

Standard HTML elements in Blazor have defined these arguments for events. The button’s onclick attribute will provide the event callback with the MouseEventArgs argument.

You can also define custom event arguments that allow you to transfer any kind of data to the parent component.

The event callback parameter on the child component is attached to the method with the same arguments on the parent component. The following code shows two components that communicate with each other using event callbacks:

LikeButton.razor

<button @onclick="@OnClickHandler">
Click to change color to black
</button>
@code {
[Parameter]
public EvenCallback<string> OnClickEvent { get; set; }
private async Task OnClickHandler()
{
  await OnClickEvent.InvokeAsync("black")
}
}

The preceding code creates a simple HTML button element with the @onclick directive. Clicking on this button will trigger the OnClickHandler method, notify the parent component about the event, and pass the arguments to the parent component. The parent component can perform some action if this notification occurs:

Parent.razor

<Header Title="Color selector" />
<LikeButton OnClickEvent="@OnClickAction" />
<p>Selected color is @color</p>
@code {
private string color = "white";
private void OnClickAction(string args)
{
  color = args;
}
}

In the preceding code, the parent component has attached a custom method for the OnClickEvent parameter. The attached method must consume the same type of parameters as the OnClickEvent type argument.

If you use predefined arguments on supported events, you can call EventCallback<T> directly from the @on… event:

<button @onclick="@OnClickEvent">Click Me</button>
@code {
  [Parameter]
  public EvenCallback<MouseEventArgs> OnClickEvent 
    { get; set; }
}

In the preceding example, OnClickEvent is called directly, without the need for a custom method. MouseEventArgs is passed to the parent component because it is supported by the prebuilt event.

There is another way to use events in the component itself. There can be a situation where you need to update the component’s data when an event occurs in the component. You can use Lambda expressions to achieve this update without the need for a custom method. In the following example, we are creating a counter that updates the information about button clicks in a single component:

Counter.razor

<p>Number of clicks: @count</p>
<button @onclick="@(e => count + 1)">
Click me to update counter
</button>
@code {
  int count = 0;
}

In the preceding code, the Counter component will update the count field with every click of the button. The e argument in the Lambda expression represents the event argument for the click event: MouseEventArgs.

The last type of event that’s used in a component is property two-way binding. Interactive elements on the website must be able to update themselves. The input element must update the text when the keyboard is pressed but also notify the parent component (for example, the form) that something has happened. Two-way binding is a way to automatically pair the Parameter property with the EventCallback property. In the following example, two-way binding has been enabled by using an input component with various properties:

CustomInput.razor

…
[Parameter]
public string BindingValue
{
get => _value;
set
{
  if (_value == value ) return;
  _value = value;
  BindingValueChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<string> BindingValueChanged 
  { get; set; }
…

The BindingValue and BindingValueChanged properties in the preceding code enable the two-way binding of the BindingValue property since there is an EventCallback property with the same name as the property, followed by Changed. The following component will create an input with two-way binding:

CustomForm.razor

…
<CustomInput @bind-BindingValue="@message" />
…
private string message = "";

In the preceding component, when the BindingValue property changes in the CustomInput component, the parent CustomForm component updates the value of the message field automatically.

Now that you know how components can communicate, it’s time to look at the component life cycle.

Understanding the component life cycle

The life cycle methods for Blazor components are defined in the BaseComponent class. Thus, all the components have to inherit – at any level – from the BaseComponent class. The BaseComponent class has both synchronous and asynchronous life cycle methods. If you need to modify component initialization or rendering, these methods can be overridden. Some methods are only called the first time the component is rendered; others are called every time the component has changed.

The following diagram shows the component life cycle events in order:

Figure 2.1 – Blazor component life cycle events

Figure 2.1 – Blazor component life cycle events

In the preceding diagram, you can see the events that are called when the component is initialized from the parent component. The SetParameterAsync and OnInitialized/OnInitializedAsync methods are only called when the component is rendered for the first time. The re-rendering of the component does not trigger these methods.

After the OnInitialized method, the OnParameterSet/OnParameterSetAsync methods are called. These methods are called whenever the component parameters change. The change of the method can trigger the re-rendering of the component.

When the ShouldRender method returns true or is in the first render cycle, the render tree is created. The render tree is then sent to the DOM. In the first render cycle, the render tree needs to be created for the first time, so the ShouldRender method is not called in this cycle and the process continues the same way as when the method returns true.

After the DOM is updated, the OnAfterRender/OnAfterRenderAsync methods are called. These methods should not trigger the component UI update since this would lead to the component looping between the StateHasChanged and OnAfterRender methods.

Structuring component code

Component code in the Razor component can be divided into three sections – the directive section at the top of the file, the section that contains Razor markup, and the C# code with defined properties, fields, and methods at the bottom of the file.

It is common to not need all of the sections in every component file. Our PageHeader.razor component only has the middle Razor markup section because there was no need for directives or additional C# code.

Having a lot of methods and functions in the component file can lower the readability of the component itself and can lead to potential mistakes. To prevent this, the @code block of the component can be moved to a separate C# file.

As we learned in the previous sections, the Razor component is translated into a C# partial class. To separate the component @code from the leading directives and Razor markup, we need to create the C# file with a partial class named the same as the component file. For example, in terms of our CustomInput.razor component file, the C# file will be named CustomInput.razor.cs.

We can create the file manually, or we can select the @code directive in our component. Open the Quick Action bar (Ctrl + . or Alt + Enter) and select Extract block to code behind. The Quick Action bar is shown in the following screenshot:

Figure 2.2 – The Quick Action bar

Figure 2.2 – The Quick Action bar

Clicking on Extract block to code behind, as shown in the preceding screenshot, will generate a new .cs file named ComponentName.razor.cs.

The created public partial class CustomInput has an inheritance from the ComponentBase class automatically, despite the inheritance not being specified in the .cs file.

Important note

To specify our custom base class for the component, our base class must inherit from the ComponentBase class in the Microsoft.AspNetCore.Components namespace. Also, we need to inherit from our base class in the .cs file and the .razor file using the @inherits directive.

The Razor component can be a small part of the website or a whole page with many different components on it. The only difference is in the directive used at the top of the file. Let’s look at how we can specify the component’s URL address.

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