Building Blazor WebAssembly Applications with gRPC

By Václav Pekárek
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    Chapter 2: Creating a Blazor WebAssembly Application
About this book

Building Blazor WebAssembly Applications with gRPC will take you to the next level in your web development career. After working through all the essentials of gRPC, Blazor, and source generators, you will be far from a beginner C# developer and would qualify as a developer with intermediate knowledge of the Blazor ecosystem.

After a quick primer on the basics of Blazor technology, REST, gRPC, and source generators, you’ll dive straight into building Blazor WASM applications. You’ll learn about everything from two-way bindings and Razor syntax to project setup. The practical emphasis continues throughout the book as you steam through creating data repositories, working with REST, and building and registering gRPC services. The chapters also cover how to manage source generators, C# and debugging best practices, and more. There is no shorter path than this book to solidify your gRPC-enabled web development knowledge.

By the end of this book, your knowledge of building Blazor applications with one of the most modern and powerful frameworks around will equip you with a highly sought-after skill set that you can leverage in the best way possible.

Publication date:
November 2022
Publisher
Packt
Pages
196
ISBN
9781804610558

 

Creating a Blazor WebAssembly Application

Blazor WebAssembly applications use Razor components as building blocks. The component itself is a reusable part of the UI that can be used anywhere in our application. Razor components are a combination of HTML and C# code; however, the HTML part of the component is compiled into C# code and emitted in the final DLL. The component files are not standalone files like in standard .html pages.

In this chapter, we will learn about Razor components. We will understand how these components are built, how they communicate with other components on the page, and what their life cycle is. We will learn how to create and use components, how to add parameters to components, and how these components can notify parents that something has happened. This is useful when we want to notify the parent about the data changing, a button or link being clicked, and so on.

We will also learn two ways of defining C# code for the components, in addition to learning how to specify the route URL for the component and how to read data from the website URL address.

Finally, we will take a look at the project we want to build and start building it using the default Blazor WebAssembly App project template provided by Microsoft.

By the end of this chapter, you will understand how to create a new project for a Blazor WebAssembly application using Visual Studio. You will also understand the concept of Razor components, how they communicate, and how Blazor uses these components to render the content on the websites.

In this chapter, we will cover the following topics:

  • Learning how to write Razor syntax
  • Creating Razor components
  • Understanding page routing in Blazor
  • Project overview and preparation
 

Technical requirements

 

Learning how to write Razor syntax

Razor syntax is a combination of HTML, Razor markup, and C#. The syntax is similar to other JavaScript SPA frameworks, such as Vue.js, React, and Angular. The HTML in the Razor component is rendered the same way as it is in the HTML file. In the Razor component, you can fluently move between HTML markup and C# code.

Razor uses the @ symbol to determine the Razor syntax. If the @ symbol is followed by a reserved keyword, then the code is transitioned to Razor syntax. This Razor syntax can add dynamic logic to our components. The expressions can be implicit, explicit, inline, or in the form of code blocks.

Writing implicit Razor expressions

Implicit Razor expressions start with the @ symbol, followed by C# code:

<span>It is @DateTime.Now</span>

In the preceding code, @ is followed by the C# code directly. Implicit Razor expressions can’t contain spaces or generic calls, because < and > symbols in the generic call are translated as HTML in the Razor syntax. The only exception is the await keyword, which can be followed by a space. If the method is used in the implicit Razor syntax call, the spaces can be used in the arguments.

Writing explicit Razor expressions

Explicit Razor expressions start with @, followed by parentheses:

<span>It was @(DateTime.Now – TimeSpan.FromDays(7))</span>

The preceding code renders the date in the last week. Explicit Razor expressions are used when we can’t access the result value directly in the property or field or when we need to calculate some result, use generic calls, or prevent the value in the HTML string from being concatenated. Here is an example:

@{
var addr = new Address(Street: "Fairmont St NW", City: "Washington", "PostalCode": "20009");
}
<span>The address is@addr.Street @addr.City @addr.PostalCode</span>

The preceding code will render the following output HTML:

<span>The address is@addr.Street Washington 20009</span>

In the preceding output, addr.Street is not replaced by the property value because the term may comprise the syntax of an email address – that is, is@addr.Street. When we need to use a directive just after a letter, we need to use the explicit Razor syntax:

<span>The address is@(addr.Street) @addr.City @addr.PostalCode</span>

As expected, the preceding code will generate the output HTML:

<span>The address isFairmont St NW Washington 20009</span>

Important information

If the @ symbol follows the letter, it is not translated to the Razor directive. If you need to render @ in the HTML, you can escape the symbol with another @.

For example, @@MyUserName will render in the HTML as @MyUserName.

Writing inline expressions

Inline expressions are all of the expressions on a single line. Both implicit and explicit expressions are inline expressions.

Writing code block expressions

When creating our Razor components, we need to use some functionality other than just the properties or fields from C#. The code block expressions start with the @ symbol and are enclosed within curly brackets:

@{
var title = "About Blazor WebAssembly";
}
<h2>@title</h2>
@{
title = "Razor syntax";
}
<h2>@title</h2>

The preceding code will generate the following output HTML:

<h2>About Blazor WebAssembly</h2>
<h2>Razor syntax</h2>

As you can see in the preceding output, the generated HTML consists of two headlines.

Block expressions can also be used to create local functions to render repeated HTML code:

@{
void RenderTitle(string title)
{
<h2>@title</h2>
}
RenderTitle("About Blazor WebAssembly");
RenderTitle("Razor syntax");
}

The output HTML generated from the preceding code will be the same as in the first example in this section. We do not need to modify the variable each time we want to render the title because we can use the RenderTitle function defined in the code block. This example shows simple code, but in the real world, the local function will contain more complex code.

Writing control structures

Control structures are specific types of code blocks. They are used when the code block has some specific meaning to the code, such as conditions, loops, and so on.

Conditions

We can use the if directive to start a code block with conditions:

@if (chapterNumber == 1)
{
  <p>This is the first chapter.</p>
}
else if (chapterNumber == 6)
{
  <p>This is the last chapter.</p>
}
else
{
  <p>Chapter @chapterNumber</p>
}

The preceding code shows a full if - else if – else statement for rendering paragraph information for each chapter in this book. The same condition can be applied by the switch statement:

@switch (chapterNumber)
{
  case 1:
    <p>This is the first chapter.</p>
    break;
  case 6:
    <p>This is the last chapter.</p>
    break;
  default:
    <p>Chapter @chapterNumber</p>
    break;
}

The preceding switch statement will generate the same HTML result as the if statement.

Loops

The following loop types are supported in Razor syntax:

  • for
  • foreach
  • while
  • do while

Each loop starts with the @ symbol. Looping through a collection of items helps you in situations where you want to apply some repetitive code to each item in the collection. The following example shows the looping of chapters:

@{
  var chapters = new Chapter[]
  {
    new Chapter(1, "Learning Razor syntax"),
    new Chapter(2, "Creating Razor components"),
    new Chapter(3, "Understanding page routing in Blazor"),
    …
  };
}

The preceding code defines a new chapters variable that will be used in the following loop examples:

  • The following is an example of a for loop:
    @for (var i = 0; i < chapters.Length; i++)
    {
      var chapter = chapters[i];
      <h2>Chapter no. @chapter.Number: @chapter.Title</h2>
    }
  • The following is an example of a foreach loop:
    @foreach (var chapter in chapters)
    {
      <h2>Chapter no. @chapter.Number: @chapter.Title</h2>
    }
  • The following is an example of a while loop:
    @{ var i = 0; }
    @while (i < chapters.Length)
    {
      var chapter = chapters[i];
      <h2>Chapter no. @chapter.Number: @chapter.Title</h2>
      i++;
    }
  • The following is an example of a do while loop:
    @{ var i = 0; }
    @do
    {
      var chapter = chapters[i];
      <h2>Chapter no. @chapter.Number: @chapter.Title</h2>
      i++;
    }
    while (i < chapters.Length)

As you can see, the Razor syntax is similar to the C# syntax. The only difference is in the transition between C# code and HTML code.

Razor syntax allows some other keywords to be used in the code blocks, such as @using (for disposable objects), @try, catch, finally, and @lock. The HTML and C# comments are also allowed.

Writing top-level directives

Razor directives, which are at the top of the file, control many aspects of the component. They can change the way the component is compiled, how the component is used on the website, or who can view the component’s output. The directives should be at the top of the file, but the order of the directives is not defined. Here is the list of these top-level directives:

  • @page: This directive specifies the page where the component is rendered. We will learn more about this in the following sections. Here is an example of the directive for the home page:
    @page "/home"
  • @namespace: The @namespace directive can override the namespace for the component. The default component namespace is based on the folder structure. Here is an example of namespace overriding:
    @namespace DemoProject.MyLibrary.Components
  • @inherits: When we want to change the base class of the component, we need to use the @inherits directive, as shown in the following example:
    @inherits CustomComponentBase
  • @implements: Our component can implement any interface. This directive can be used multiple times. Here is an example of implementing two interfaces:
    @implements IDisposable
    @implements ISortable
  • @layout: The @layout directive is only used on the component that contains the @page directive. The default layout is used if nothing has been set. If the page directive is missing, the @layout directive is ignored. Here is an example of the layout directive setting the layout to LoginLayout:
    @layout LoginLayout
  • @attribute: The @attribute directive adds a class-level attribute to the component class. This directive can be used multiple times. Here is an example of marking a component for only authorized viewing:
    @attribute [Authorize]
  • @using: This directive can be used multiple times to import namespaces to the component scope:
    @using DemoProject.Model
  • @typeparam: The @typeparam directive is mostly used in combination with the generic base class in the @inherits directive. If the base class contains multiple generic types, the order of the @typeparam directive must reflect the order of the generic types:
    @implements MyBaseClass<TItemModel, TFilterModel>
    @typeparam MyItemModel
    @typeparam MyFilterModel
  • @inject: The @inject directive injects service from the dependency injection container into the component:
    @inject HttpClient Client
    @inject IJSRuntime JS

The preceding code shows how to inject an HttpClient into the Client property and IJSRuntime into the JS property.

Writing inline directives

Inline directives are used in the HTML or Razor components. These directives include the following:

  • @attributes: These directives represent a collection (Dictionary<string, object>) of HTML attributes that we want to render in the child component that has not specified these HTML attributes as component parameter properties:
    @{
      Dictionary<string, object> AdditionalAttributes =
        new Dictionary<string, object()
    {
      { "tooltip", "Set your full name." },
      { "required", "true" },
      { "data-id", 150 }
    };
    }
    <input @attributes="AdditionalAttributes" />
  • @bind: This directive creates two-way data binding for a component with user input:
    @{ public string UserName { get; set; } }
    <input type="text" @bind-Value="UserName" /> 
  • @on{event}: This directive adds an event handler for the event specified (click, change, and so on):
    <button @onclick="OnClickHandler">Click here!</button> 
  • @key: This directive specifies the unique key used to render the collection of data in loops (for, foreach, while, and do while):
    @for (var i = 1; i <= 10; i++)
    {
      <p @key="i">This is row @i</p>
    }

Such keys are used when updating the rendered data to update a correct item of the loop, instead of updating the whole loop.

  • @ref: This directive captures a reference to the component or HTML element:
    <ConfirmBox @ref="myConfirmBox" />
    @code {
    …
      myConfirmBox.OnConfirm(…);
      …
    }

@ref can be used to trigger JavaScript events.

Now that we know how to write Razor syntax, let’s learn how to use Razor syntax and create fully functional Razor components.

 

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.

 

Understanding page routing in Blazor

Blazor WebAssembly is a SPA. This means that routing is not done on the server but the client. While clicking on the link updates the address bar of the browser, the page itself is not refreshed. Blazor finds a component with a matching route in the @page directive and renders this component (and all child components) as a page.

The Router component takes care of resolving the correct component to render. This component is typically used in the application root component – that is, App.razor. The component is created from two RenderFragments, as follows:

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData"
          DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData"
          Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this
              address.</p>
        </LayoutView>
    </NotFound>
</Router>

In the preceding code, you can see the Router component with the Found and NotFound sections. The Router component looks to the specified AppAssembly and discovers all routable components (components with the @page directive). When navigating from one page to another, the Router component renders the Found section, if there is any routable component with a matched route. Otherwise, the NotFound section is rendered.

Rendering of the component is handled in the RouteView component. The RouteData parameter of the component contains information about the matched component. RouteView uses a layout defined on the component’s @layout directive, or DefaultLayout if the component layout is not specified.

The FocusOnNavigate component is not required. This component sets focus on the element specified by the CSS selector in the Selector parameter. It is useful when building websites compatible with screen readers, or when you want to focus on some user input fields after changing the page.

Navigating between pages

To navigate between pages, the standard HTML anchor is used:

<a href="/contacts">Contacts</a>

As shown in the preceding code, there is no difference between page navigation in standard HTML and Blazor. That is one of Blazor’s advantages over other JavaScript SPA frameworks, which use the custom component to provide SPA navigation. Blazor intercepts any navigation on the same site and tries to find the corresponding components to render.

If you need to manipulate the address from the C# code, you must inject NavigationManager into your code, as follows:

@page "/offers"
@inject NavigationManager NavManager
…
<button @onclick="GoToContacts">Contacts</button>
@code {
  void GoToContacts()
  {
    NavManager.NavigateTo("contacts");
  }
}

The preceding code will change the page to /contacts after the button is clicked.

Page directive

The @page directive can be specified multiple times in the component. It can also contain dynamic parts of the URL:

PageDirective.razor

@page "/author"
@page "/author/{Name}"
<h1>Author @authorName </h1>
@code {
  private string authorName;
[Parameter]
  public string Name { get; set; }
  
  protected override void OnInitialized()
  {
    authorName = Name ?? "Not set";
  }
}

In the preceding code, the first @page directive specifies the URL without any parameters. The second @page directive specifies a parameter with the name. The following URLs are valid for the PageDirective component:

  • /author
  • /author/John
  • /author/123

In the third example, /author/123, you can see that we can pass numbers to the text parameter. It is fine here because any value can be treated like a string. But what if we expect a different type?

Route constraints

Route constraints are used when you need to enforce a specific data type of the route parameter. The route constraints are defined the same way as in C# API endpoints – that is, by adding a semicolon after the parameter name and then specifying the data type:

@page "/author/{Id:int}"

The preceding code will define the URL for the author with Id as an integer.

Not all constraints are supported at the time of writing. The following table shows the supported types:

Constraint type

Example of constraint

Example of valid values

bool

@page "/user/{enabled:bool}

True, true, FALSE

datetime

@page "/user/{createdAt:datetime}

2022-04-23, 2022-01-01 6:18am

decimal

@page "/basket/{price:decimal}

59.99, -999.99

double

@page "/package/{weight:double}

1.15, -39.12

float

@page "/package/{weight:float}

1.234, -1.234

guid

@page "/user/{id:guid}

905E47D7-DA48-4301-8137-B25541438240, {FEB6A90F-3D25-46D1-AD62-64BBD499A0D6}

int

@page "/user/{id:int}

123456, -123456

long

@page "/timer/{ticks:long}

123456789, -123456789

Table 2.2 – Blazor router supported constraints

Component route parameters can also be marked as optional:

@page "/user/{id:int}/{valid:bool?}"

The preceding code specifies the URL to /user with the required Id parameter and optional Valid bool parameter. The optional ? symbol can also be used without type constraints.

Catch-all parameters

Sometimes, you may need to create components for multiple routes. In that case, a catch-all parameter can be used:

Posts/AllPosts.razor

@page "/posts/{*pageRoute}"
@code {
  [Parameter]
  public string? PageRoute { get; set; }
}

The preceding code shows a catch-all parameter named PageRoute. The catch-all route parameter must be of the string type and placed at the end of the route; it is not case-sensitive.

The PageRoute parameter will contain all the matched values from the URL:

  • For the /posts/animals/africa/forest URL, the PageRoute value is animals/africa/forest
  • For the /posts/animals URL, the PageRoute value is animals
  • For the /posts/animals/europa%2Fwater URL, the PageRoute value is animals/europa/water

With that, we know how to create components and how to navigate between them. In the next section, we will look at the project we will be creating in this book. And yes! We will use everything we’ve learned in this chapter up until this point.

 

Project overview and preparation

In this book, we will be building a complex Blazor WebAssembly application for managing a movie collection. Our application will be able to manage movies and their directors, actors, categories, and more.

We do not want to create an application with lots of functionality at the beginning. Instead, we want to create an application that can be easily extended with other functionality, such as trailers, reviews, and so on.

In Chapter 3, Creating a Database Using Entity Framework Core, and Chapter 4, Connecting Client and Server with REST API, we will introduce generic components to communicate between the Blazor application client and server parts. In Chapter 5, Building gRPC Services, we will learn how to replace the integrated REST API with gRPC communication to improve application performance. In the last part of our project, in Chapter 6, Diving Deep into Source Generators, we will be implementing source generators to generate repetitive parts of the code. Let’s begin with a simple application.

Creating a demo Blazor WebAssembly project

Our demo project is based on the default Blazor WebAssembly App template in Visual Studio 2022. This template can be a little bit different, depending on the exact minor version of Visual Studio. After we have used the template to create the project, we will examine its different parts and run the default project to see if everything works as expected. Then, we will modify the existing components and create our components to present list views and details of the MediaLibrary data.

Creating the demo MediaLibrary project

Visual Studio has many predefined templates for different kinds of projects. We are going to use the Blazor WebAssembly App template for this. This template can be configured differently, so we need to set everything properly. Follow these steps:

  1. Open Visual Studio 2022.
  2. Click the Create a new project button in the right bottom part of the window.
  3. Use the All languages filter to select C#. Then, in the All project types filter, select Blazor to find Blazor WebAssembly App.

The following screenshot shows the Blazor WebAssembly App project template in the Visual Studio 2022 template list:

Figure 2.3 – Blazor WebAssembly App project template

Figure 2.3 – Blazor WebAssembly App project template

  1. Select the Blazor WebAssembly App template and click the Next button.
  2. On the Configure your new project screen, set the Project name to MediaLibrary, specify a Location for the project, and click Next. The Solution name area will be automatically filled with the same project name.

The following screenshot shows the Configure your new project window:

Figure 2.4 – The Configure your new project window

Figure 2.4 – The Configure your new project window

In the preceding screenshot, you can see that we set D:\BlazorProjects as our project’s Location. The location is not important, so you can use whatever location suits you. Click Next.

  1. In the Additional information window, ensure that Framework is set to .NET 6.0 (Long-term support) and that Authentication type is set to None. Then, check the checkboxes for Configure for HTTPS and ASP.NET Core hosted.

The required state is shown in the following screenshot:

Figure 2.5 – Additional information for setting up the Blazor WebAssembly App template

Figure 2.5 – Additional information for setting up the Blazor WebAssembly App template

  1. Click the Create button. The project will be created and opened.

Running the project

Now, you can run the project to see what the project does. The Demo project from the template contains three pages, where each one shows a different functionality. On the Home page, there are static texts. The Counter page has a Click me button and shows how many times the button was clicked. On the Fetch data page, there is a component showing the weather forecast that was downloaded from the REST API call to the server.

You can run the project using the Debug menu and choosing Start Without Debugging or by using the Ctrl + F5 shortcut. Visual Studio may ask you to install an SSL Certificate to run on HTTPS. Depending on the browser, there may be a warning message about accessing an untrusted website. It is safe to access the site so long as you are on the local host domain.

Examining the project

The advantage of using the Visual Studio Blazor WebAssembly App template is that we don’t start with an empty project and many requirements to run this type of project are preconfigured.

The following figure shows the generated project’s structure:

Figure 2.6 – Demo project structure

Figure 2.6 – Demo project structure

In the preceding figure, you can see that three projects have been generated in our MediaLibrary solution.

Client project

The client project contains our WebAssembly application. It is constructed from a single .cs file and multiple Razor components and static files, including images, CSS, JavaScript, and so on.

The wwwroot folder

The wwwroot folder is a container for the client application. In our project, you can see that this folder holds custom CSS files, icons, favicon, and an index.html file. This folder can be used for public static resources, such as images and other files, which are not built but are published with the application.

The index.html file is the mounting point of the application. When the browser requests your application, the index.html file is downloaded. This file contains references to additional CSS files, the title of the website, and other meta information. Additional content can be injected into the <head> section of the file from the Razor components.

The <body> section of the file contains two div elements and one script:

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

The script element has a link to a file provided by the Blazor framework. This JavaScript file contains all the logic needed to run the WebAssembly application in the browser. The file will download the .NET runtime and your application’s assemblies with all their dependencies.

Another highlighted part of the code is the div element, which has an id attribute with a value of app. This is where all the components will be rendered.

A blazor-error-ui element is where unhandled exceptions are shown in case there are any problems on the website.

The Pages folder

The Pages folder contains all Razor components that have the @page directive. These components are discovered as routable components.

Note

The routable components can be discovered even outside the Pages folder but comprise a common way to separate components in the project. The correct component organization will help you navigate through the files in the project.

The Shared folder

The Shared folder contains all the Razor components, which are shared between pages. This folder is a place for all the components that are not routable, regardless of whether you use the component on one page or five. The Shared folder can contain additional folder structure to help organize the components.

This folder is also used to define the layout component. The MainLayout.razor component is defined in our Demo project. The layout component inherits from the LayoutComponentBase class, rather than the ComponentBase class:

@inherits LayoutComponentBase
<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>
    <main>
        <div class="top-row px-4">
            <a href=https://docs.microsoft.com/aspnet/
              target="_blank">About</a>
        </div>
        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

The layout components contain the @Body directive, which is not used in other components. In the runtime, the @Body directive is replaced with the generated page content.

The _Imports.razor file

This file contains the @using directives for namespaces. The namespaces included in this file are automatically included in all Razor components files. The default content of this file has namespaces for HttpClient, basic Razor components provided by the ASP.NET Core team, the JSInterop library, and the namespace of our Shared folder.

The App.razor file

The App.razor component is the root component of the Blazor WebAssembly application. This component uses the Router component to determine what routable page should be rendered. The default layout component is also defined here. The content of the App.razor component was shown in the Understanding page routing in Blazor section.

The Program.cs file

This file is the entry point for the Blazor WebAssembly application and contains the configuration for the application:

using MediaLibrary.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient {
  BaseAddress = new
   Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();

Here, you can see the code for creating a new builder for our application. After that, two components are attached. First, the App component is attached to the element with the app ID. After that, the HeadOutlet component is attached inside <head> as a last inner element.

The HeadOutlet component is provided by the Blazor framework and is used to change the title of the page or add additional meta information from the rendered components.

Server project

The server project contains part of the application that is executed on the server. It is a generated API project with support for Razor Pages and has a fallback to the index.html file, which is the mounting point for our client WebAssembly application.

This project is no different from other types of WebApi projects. We will use this part to create our REST API, gRPC services, and more.

Shared project

The shared project is referenced by both the client and server projects in our MediaLibrary solution. This project is used for the code that needs to be shared between these two projects. It is mostly used to specify Data Transfer Objects (DTOs), models, custom types, and enums.

Preparing the demo project

Now, let’s prepare the demo project for our needs. The first thing we have to do is delete all the unnecessary files. So, we need to delete the following files:

  • MediaLibrary.Client:
    • Pages/Counter.razor
    • Pages/FetchData.razor
    • Shared/SurveyPrompt.razor
  • MediaLibrary.Server:
    • Controllers/WeatherForecastController.cs
  • MediaLibrary.Shared:
    • WeatherForecast.cs

Once we’ve deleted the files, we should verify that the project is still correct. We can do this by building the project using the Build menu option and then choosing Build Solution (Ctrl + Shift + B). The project should be built without any errors. The navigation to the Counter and FetchData components defined in Shared/NavMenu.razor component will not work because we’ve deleted the respective pages. We will fix this later when we add new pages. Now, we have a clean Blazor WebAssembly project for the next chapter.

In this section, we introduced the default Blazor WebAssembly App template and explained how to create a demo project using Visual Studio 2022 and this template. Then, we created the demo project and explained the purpose of all the important files. Now, let’s summarize this chapter.

 

Summary

After reading this chapter, you should know about the Razor syntax and how the C# code can be combined with HTML to create dynamic content.

We also covered how to create Razor components, from simple ones containing plain HTML to more advanced components with content defined in parameters by the parent component, as well as the components that can render multiple RenderFragments.

By now, you should know how to use routing in Blazor to specify routable components, navigate between components when the user clicks on the anchor element, and use code behind the NavigationManager class. With all this knowledge, you should be able to create any type of Blazor WebAssembly application for websites with dynamic content and JavaScript-like events, but without using any JavaScript libraries or scripts. If you know about the older Microsoft WebForms technology, you should be able to migrate such applications to the latest framework using Blazor components.

In the next chapter, we will continue with our demo project. We will create custom routable components that will show a different type of data, create some resource classes that will provide the data for us, and learn how to connect the client with the server to pass the data to the website.

About the Author
  • Václav Pekárek

    Václav Pekárek is an experienced developer with 10 years of web development experience using .NET and C#. He is also a Microsoft Certified Professional.

    Václav is the owner of a small IT company in Ostrava, Czech Republic.

    Browse publications by this author
Building Blazor WebAssembly Applications with gRPC
Unlock this book and the full library FREE for 7 days
Start now