We learn how to use the CircuitHandler to handle client/server connections in Blazor Server.
Blazor Server applications leverage a persistent WebSocket connection between clients and servers.
Whenever the user interacts with the application, the server internally updates the Document Object Model (DOM) and returns the required changes to the client’s user interface.
This architecture requires a stable internet connection. If the connection is unstable, the Blazor Server applications become slow and sometimes stop responding.
In this article, we will learn about the Circuit Handlers. A CircuitHandler
allows us to write custom code for events triggered when a user establishes or loses its connection with a Blazor Server web application.
You’ll find out what scenarios benefit from a custom CircuitHandler
implementation.
You can access the code used in this example on GitHub.
In Blazor Server, a circuit refers to a user’s session lifetime. The session starts when the user first connects to the server and ends when the client closes the web browser or leaves the website.
Hint: It’s essential to understand that circuits only matter in Blazor Server because we have a persistent WebSocket connection between a client and the server.
A CircuitHandler
is the implementation of a class inheriting from the Microsoft.AspNetCore.Components.Server.Circuits.CircuitHandler
type. The base class allows us to override four methods:
OnCircuitOpenedAsync
is called when a new circuit is established, which typically is when a new user connects to the server and starts a session.OnCircuitClosedAsync
is called when a circuit ends, which is typically when a user terminates the session by leaving the website.OnConnectionUpAsync
is called when a connection becomes available, typically in a reconnection scenario.OnConnectionDownAsync
is called when a connection is lost.Let’s implement a custom CircuitHandler that counts the number of concurrent users. We will also render the information on the website.
First of all, in a new Blazor Web App with Blazor Server as the interactivity mode, we create a new class named DemoCircuitHandler
, inherit from the CircuitHandler
base type, and override the OnCircuitOpenedAsync
and the OnCircuitClosedAsync
methods.
using Microsoft.AspNetCore.Components.Server.Circuits;
namespace CircuitHandlerDemo;
public class DemoCircuitHandler : CircuitHandler
{
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
// Code to run when a new circuit (user session) starts
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
// Code to run when a circuit ends (user disconnects)
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
}
Before we implement the two overridden methods, we register the DemoCircuitHandler
class with the dependency injection system in the Program.cs
file.
using Microsoft.AspNetCore.Components.Server.Circuits;
builder.Services.AddSingleton<CircuitHandler, DemoCircuitHandler>();
Next, we implement a simplistic service to store the value of how many concurrent users are visiting the website.
namespace CircuitHandlerDemo;
public class ConcurrentUsersService
{
public int Users { get; set; }
}
To keep it simple, we do not use an interface, and we do not implement event handling. However, we also need to register this class as a Singleton instance in the Program.cs
file:
builder.Services.AddSingleton<ConcurrentUsersService>();
Now, we are ready to implement the code in the DemoCircuitHandler
class.
using Microsoft.AspNetCore.Components.Server.Circuits;
namespace CircuitHandlerDemo;
public class DemoCircuitHandler(ConcurrentUsersService _concurrentUsersService) : CircuitHandler
{
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
// Code to run when a new circuit (user session) starts
_concurrentUsersService.Users += 1;
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
// Code to run when a circuit ends (user disconnects)
_concurrentUsersService.Users -= 1;
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
}
We use Primary Constructors (C# 12) to receive a reference to the ConcurrentUsersService
instance.
Next, we access the ConcurrentUsersService
instance and increase the number of concurrent users when the OnCircuitOpenedAsync
method is called.
We implement the same code but decrease the number by 1 in the OnCircuitClosedAsync
method.
Now, we want to show the number of concurrent users on the user interface. We open the NavMenu
component because it is rendered on all pages.
We inject a reference to the ConcurrentUsersService
using the @inject
directive:
@inject ConcurrentUsersService ConcurrentUsersService
Next, we add another item below the third NavLink
component:
<div class="nav-item px-3">
<div style="color: white;">Active Users: @ConcurrentUsersService.Users</div>
</div>
This code accesses the Users
property on the injected ConcurrentUsersService
instance and renders the value on the screen.
Now, let’s test the application. When you start the application, you should see the text Active users: 1 on the bottom of the navigation menu on the left.
Open another browser and navigate to the same URL where your Blazor Server application is running. The text should now state that there are two active users.
Notice: With the simple implementation of the ConcurrentUsersService
, we do not get automatic user interface updates when a user connects or disconnects.
Let’s fix that.
We replace the simplistic implementation of the ConcurrentUsersService
class with a more sophisticated implementation that defines and fires an event.
namespace CircuitHandlerDemo;
public class ConcurrentUsersService
{
private int users;
public event Action<int>? OnUsersChanged;
public int Users
{
get => users;
set
{
users = value;
OnUsersChanged?.Invoke(value);
}
}
}
Notice the OnUsersChanged
event defined below the private field that holds the number of users.
We invoke/fire the event whenever the setter of the Users
property is executed and provide the current value to the event handler.
This change requires us to change the code in the NavMenu
component that consumes this service.
In the NavMenu.razor
file, we add a code section with the following implementation:
@code {
public int Users { get; set; }
protected override void OnInitialized()
{
ConcurrentUsersService.OnUsersChanged += UpdateState;
UpdateState(ConcurrentUsersService.Users);
}
public void UpdateState(int users)
{
InvokeAsync(() =>
{
Users = users;
StateHasChanged();
});
}
public void Dispose()
{
ConcurrentUsersService.OnUsersChanged -= UpdateState;
}
}
First, we introduce a Users
property that holds the component state. We also change the code that renders the user interface to use the Users
property.
<div class="nav-item px-3">
<div style="color: white;">Active Users: @Users</div>
</div>
Inside the OnInitialized
lifecycle method, we register the UpdateState
method with the OnUsersChanged
event on the ConcurrentUsersService
. We also execute the UpdateState
method with the current value.
Don’t forget to implement the Dispose
method to unregister the UpdateState
method from the OnUsersChanged
event to prevent memory leaks.
Now, let’s test the application again.
With a single browser, the application still looks the same. However, when opening another browser and accessing the website, both browser windows show the up-to-date value for how many users are connected to the application.
When you close one of the browsers, the value should be updated (decreased by 1) in the remaining browser windows.
Besides tracking how many users are active, there are many more scenarios where implementing custom code in a Circuit Handler makes sense.
Typical scenarios involve logging when a user connects or disconnects, managing resources tied to a user’s session (for example, cleaning up expensive in-memory objects), or saving the application state when a user disconnects.
Let’s say you have a long-lasting process where the user is required to enter information. If the user suddenly disconnects, the data is lost.
However, inside the OnConnectionDownAsync
method, you get the chance to persist that state and to restore it in the OnConnectionUpAsync
method. Or you can even go further and persist the state and ask the user if they want to continue work where they last left.
It’s interesting that you can leverage the CircuitHandler
class to gain information about how many users are connected to a server and emit that information to Application Insights or other metrics tooling.
When running in the cloud, you can use this information to scale up/out (or down) based on how many users are connected to your application.
However, implementing such a specialized use case is beyond this article. Still, it shows how versatile the different use cases for implementing a CircuitHandler
in Blazor Server can be.
With a custom CircuitHandler
, you can add additional functionality to persist the intermediate application state when a user loses connection to a Blazor Server application.
There are different scenarios, such as logging, saving/restoring the application state or managing expensive resources.
If you want to learn more about Blazor development, you can watch my free Blazor Crash Course on YouTube. And stay tuned to the Telerik blog for more Blazor Basics.
Claudio Bernasconi is a passionate software engineer and content creator writing articles and running a .NET developer YouTube channel. He has more than 10 years of experience as a .NET developer and loves sharing his knowledge about Blazor and other .NET topics with the community.