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
All the code for this chapter can be found at https://github.com/PacktPublishing/gRPC-Powered-Blazor-WebAssembly-Development/tree/main/ch2.
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 toLoginLayout
:@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
, anddo 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 |
|
|
|
|
|
|
|
This argument class is supported by most of the events and holds basic event data. General
Clipboard
Input
Media
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
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
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, thePageRoute
value isanimals/africa/forest
- For the
/posts/animals
URL, thePageRoute
value isanimals
- For the
/posts/animals/europa%2Fwater
URL, thePageRoute
value isanimals/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:
- Open Visual Studio 2022.
- Click the Create a new project button in the right bottom part of the window.
- 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
- Select the Blazor WebAssembly App template and click the Next button.
- 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
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.
- 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
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
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.