Blazor Essentials: Your Step-By-Step Guide
Getting Started with Blazor
Blazor is becoming one of the most popular and widely used frameworks for modern-day web app development. In essence, it represents a .NET web framework that allows you to build client-side Single Page Application (SPA) using C#, Razor syntax, and HTML. There are plenty of reasons to start using it for your next project as it delivers tons of benefits like component-based architecture, sharable UI elements across pages, JavaScript interoperability, and many more.
To better understand how Blazor and Ignite UI for Blazor work, we created this Quick Blazor Essential Guide with examples, code snippets, and use cases.
Blazor Essentials
What is Blazor?
Blazor is a .NET web framework that allows us to create client-side applications using C#, Razor syntax, and HTML. It allows us to create a rich and modern Single Page Application (SPA), using C#, and run it on the browser of our choice.
The name "Blazor" comes from the ability of the framework to run C# and Razor in the browser. Combining "browser" and "Razor" gives us the name "Blazor."
Why should we use Blazor?
- Blazor allows us to create a rich and interactive UI using C# - a modern and feature-rich language.
- We can reuse the app logic by sharing it with both the client and the server. It allows us to have a full-stack .NET development experience.
- We can use existing .NET APIs and tools to create rich web applications.
- Blazor is supported on both Visual Studio and Visual Studio Code. This provides a great .NET development experience across multiple platforms, including Linux, Windows, and Mac.
- Supported by all the modern browsers, including mobile browsers.
- It is an open-source framework with great community support.
- Easy to host the app using IIS, Azure app service, and Docker.
Features of Blazor
- Component-based architecture: Blazor provides us with a component-based architecture to create rich and composable UI
- Layouts: We can share common UI elements (for example, menus) across pages using the layouts feature.
- JavaScript interop: This allows us to invoke a C# method from JavaScript, and we can call a JavaScript function or API from C# code.
- Routing: We can redirect the client request from one component to another with the help of routing.
- Forms and validation: We can create interactive forms to handle user inputs and apply validation techniques to handle any errors in the form.
- State management: We can persist the app state for the user in the browser's memory. However, if the user re-opens their browser or reloads the page, the user state held in the browser's memory is lost.
- Globalization and localization: A Blazor app can be made accessible to users in multiple cultures and languages. This allows us to extend the reach of our application to a worldwide audience.
- Progressive Web Applications: We can create a Blazor app as a Progressive Web Application. This will allow the Blazor app to work offline and load instantly irrespective of the user’s network speed.
- Lazy Loading: Lazy loading allows us to delay the loading of some application assemblies until they are required. This will enhance the start-up performance of the application.
- Debugging: We can debug the Blazor WebAssembly apps in Chromium-based browsers such as Microsoft Edge and Google Chrome. And also, debugging support in Firefox is becoming available (currently, it is in the preview stage). We can also debug the app in Visual Studio and Visual Studio Code IDE.
Blazor Hosting Model
The component model of Blazor is responsible for calculating UI changes. However, we can use different renderers to control how the UI is displayed and updated. These renderers are known as hosting models.
Blazor supports two hosting models
- Blazor WebAssembly
- Blazor Server
Blazor Server Execution Model
What is Blazor Server?
The Blazor server hosting model allows the Blazor application to run on the server on top of the full .NET runtime.
How does Blazor Server work?
The Blazor server execution model is shown in the image below:
When the user loads the application, a small JavaScript file (blazor.server.js) is downloaded on the browser which establishes a real-time, two-way SignalR connection with the server.
Any user interaction with the app is then transmitted back to the server over the SignalR connection using the WebSocket protocol by default. The server will process the client's request. After the server is done, any UI updates are transmitted back to the client and applied to the DOM.
The server maintains a state for each connected client. This state is known as a circuit.
A circuit is created when we launch the app in the browser. Each instance of the app in the browser creates a new circuit on the server. This means that if you open the app in two different tabs of the same browser, then two circuits will be created on the server.
When the app is closed, either by closing the browser or navigating to an external URL, the circuit and associated resources are released immediately.
Advantages of using Blazor Server
The Blazor server provides us with the following advantages.
- The download size of the app is significantly smaller as compared to the Blazor WebAssembly app. This helps in loading the app faster.
- A Blazor server app takes full advantage of server capabilities, such as .NET compatible APIs.
- Since the app runs on the server, we can take full advantage of the existing .NET tooling such as debugging.
- The code base of the app is not shared with the clients.
When to use Blazor Server?
The Blazor server app is preferred in the following scenarios:
- When you want the app to load swiftly.
- When you want the app to access the server and network resources.
- Since every user interaction involves a network hop, there is a high latency observed in the Blazor server apps. Therefore, go with the Blazor server where high latency is not an issue.
- Since all the heavy lifting is done by the server, Blazor server is preferred when the client resources are limited.
- Blazor server is suitable for browsers which do not support WebAssembly.
Blazor WebAssembly
What is Blazor WebAssembly?
The Blazor WebAssembly(WASM) hosting model allows the Blazor app to run client-side in the browser on a WebAssembly-based .NET runtime. It is the principal hosting model for Blazor.
How does Blazor WebAssembly work?
The Blazor WASM execution model is shown in the image below:
When the user loads the application, a small JavaScript file (blazor.webassembly.js) is downloaded on the browser.
This file handles the following two operations:
- It downloads the .NET runtime alongside the Blazor app and its dependencies to the browser.
- It initializes the .NET runtime to execute the app.
The app execution happens directly on the browser UI thread. The UI updates and event handling also occur within the same process.
A web server or any other service that can serve static content to the clients can be used to deploy the app assets as static files.
Types of Blazor WebAssembly app
There are two types of Blazor WASM app:
- Standalone: When the Blazor WebAssembly app is created for deployment without a backend ASP.NET Core app to serve its files, the app is called a standalone Blazor WASM app.
- Hosted: When the app is created for deployment with a backend app to serve its files, the app is called a hosted Blazor WASM app. A hosted app provides a full-stack web development experience with .NET. It allows us to share code between the client and the server apps, and support prerendering and integration with MVC and Razor Pages.
Advantages of using Blazor WebAssembly
The Blazor WebAssembly provides us with the following advantages:
- There is no server-side dependency once the app is downloaded to the client. This ensures that the app remains functional even if the server goes offline.
- The client will do the heavy lifting. Hence there is less load on the server.
- The app takes full advantage of the client's resources.
- Since a server is not required to host the app, serverless deployment scenarios are supported.
When to use Blazor WebAssembly?
The Blazor WASM app is preferred in the following scenarios:
- When we want the app to leverage the resources of the client.
- When the app is required to run offline if the client is not able to connect to the internet.
- When we want to host the app as a static site.
Blazor Hybrid
What is Blazor Hybrid?
Blazor Hybrid allows us to create native client apps using .NET, HTML and CSS. We can use the existing .NET native app frameworks such as .NET MAUI, WPF, and Windows Forms to create a Blazor hybrid app.
How does Blazor Hybrid work?
A Blazor hybrid app runs the razor components natively on the device. The Blazor components are rendered to an embedded Web View control via a local interop channel. The components run directly on the native app and not on the browser. Hence, WebAssembly is not involved in a hybrid app.
A hybrid app can access the capabilities of the native platform via .NET APIs. Being a native app, a Blazor hybrid app can support functionalities that are not available with only the web platform. We can also share and reuse the existing razor components from a Blazor server or a Blazor WASM application with a Blazor hybrid app.
Advantages of using Blazor Hybrid
- It allows us to reuse existing components from the Blazor server & Blazor WASM application. This enables code sharing and reusability across mobile, desktop, and web platforms.
- The app can leverage the native capabilities of the device.
When to use Blazor Hybrid?
The Blazor Hybrid app is preferred in the following scenarios:
- When we want to create a native app using .NET APIs.
- When we want to take advantage of the native client capabilities.
- When the app is required to run offline.
- When we want to offload the data processing of the app to the client
Blazor Components
What are Blazor components?
A Blazor component is defined as a part of the UI such as the nav bar, buttons, form etc. Blazor components are created as Razor component files with the extension “.razor”.
The components are reusable and they can be shared across multiple projects. A Blazor component is also known as a Razor component.
Razor is a markup syntax for combining HTML and C# code.
The name of a Blazor component must start with an uppercase character.
e.g. – The name LoginForm.razor is a valid component name. Whereas the name loginForm.razor is an invalid component name.
Look at the example component code shown below:
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status" >Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
The @page directive is used to specify the route for the component. The @code directive is used to specify the C# code for the component. The method IncrementCount will be invoked on the button click event.
We can also use multiple, but distinct @page directives for a single component. We cannot set the same route for two different components. Doing so will result in a runtime error.
Component base class
We can create a base class for the component to separate the HTML and C# logic. The base class must derive from the ComponentBase class. We can use the @inherits directive to inherit a base class in a component.
We have created a base class Welcome.razor.cs as shown below:
using Microsoft.AspNetCore.Components;
namespace BlazorTutorial.Client.Pages;
public class WelcomeBase : ComponentBase { public string WelcomeMessage { get; set; } = "Welcome to Blazor tutorial."; }
We have created a razor component Welcome.razor as shown below:
@page "/welcome"
@inherits WelcomeBase
<h1>@WelcomeMessage</h1>
We are using the @inherits directive to inherit the base class.
Component parameters
Component parameters are used to pass data from the parent to the child component.
Let us understand this with the help of an example.
Create a component Child.razor as shown below:
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
@code {
[Parameter]
public int num1 { get; set; }
[Parameter]
public int num2 { get; set; }
}
The component parameters are annotated with the [Parameter] attribute. This component will accept two parameters of type integer and will display the sum of both numbers.
Create another component Parent.razor as shown below:
@page "/parent"
<h3>Parent Component</h3>
<Child num1="5" num2="10"><Child>
We have invoked the Child component inside the Parent component and passed both the parameters to it.
We can also assign the C# property to the parameter value using the “@” symbol.
Look at the example shown below:
@page "/parent"
<h3>Parent Component</h3>
<Child num1="@number1" num2="@number2"><Child>
@code {
int number1 = 5;
int number2 = 10;
}
A parameter can be marked required using the [EditorRequired] attribute.
Look at the example shown below:
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)<p>
@code {
[Parameter]
[EditorRequired]
public int num1 { get; set; }
[Parameter]
[EditorRequired]
public int num2 { get; set; }
}
We must use the [Parameter] attribute while using the [EditorRequired] attribute.
If we do not supply the required parameters while invoking the component, it will throw a compile-time warning.
We can also provide default values to the component parameters.
Look at the example shown below:
@page "/welcome"
<h1>WelcomeMessage</h1>
@code {
[Parameter]
public string WelcomeMessage { get; set; } = "Welcome";
}
Razor component lifecycle
Razor components go through a series of lifecycle events from their creation till it is removed. It has both synchronous as well as asynchronous methods. The Blazor framework allows us to override lifecycle methods to perform additional operations on components.
SetParametersAsync
This method will be called before the parameters are set. It will set the parameters supplied by the parent component or from the component route. We must call the base.SetParametersAsync() method to set the value for the component parameters. Otherwise we need to write our custom code to manipulate the parameters.
OnInitialized
This method will be called before the parameters are set. It will be invoked when the component is ready to start after receiving the initial parameters from its parent.
The asynchronous version is called OnInitializedAsync. Override OnInitializedAsync if you want to perform an asynchronous operation and want the component to refresh when that operation is completed.
OnParametersSet
This method will be called after the parameters are set. It will be invoked when the component has received parameters from its parent, and the incoming values have been assigned to properties. This method will be executed every time the parameters are updated. The asynchronous version is called OnParametersSetAsync.
OnAfterRender
This method will be called after a component has finished rendering i.e., the HTML is already displayed. This method can be used to perform any extra steps required for component initialization, such as activating any third-party JavaScript libraries that use the rendered DOM elements. This method is executed every time the component is rendered. The asynchronous version is known as OnAfterRenderAsync.
StateHasChanged
This method will notify the component that its state has changed and then re-render the component. During an EventCallback, this method will be called automatically to re-render the Parent component.
Invoking this method will allow us to render the component anytime. However, too many calls to StateHasChanged can add unnecessary rendering costs to the application.
Let us understand this with the help of an example.
We have created a base class Lifecycle.razor.cs as shown below:
using Microsoft.AspNetCore.Components;
namespace BlazorTutorial.Client.Pages;
public class LifecycleBase : ComponentBase { public override async Task SetParametersAsync(ParameterView parameters) { Console.WriteLine("SetParametersAsync-start"); await base.SetParametersAsync(parameters); Console.WriteLine("SetParametersAsync-end"); }
protected override void OnInitialized() { Console.WriteLine("OnInitialized-start"); base.OnInitialized(); Console.WriteLine("OnInitialized-end"); }
protected override async Task OnInitializedAsync() { Console.WriteLine("OnInitializedAsync-start"); await base.OnInitializedAsync(); Console.WriteLine("OnInitializedAsync-end"); }
protected override void OnParametersSet() { Console.WriteLine("OnParametersSet-start"); base.OnParametersSet(); Console.WriteLine("OnParametersSet-end"); }
protected override async Task OnParametersSetAsync() {Console.WriteLine("OnParametersSetAsync-start"); await base.OnParametersSetAsync(); Console.WriteLine("OnParametersSetAsync-end"); }
protected override void OnAfterRender(bool firstRender) { Console.WriteLine("OnAfterRender({0})-start", firstRender); base.OnAfterRender(firstRender); Console.WriteLine("OnAfterRender({0})-end", firstRender); }
protected override async Task OnAfterRenderAsync(bool firstRender) { Console.WriteLine("OnAfterRenderAsync({0})-start", firstRender); await base.OnAfterRenderAsync(firstRender); Console.WriteLine("OnAfterRenderAsync({0})-end", firstRender); } }
We have created a razor component Lifecycle.razor as shown below:
@page "/lifecycle"
@inherits WelcomeBase
<h1>Blazor component lifecycle example</h1>
When we execute the application and navigate to the lifecycle component, we can see the output in the browser console as shown below:
This output shows the execution sequence of the lifecycle methods of a Razor component.
Blazor Cascading Values and Parameters
Cascading values and parameters allow us to pass data from a component to all its descendent components. A component can provide a cascading value using the <CascadingValue> component.
To utilize the cascading values supplied by the parent component, a child component can declare cascading parameters using the [CascadingParameter] attribute.
The cascading values are bound to cascading parameters by data type. If we want to cascade multiple values of the same type, then we can provide a unique name to each [CascadingParameter] attribute.
Let us understand this with the help of an example.
Create a component Child.razor as shown below:
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
@code {
[CascadingParameter(Name = "FirstNumber")]
public int num1 { get; set; }
[CascadingParameter(Name = "SecondNumber")]
public int num2 { get; set; }
}
Create a component Parent.razor as shown below:
@page "/parent"
<h3>Parent Component</h3>
<CascadingValue Value="@number1" Name="FirstNumber" >
<CascadingValue Value="@number2" Name="SecondNumber" >
<Child></Child>
</CascadingValue>
</CascadingValue>
@code {
int number1 = 5;
int number2 = 10;
}
We have called the Child component and passed the cascading values. The child component will bind the parameter values using the Name property.
Pass data across a component hierarchy
We can use the cascading parameters to pass data across the component hierarchy.
Let us understand this with the help of an example.
Create a component GrandChild.razor as shown below:
<h3>Grand Child Component</h3>
<p>The product of number is : @(num1 * num2)</p>
@code {
[CascadingParameter(Name = "FirstNumber")]
public int num1 { get; set; }
[CascadingParameter(Name = "SecondNumber")]
public int num2 { get; set; }
}
Create a component Child.razor as shown below:
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
<GrandChild></GrandChild>
@code {
[Parameter]
public int num1 { get; set; }
[Parameter]
public int num2 { get; set; }
}
@page "/parent"
<h3>Parent Component</h3>
<CascadingValue Value="10" Name="FirstNumber" >
<CascadingValue Value="5" Name="SecondNumber" >
<Child></Child>
</CascadingValue>
</CascadingValue>
@code {
int number1 = 5;
int number2 = 10;
}
We have passed the cascading values from the Parent component to the GrandChild component. Note that the name of the cascading parameters is the same in both the Child & the GrandChild components.
Upon execution, you can see an output as shown below:
Blazor Data Binding
One-way data-binding
It allows us to bind the value of a property to HTML DOM elements, but not vice versa. To bind a property or a field to an HTML tag, we need to pass the property name, prefixed with the @ symbol.
Look at the example shown below:
@page "/databinding"
<h3>One way data binding</h3>
<p>@SampleText</p>
@code {
string SampleText = "This is a sample text depicting one-way data-binding";
}
The C# field SampleText is bound to the HTML DOM using the @ symbol.
Two-way data-binding
It allows us to bind the value of a property or a field to HTML DOM elements, and vice versa. We can achieve two-way data binding by using the @bind attribute.
We can bind a C# property on DOM events using the @bind:event="{EVENT}" attribute on the HTML elements, where {EVENT} is a placeholder for the DOM events.
Look at the example shown below:
@page "/databinding"
<h3>Two way data binding</h3>
<div>
<span>Enter your Name: </span>
<input type="text" @bind="Name" @bind:event="oninput" />
</div>
<br/>
<p>You name is: @Name</p>
@code {
string Name { get; set; }
}
The value of the input field is bound to the Name property. The data binding will happen when the oninput event of the input field is triggered, i.e. when the value of the text box changes.
NOTE:
The attribute binding is case-sensitive. This means @bind, @bind:event is valid whereas, @Bind, @Bind:EVENT and other syntax using upper case characters are invalid.
Bind multiple options using <select> element
We can bind the values from a multiple select to a C# property of array type.
Look at the example shown below:
@page "/databinding"
<p>
<label>
Select one or more days:
<select @onchange="SelectedDaysChanged" multiple >
<option value="monday">Monday</option>
<option value="tuesday">Tuesday</option>
<option value="wednesday">Wednesday</option>
<option value="thursday">Thursday</option>
<option value="friday">Friday</option>
</select>
</label>
</p>
<p>
Selected Days: @string.Join(", ", SelectedDays)
</p>
@code {
public string[] SelectedDays { get; set; } = new string[] { };
void SelectedDaysChanged(ChangeEventArgs e)
{
if (e.Value is not null) {
SelectedDays = (string[])e.Value;
}
}
}
The selected values are bound to an array of strings using the @onchange event of the <select> element.
Bind to a formatted string
Data binding works with a formatted string using the syntax @bind:format="{FORMAT STRING}"
The formatted data-binding is supported for the following .NET types:
- System.DateTime
- System.DateTimeOffset
Look at the example shown below:
@page "/databinding"
<h3>Formatted string</h3>
<div>
<span>The Sample date is: </span>
<input @bind="SampleDate" @bind:format="dd-MM-yyyy" />
</div>
@code {
DateTime SampleDate { get; set; } = new DateTime(2023, 1, 14);
}
The date will be displayed in the format specified using the @bind:format attribute.
We can specify a custom binding format using the get and set accessors.
Look at the example shown below:
@page "/databinding"
<input @bind="SalaryValue" />
<h3>Formatted string</h3>
<p>
<code>decimalValue</code>: @salary
</p>
@code {
decimal salary = 123456;
string SalaryValue {
get => salary.ToString("0.000");
set {
if (Decimal.TryParse(value, out var number)) {
salary = Math.Round(number, 3);
}
}
}
}
The string property SalaryValue is bound up to three decimal places with the input element.
Binding with component parameters
We can bind the property of a child component to a property in its parent component. Since the data binding happens at multiple levels, this scenario is known as a chained bind.
We can use the syntax @bind-{PROPERTY}, where {PROPERTY} is a placeholder for the property to bind. We must provide an event handler and value to support updating the property in the parent from the child component.
Let us understand this with the help of an example.
Create a component Child.razor as shown below:
<h3>Child Component</h3>
<p>Child Message: @Message</p>
<button @onclick="UpdateMessageFromChild">Update Message from Child</button>
@code {
[Parameter]
public string Message { get; set; }
[Parameter]
public EventCallback <string> MessageChanged { get; set; }
private async Task UpdateMessageFromChild() {
await MessageChanged.InvokeAsync("Message from child component");
}
}
We have declared a component parameter of type string and an EventCallback of the same type as the component parameter.
The name of the EventCallback must follow the syntax {PARAMETER NAME}Changed, where the {PARAMETER NAME} is a placeholder for the component parameter name. Using any other naming format will result in a runtime error.
In the example shown above, the component parameter is named Message and the EventCallback is named as MessageChanged.
Create a component Parent.razor as shown below:
@page "/parent"
<h3>Parent Component</h3>
<Child @bind-Message="MesssageFromParent"></Child>
<button @onclick="UpdateMessageFromChild">Update Message from Child</button>
@code {
string MesssageFromParent = "Message from parent component";
}
The C# property MesssageFromParent is bound to the Message parameter of the Child component. The Message parameter of the Child component is bindable because it has a companion MessageChanged event of the same type as that of the Message parameter.
Upon execution you can see an output as shown below:
As soon as you click on the button, the message will be updated as shown in the image below:
Blazor Event Handling
Add an HTML attribute with the name @on{EVENT} and have a delegate type value. The value for this attribute is treated as an event handler by the Blazor component.
Some of the event handlers supported by Blazor are @onclick, @onchange, @onselect, @onfocus, @onkeyup etc.
Example:
<button @onclick="ButtonClicked">Click me</button>
@code {
void ButtonClicked() {
Console.WriteLine("button clicked");
}
}
The ButtonClicked method will be invoked when we click on the button.
Async event handling
Look at the example shown below:
<button @onclick="ButtonClicked">Click me</button>
@code {
async Task ButtonClicked() {
await Task.Delay(1000);
Console.WriteLine("button clicked");
}
}
The ButtonClicked method will be invoked asynchronously when the button is clicked.
Using event arguments
Look at the example shown below:
<select class="form-control col-md-4" @onchange="SelectGender" />
<option value="">-- Select Gender --</option>
<option value="Male">Male</option>
<option value="Famale">Famale</option>
</select>
@code {
protected string Gender { get; set; }
protected void SelectGender(ChangeEventArgs e) {
Gender = e.Value.ToString();
}
}
We have bound the SelectGender method to the onchange event of the select element.
Specifying an event argument in the event method definition is optional. It is mandatory only when the event argument is used in the method.
Using Lambda expressions for event handling
We can use a lambda expression to create an anonymous function for the event attribute.
Look at the example shown below:
@for (int i = 1; i < 4; i++) {
int textboxNumber = i;
<p>
<input> @onfocus="@(() => FocusTextbox(textboxNumber))" />
</p>
}
@code {
protected string Gender { get; set; }
protected void FocusTextbox(int textboxNumber) {
Console.WriteLine($"You have selected textbox number {textboxNumber}");
}
}
We have created three textbox controls using a for loop. We have used a lambda expression to invoke the FocusTextbox method on the onfocus event of each text box.
EventCallback
EventCallback can be used to expose events across multiple components. This helps to invoke a parent component's method when an event occurs in a child component.
We can strongly type an EventCallback by specifying an event parameter using the syntax EventCallback<TValue>.
E.g.- EventCallback<MouseEventArgs>
Let us understand EventCallback with the help of an example.
Create a component Child.razor as shown below:
<h3>Child Component</h3>
<button class="btn btn-primary" @onclick="ChildClick">
Invoke parent component method
</button>
@code {
[Parameter]
public EventCallback ChildClick { get; set; }
}
Create another component Parent.razor as shown below:
<h3>Parent Component</h3>
<Child ChildClick="ChildClickHandler"> </Child>
<p><strong> @message </strong></p>
@code {
string message { get; set; }
void ChildClickHandler() {
message = "Child event has occurred";
}
}
We have created an EventCallback parameter in the child component. The onclick event handler of the button is set up to receive an EventCallback delegate from the ParentComponent.
The ParentComponent sets the child component’s EventCallback, ChildClick to its ChildClickHandler method.
When we click on the button in the Child component, it will invoke the ChildClickHandler method of the Parent Component. The string property message is updated and displayed in the ParentComponent.
Prevent default actions
We can use the @on{DOM EVENT}:preventDefault directive attribute to prevent the default action for an event. This attribute accepts a Boolean value as an argument. If we do not specify any argument, then the default value of true will be considered.
Look at the example shown below:
<input value="@name" @onkeydown="KeyDownHandler" @onkeydown:preventDefault />
@code {
private string name { get; set; }
private void KeyDownHandler(KeyboardEventArgs e) {
// do something
}
}
The Boolean attribute can be bind to a property and hence we can control the event propagation based on the requirement of the user.
Look at the example shown below:
<input @onkeydown="KeyDownHandler" @onkeydown:preventDefault="shouldPreventDefault" />
@code {
private bool shouldPreventDefault = true;
}
Stop event propagation
Sometimes the HTML elements propagate events to their parent elements.
Blazor allows us to use the @on{DOM EVENT}:stopPropagation directive attribute to stop event propagation. This attribute accepts a Boolean value as an argument. If we do not specify any argument, then the default value of true will be considered.
Look at the example shown below:
<button @onclick:stopPropagation>Click</button>
The Boolean attribute can be bind to a property and hence we can control the event propagation based on the requirement of the user.
<button @onclick:stopPropagation="shouldStopPropagation">Click</button>
@code {
private bool shouldStopPropagation = true;
}
Routing & Navigation
The Router component, used in the App.razor file, enables the routing to the components in a Blazor application.
When we create a Blazor application, the App.razor file will contain the code as shown below.
<Router AppAssembly="@typeof(App).Assembly >
<Found Context="routeData" >
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" / >
<FocusOnNavigate RouteData="@routeData" Selector="h1" / >
</Found>
<NotFound>
<PageTitle>NotFound</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert"> Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
The Router component is used to supply route data corresponding to the current navigation state. The FocusOnNavigate component is used to set focus to an element matching a CSS selector when the user navigates from one component to another. This allows us to create an accessible routing mechanism which is compatible with screen readers.
The NotFound property is used to display custom content when no data is found on the requested route.
We can use the @page directive to define the route for a Razor component. We can also use multiple but distinct @page directives for a single component.
Look at the example shown below:
@page "/route1"
@page "/home/route1"
<h1>This component is accessible via multiple routes.</h1>
We cannot set the same route for two different components. Doing so will result in a runtime error.
Route parameters
We can define route parameters which can be used to populate component parameters with the same name. A route can have multiple parameters.
Look at the example shown below:
@page "/home/{Name}/{Message}"
<h1>Hello @Name</h1>
<h3>@Message</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public string Message { get; set; }
}
The component’s route parameter property must be defined as public. Using any other access modifier will result in a compile-time error.
The route parameter names are case insensitive, which means we can use the route parameters as shown below:
@page "/home/{name}/{message}"
<h1>Hello @NAME</h1>
<h3>@MESSAGE</h3>
@code {
[Parameter]
public string NAME { get; set; }
[Parameter]
public string MESSAGE { get; set; }
}
Blazor also supports optional route parameters. To mark a route parameter as optional, suffix it with the ? symbol.
Look at the example shown below:
@page "/home/{name}/{message?}"
<h1>Hello @Name</h1>
<h3>@Message</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public string Message { get; set; }
}
We can have more than one optional parameter.
The following route is valid.
@page "/home/{name}/{message?}/{text?}"
The non-optional parameters cannot appear after the optional parameters. If you add any non-optional parameter after an optional parameter, you will get a runtime error.
The following route is invalid.
@page "/home/{name?}/{message}"
Route constraints
We can use the route constraints to enforce type matching on the route.
Look at the example shown below:
@page "/home/{name}/{userID:int}/{isAdmin:bool}"
<h1>Hello @Name</h1>
<h3>User ID: @userID</h3>
<h3>isAdmin: @isAdmin</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public int userID { get; set; }
[Parameter]
public Boolean isAdmin { get; set; }
}
The route for this component will match if the following criteria are satisfied:
- The route must have a value for the name parameter.
- The route must have a value for the userID parameter, and it should be of type int.
- The route must have a value for the isAdmin parameter, and it should be of type bool.
An example route to match this pattern can be – “/home/John/1234/true”
We can use route constraints with optional parameters as well.
e.g.:
@page "/home/{name}/{userID:int}/{isAdmin:bool?}"
NavigationManager
We can use NavigationManager class to handle navigation via C# code. This class provides a NavigateTo method, which accepts the route of the component as a parameter and redirects the user from one component to another.
Let us understand this with the help of an example.
We have created a base class Routing.razor.cs as shown below:
using Microsoft.AspNetCore.Components;
namespace BlazorWasmDemo.Client.Pages;
public class RoutingBase : ComponentBase { [Inject] public NavigationManager NavigationManager { get; set; }
[Parameter] public string Name { get; set; }
protected void NavigateAway() { NavigationManager.NavigateTo("parent/1234"); } }
We will add the following code in the Routing.razor component.
@page "/home/{name}"
@inherits RoutingBase
<h1>Hello @Name</h1>
<button @onclick="NavigateAway" >Navigate away</button>
We will invoke the NavigateAway method with the click of a button. It will redirect the user to the route “/parent/1234”.
The NavigateTo method accepts an optional Boolean parameter, forceLoad. If we pass the true value for this parameter, the browser will reload the new page from the server.
Catch-all route parameters
A catch-all route parameter can be used to handle various routes when the routing parameters are not explicitly defined.
Look at the example component shown below:
@page "/home/{*routeParams}"
@code {
[Parameter]
public string? RouteParams { get; set; }
}
For this component, the following routes are valid:
- /home/John
- /home/123
- /home/John/123/345/USA/true
A catch-all route parameter must follow the following criteria:
- It should have a corresponding component parameter with the same name. The name is case insensitive.
- It should be a string type. We can not use route constraints on them.
- It should be present at the end of the URL.
NavLink component
We can use the NavLink component in place of the <a> attribute in HTML while creating navigation links. It toggles an active CSS class, based on whether the current URL matches the href property. This helps the user to understand which pages are active among all the available navigation links.
e.g.:
<NavLink class="nav-link" href="" Match="NavLinkMatch.All" >
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
The Match attribute of the NavLink component can have two possible values:
- NavLinkMatch.All: NavLink should be active only if it matches the entire current URL
- NavLinkMatch.Prefix: NavLink should be active only if it matches any prefix of the current URL. This is the default value.
Query strings
We can use the [SupplyParameterFromQuery] attribute along with the [Parameter] attribute to specify that the component parameter can be supplied via a query string from the route.
@page "/movie"
<p>@Name</p>
<p>@Genre</p>
@code {
[Parameter]
[SupplyParameterFromQuery]
public string? Name { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public string? Genre { get; set; }
}
A valid route for this component can be “/movie?name=avatar&genre=science%20fiction” We can specify the name for the query parameter using the Name property.
e.g.:
[Parameter]
[SupplyParameterFromQuery(Name="Category")]
public string? Genre { get; set; }
A valid route, in this case, will be “/movie?name=avatar&category =science%20fiction”
A query parameter for the component supports the following data types:
- bool
- DateTime
- decimal
- double
- float
- Guid
- int
- long
- string
We can use the GetUriWithQueryParameter method of the NavigationManager class to add, update or delete one or more query parameters from the current URL.
The syntax for this method is GetUriWithQueryParameter("{NAME}", {VALUE}), where {NAME} is a placeholder for the name of the query parameter and {VALUE} is a placeholder for the value of the query parameter. The method will return a string value.
If the query parameter already exists in the current URI, this method will update the value. If the query parameter does not exist in the current URI, this method will add the new parameter with the specified value.
e.g.:
@page "/movie"
@inject NavigationManager Navigation
<p>@Name</p>
<p>@Genre</p>
<button @onclick="UpdateCurrentURI">Update URI</button>
<p>NewURI: @NewURI</p>
<button @onclick="NavigateToNewURI">Go To New URI</button>
@code {
string NewURI { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public string? Name { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "Category")]
public string? Genre { get; set; }
void UpdateCurrentURI() {
NewURI = Navigation.GetUriWithQueryParameter("name", "Top Gun");
}
void NavigateToNewURI() {
Navigation.NavigateTo(NewURI);
}
}
The value for the query parameter, “name” will be updated when we click on the button.
If we want to append a new query parameter to the URI, then we can update the code as shown below:
NewURI = Navigation.GetUriWithQueryParameter("language", "English");
If we want to remove an existing query parameter from the URI, then we can set the value to null as shown below:
NewURI = Navigation.GetUriWithQueryParameter("name", (string?)null);
We can use the GetUriWithQueryParameters method to add, update, and remove multiple parameters from a URI simultaneously.
Look at the example shown below:
NewURI = Navigation.GetUriWithQueryParameters(new Dictionary<string, object?>
{
["name"] = "Top Gun",
["category"] = "Action",
["language"] = "English",
});
This will update the existing query parameters, name & category and it will add a new query parameter, language with the value set to English.
Blazor layouts
A layout is a Blazor component which contains the UI features that are common across multiple components such as the Nav menu, header, footer etc. The default app layout is defined in the Router component inside App.razor file.
We can use layouts only for the routable Razor components having the @page directive. The default app layout is defined in the Router component inside App.razor file.
We can use the @layout directive to specify the layout for a routable component having the @page directive. Specifying the layout directly in a component overrides the default app layout set in the Router component.
Look at the example shown below:
@layout CustomLayout
@page "/layout-demo"
<h1>Custom layout demo.</h1>
Blazor supports the nesting of layouts. A component can refer to a layout, which in turn will refer to another layout. This can help create a multi-level menu structure.
Create a custom layout component
For a component to act as a layout component, it must fulfil the following two criteria:
- It should be inherited from the LayoutComponentBase class. This class defines a Body property, which is used to render the content inside the layout.
- It must define a location to specify where the body content should be rendered. This is done by using Razor syntax @Body.
Look at the example shown below:
@inherits LayoutComponentBase
<header>
<h1>Welcome to Blazor tutorial</h1>
</header>
@Body
<footer>
<h1>All rights reserved</h1>
</footer>
Dependency Injection
Dependency injection (DI) is a software design pattern which helps us to achieve Inversion of Control (IoC) between classes and their dependencies.
We can inject the framework-registered services directly into the Blazor components. Custom services should be registered in the Blazor app to make them available via DI.
Service lifetime
Blazor services can be configured with the following three lifetimes:
- Singleton: Creates a single instance of a service that is shared across all components.
- Transient: Creates a new instance of a service every time a component requests the service.
- Scoped:This creates one instance of service per web request.
- The Blazor WebAssembly apps do not support the Scoped lifetime. A Scoped-registered service behaves like a Singleton service for Blazor WebAssembly apps.
- The Blazor Server apps support the Scoped lifetime across HTTP requests but not across SignalR connection messages among components that are loaded on the client side.
Default Blazor Services
The services shown in the following table are frequently used in Blazor apps.
Service | Blazor Wasm Lifetime | Blazor Server Lifetime | Description |
---|---|---|---|
HttpClient | Scoped | Scoped | Provides methods for handling HTTP requests and responses from a resource URL. |
IJSRuntime | Singleton | Scoped | Represents an instance of a JavaScript runtime where JavaScript calls are dispatched. |
NavigationManager | Singleton | Scoped | Provides helper classes for querying and managing URI navigation. |
Add services to a Blazor WASM app
We can use the Program.cs file to register custom services for both the Blazor Wasm app and the Blazor Server app.
Look at the code sample from the Program.cs file for a Blazor Wasm app:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
Look at the code sample from the Program.cs file for a Blazor Server app:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
app.UseRouting();
app.Run();
Inject a Service in Blazor component
We can inject a service into a component using the following two methods:
- Using the [Inject] attribute in the base class.
- Using the @inject directive in the component.
We can inject multiple services into a single component.
Look at the example shown below for the Razor component:
@page "/movie"
@inject NavigationManager Navigation
<button @onclick="NavigateToPage">Go To Page</button>
@code {
void NavigateToPage()
{
Navigation.NavigateTo("/home");
}
}
Look at the example shown below for the base class:
using Microsoft.AspNetCore.Components;
namespace BlazorWasmDemo.Client.Pages
{ public class RoutingBase : ComponentBase { [Inject] public NavigationManager NavigationManager { get; set; } = default!;
[Inject] HttpClient Http { get; set; } = default!;} }
The injected services are expected to be available as the component gets initialized Therefore, we have assigned a default literal with the null-forgiving operator (default!). If we do not assign a non-null default value, the compiler will show a warning message as “Non-nullable property must contain a non-null value when exiting constructor”. Alternatively, with .NET SDK version 7 or later, the "required" modifier, added from C#11, can be used to write the following without resorting to the null-forgiving operator.
[Inject] public required NavigationManager NavigationManager { get; set; }
Use dependency injection in Blazor services.
We can use the constructor injection to inject a service into another service. The [Inject] attribute or the @inject directive is not available for use in the service class.
Look at the example shown below:
public class MyCustomService
{ private readonly HttpClient _httpClient;private readonly HttpClient _httpClient; public MyCustomService(HttpClient httpClient, NavigationManager navigationManager) { _httpClient = httpClient; _navigationManager = navigationManager; } }
JavaScript Interop
JavaScript interoperability aka JavaScript interop is defined as the ability to invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions.
Call JavaScript functions from .NET methods
We can use the IJSRuntime abstraction to call JavaScript functions from .NET.
The IJSRuntime interface provides two methods:
- InvokeAsync: It is used to call the JavaScript functions that return any value or an object, including a Promise.
- InvokeVoidAsync: It is used to call the JavaScript functions which return void or undefined.
We can add the custom JavaScript code in the wwwroot/index.html in a Blazor WebAssembly app. We can add the custom JavaScript code in the Pages/_Host.cshtml in a Blazor WebAssembly app.
Let us understand this with an example.
<script>
window.getArraySum = (numberArray) => {
return numberArray.reduce((a, b) => a + b, 0);
}
</script>
The getArraySum function will accept an array as a parameter and return the sum of all the elements of the array.
Create a component JSDemoBase.razor and add the following code into the base class JSDemoBase.razor.cs.
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorWasmDemo.Client.Pages;
public class JSDemoBase : ComponentBase
{ [Inject] Protected IJSRuntime JSRuntime { get; set; } = default!;
protected int sumOfArray = 0;
private int[] arrayItems = new int[] { 2, 4, 6, 8, 10 };
protected async Task GetSumOfArray(){ sumOfArray = await JSRuntime.InvokeAsync<int>("getArraySum", arrayItems); } }
We will use the InvokeAsync method to invoke the JS function. The JS function will return the sum and it will be assigned to an integer variable sumOfArray.
This GetSumOfArray method can be invoked on a button click as shown below:
@page "/calljsmethod"
@inherits JSDemoBase
<button @onclick="GetSumOfArray">Get Array Sum</button>
<p>The sum of array elements is : @sumOfArray</p>
If we want to invoke the inbuilt methods of JavaScript, we can use the following piece of code:
protected async Task ShowAlert()
{ await JSRuntime.InvokeVoidAsync("alert", "I am invoked from .NET code"); }
protected async Task ShowArrayItems()
{ await JSRuntime.InvokeVoidAsync("console.table", arrayItems); }
We have invoked the alert function and passed a string parameter to it. The ShowArrayItems method will invoke the console.table function of JavaScript and pass the arrayItems to be displayed as input.
Capture references to HTML elements
We can capture the references to HTML elements in a component using the ElementReference struct. It is used to represent a reference to a rendered element.
To capture references to HTML elements in a component, we need to add an @ref attribute to the HTML element. Also, define a field of type ElementReference whose name matches the value of the @ref attribute.
We can pass the ElementReference to the JS code via JS interop. The JS code will receive an HTMLElement instance which it can use for DOM manipulation.
Let us understand this with an example.
Add the following JS code.
<script>
window.showValue = (element) => {
alert('Your name is :' + element.value);
}
</script>
This function will receive the reference of an HTML element and display the value in an alert box.
We can add the following code in the base class to invoke this function.
protected ElementReference name;
protected async Task ShowName()
{ await JSRuntime.InvokeVoidAsync("showValue", name); }
We can use the @ref attribute in an HTML element to capture the ElementReference instance as shown in the code below:
<button class="btn btn-primary" @onclick="ShowName">Show Name</button>
<input type="text" @ref="name" class="form-control" placeholder="Enter your name" />
Call static .NET methods from JavaScript functions
We can use DotNet.invokeMethod or DotNet.invokeMethodAsync functions to invoke a static .NET method from JS.
The .NET method should fulfil the following criteria:
- It should be declared as public and static.
- It should be decorated with the [JSInvokable] attribute.
We can pass the identifier of the static method, the name of the assembly which contains the C# method and any arguments required.
The syntax of the function is - DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD Name}', {ARGUMENTS});
where,
- {ASSEMBLY NAME} is a placeholder for the application assembly name.
- {.NET METHOD Name} is a placeholder for the .NET method to be invoked.
- {ARGUMENTS} is a placeholder for the argument required by the .Net method to be invoked. This is an optional parameter.
Let us understand this with an example.
Add the following code in the JSDemoBase.razor.cs file.
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorTutorial.Client.Pages;
public class JSDemoBase: ComponentBase
{ [Inject] protected IJSRuntime JSRuntime { get; set; } = default!;
protected void InvokeJSMethod() { JSRuntime.InvokeVoidAsync("printMessageToConsole"); }
[JSInvokable] public static Task<string> PrintMessage() { return Task.FromResult("I am invoked from JavaScript."); }}
The InvokeJSMethod will call a JS function, printMessageToConsole. The PrintMessage method has the [JSInvokable] attribute. This method is declared as public and static. This will allow the JS function printMessageToConsole to invoke the PrintMessage method.
Add a button in the JSDemoBase.razor file as shown below:
<button @onclick="InvokeJSMethod">Invoke static .NET method</button>
Add the following JS code.
<script>
window.printMessageToConsole = () => {
DotNet.invokeMethodAsync(BlazorTutorial.Client', 'PrintMessage')
.then(data => {
console.log(data);
});
}
</script>
We have created a JS function, printMessageToConsole. We will use the DotNet.invokeMethodAsync function to invoke the C# method, PrintMessage. We have passed the name of the assembly as BlazorTutorial.Client.
Call component instance method
We can invoke the .NET instance method of a Blazor component using the following steps:
- Generate a .NET object reference that can be passed to JavaScript functions using DotNetObjectReference.Create.
- Make an instance method call to the component using the invokeMethod or invokeMethodAsync method of a .NET object.
- Ensure the .NET object reference is also disposed when the component is disposed.
Let us understand this with an example.
Add the following JS code.
function displayMessageCallerJS(objectRef, value) {
objectRef.invokeMethodAsync('DisplayMessage', value);
}
We have added a function displayMessageCallerJS which will accept two parameters named objectRef and value. This function will then invoke the DisplayMessage method of the component object which the objectRef is referencing and pass the value as the argument.
Add the following code in the JSDemoBase.razor.cs file:
public class JSDemoBase : ComponentBase, IDisposable
{
private DotnetObjectReference<JSDemoBase>? objectRef;
protected string message = "Click the button.";
protected override void OnInitialized()
{
objectRef = DotNetObjectReference.Create(this);
}
[JSInvokable]
public void DisplayMessage(string value)
{
message = value;
StateHasChanged();
}
public void Dispose()
{
objectRef?.Dispose();
}
}
We have declared a field variable objectRef of type DotNetObjectReference<JSDemoBase> which represents a reference of a .NET object. Inside the OnInitialized lifecycle method, we are creating a reference of this component itself using the DotNetObjectReference.Create static method and assigning it to the objectRef field.
The InvokeDisplayMessageCallerJS method will accept a string parameter. Then this method will call the JS function, displayMessageCallerJS, and pass the .NET object reference field that references this component as a first argument and pass the string parameter as a second argument.
We have created a public instance method, DisplayMessage, which has the [JSInvokable] attribute and will accept string parameters. This instance method will be invoked from the JS function displayMessageCallerJS. We will assign the parameter value to the message field and then call the StateHasChanged() method to notify the component about the state change.
Finally, implement the IDisposable interface to ensure the .NET object reference that refers to the component itself is also disposed when this component is disposed. Specifically, we have called the Dispose method of the .NET object reference in the Dispose method of this component.
Add a button in the JSDemoBase.razor file as shown below:
<button @onclick="@(()=>InvokeDisplayMessageCallerJS("The Button is clicked"))">Call JS Method</button>
<br />
<p >@message</p>
On the button click, we will call the InvokeDisplayMessageCallerJS method and pass a string value as the parameter. The updated value of the message will be displayed on the screen.
Continue Reading
Fill out the form to continue reading.