Telerik blogs

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.

What Is a Circuit in Blazor?

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.

What Is a CircuitHandler?

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.

Showing the Number of Connected Users using a CircuitHandler

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.

A browser showing the application with the text 'Active Users: 1'

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.

Two browser windows showing the application with the text 'Active Users: 1' and 'Active Users: 2'

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.

Three browser windows showing the application with the up-to-date text 'Active Users: 3'.

When you close one of the browsers, the value should be updated (decreased by 1) in the remaining browser windows.

Use Cases for Circuit Handlers

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.

Advanced Scenario: Using the CircuitHandler to Scale Out

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.

Conclusion

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.


About the Author

Claudio Bernasconi

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.

Comments

Comments are disabled in preview mode.