Conteggiare le connessioni SignalR aperte in Blazor Server

di Moreno Gentili, in ASP.NET Core,

Nelle applicazioni Blazor Server, il client stabilisce una connessione persistente con il server e delega ad esso le responsabilità di mantenere lo stato della UI e determinare quali cambiamenti devono essere apportati a seguito delle interazioni dell'utente.
Questa "relazione di lunga durata" tra client è server è anche chiamata "Circuit".

Blazor Server è estendibile e ci permette di creare un cosiddetto CircuitHandler per eseguire della logica personalizzata quando un Circuit viene aperto o chiuso, ovvero quando un client si connette o disconnette dall'applicazione.
Vediamo un primo esempio minimale: creiamo una classe che deriva da CircuitHandler in una directory qualsiasi del progetto, ad esempio in /CircuitHandlers.

public class MinimalCircuitHandler : CircuitHandler
{
  public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
  {
    //TODO: Eseguiamo del codice quando un Circuit viene aperto

    //Poi invochiamo il metodo base per non interferire con la logica base del CircuitHandler
    return base.OnCircuitOpenedAsync(circuit, cancellationToken);
  }
  public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
  {
    //Il Circuit è stato chiuso perché probabilmente l'utente ha chiuso la tab del browser
    //oppure perché ha perso la connessione al server per altri motivi

    //Ed eseguiamo il metodo base
    return base.OnCircuitClosedAsync(circuit, cancellationToken);
  }
}

Il CircuitHandler che abbiamo appena visto è banale ma vediamo un caso più significativo, in cui può tornarci utile eseguire della logica personalizzata.

Conteggiare i Circuit aperti

L'uso più semplice che possiamo fare di un CircuitHandler è quello di conteggiare i Circuit aperti. Questo valore può non corrispondere esattamente al numero di utenti collegati perché ogni utente ha facoltà di aprire l'applicazione in molteplici tab del suo browser. Per ogni tab, Blazor Server aprirà un Circuit.

Vale comunque la pena di conteggiare i Circuit aperti anche soltanto a scopo diagnostico, per avere un'idea di massima di quale sia il numero di connessioni contemporanee che l'applicazione sta mantenendo. Questo valore può aiutarci a dimensionare le caratteristiche dell'hardware su cui gira l'applicazione. Ecco degli esempi forniti da Microsoft all'indirizzo https://devblogs.microsoft.com/aspnet/blazor-server-in-net-core-3-0-scenarios-and-performance/


Per ottenere il numero di Circuit aperti, incrementiamo un contatore nel metodo OnCircuitOpenedAsync e lo decrementiamo nel metodo OnCircuitClosedAsync. È importante aggiornare il contatore in maniera thread-safe, ad esempio usando la classe Interlocked. Come vedremo poi, il CircuitHandler dovrà essere registrato con il ciclo di vita Singleton.

public class CountCircuitHandler : CircuitHandler, ICircuitCounter
{
    //Inizializziamo il contatore
    private long circuitCount = 0;

    public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        //Incrementiamo il contatore in maniera thread-safe
        long updatedCircuitCount = Interlocked.Increment(ref circuitCount);
        //Solleviamo un evento per notificare l'avvenuto cambiamento
        NotifyUpdatedCircuitCount(updatedCircuitCount);
        //Invochiamo il metodo base
        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }
    public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
    {
        //Decrementiamo il contatore
        long updatedCircuitCount = Interlocked.Decrement(ref circuitCount);
        //Notifichiamo
        NotifyUpdatedCircuitCount(updatedCircuitCount);
        //Invochiamo il metodo base
        return base.OnCircuitClosedAsync(circuit, cancellationToken);
    }

    private void NotifyUpdatedCircuitCount(long updatedCircuitCount)
    {
        CircuitCountChanged?.Invoke(this, updatedCircuitCount);
    }

    public long CurrentCircuitCount => Interlocked.Read(ref circuitCount);
    public event EventHandler<long> CircuitCountChanged;
}

Come si vede nell'esempio, la classe ora implementa anche una nostra interfaccia personalizzata ICircuitCounter che usiamo per ridurre la superficie della API pubblica del CircuitHandler.

public interface ICircuitCounter
{
  long CurrentCircuitCount { get; }
  event EventHandler<long> CircuitCountChanged;
}

In questo modo, esponendo la proprietà CurrentCircuitCount, possiamo consultare il numero di Circuit attualmente aperti e, con l'evento CircuitCountChanged, otteniamo i nuovi valori in tempo reale. Andiamo a consumare questi due membri pubblici da un Razor Component.

Visualizzare il numero di Circuit aperti in un Razor Component


Da un Razor Component, sfruttiamo la dependency injection per ricevere l'istanza del servizio ICircuitCounter e, grazie a esso, visualizziamo in tempo reale il numero di Circuit aperti.

@page "/counter"
@inject ICircuitCounter circuitCounter
@implements IDisposable
<h1>Circuit counter</h1>
<p>Numero di Circuit attualmente aperti: @currentCircuitCount</p>

@code {
  long currentCircuitCount;
  protected override void OnInitialized()
  {
    //Otteniamo il valore attuale dalla proprietà CurrentCircuitCount
    currentCircuitCount = circuitCounter.CurrentCircuitCount;
    //E ci sottoscriviamo all'evento CircuitCountChanged per ricevere i futuri cambiamenti
    circuitCounter.CircuitCountChanged += UpdateCircuitCount;
  }
  private void UpdateCircuitCount(object sender, long circuitCount)
  {
    //Usiamo InvokeAsync per far eseguire questo codice
    //al thread del renderer, altrimenti avremmo un'eccezione
    InvokeAsync(() => 
    {
      //Aggiorniamo il conteggio
      currentCircuitCount = circuitCount;
      StateHasChanged();
    });
  }
  public void Dispose()
  {
    //Rimuoviamo la sottoscrizione quando
    //il Razor Component non serve più
    circuitCounter.CircuitCountChanged -= UpdateCircuitCount;
  }
}

Quando sottoscriviamo eventi come in questo caso, è sempre importante che il Razor Component implementi l'interfaccia IDisposable e che le sottoscrizioni vengano rimosse nel suo metodo Dispose.

Registrare il Circuit Handler per la dependency injection


Affinché tutto ciò funzioni, facciamo in modo che il nostro CircuitHandler vada a rimpiazzare quello predefinito di Blazor Server. Per far questo, rechiamoci nella classe Startup e nel suo metodo ConfigureServices aggiungiamo quanto segue:

//Usiamo il ciclo di vita Singleton
services.AddSingleton<CircuitHandler, CountCircuitHandler>();
//La stessa istanza viene riutilizzata anche quando si richiede il servizio ICircuitCounter
services.AddSingleton<ICircuitCounter>(provider => provider.GetService<CircuitHandler>() as ICircuitCounter);

Avviando l'applicazione in debug noteremo che il contatore viene incrementato ogni volta che viene aperta in una nuova tab del browser e decrementato istantaneamente alla sua chiusura.


Nel prossimo script proveremo invece a elencare i nomi degli utenti connessi, che può risultare utile per una funzionalità di messaggistica istantanea interna all'applicazione.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi