SignalR in ASP.NET Core 2026: Comunicazione Real-Time, Hub e Domande da Colloquio

Padroneggiare SignalR per applicazioni web real-time in ASP.NET Core. Hub, gruppi, scalabilità con Redis e domande frequenti nei colloqui tecnici.

SignalR in ASP.NET Core 2026: Comunicazione Real-Time, Hub e Domande da Colloquio

SignalR abilita la comunicazione bidirezionale in tempo reale tra server e client nelle applicazioni ASP.NET Core. A differenza dei tradizionali pattern HTTP request-response, SignalR mantiene connessioni persistenti che permettono ai server di inviare aggiornamenti ai client istantaneamente, elemento essenziale per applicazioni di chat, dashboard live, editing collaborativo e notifiche in tempo reale.

Fallback dei Trasporti SignalR

SignalR seleziona automaticamente il miglior trasporto disponibile: prima WebSockets, poi Server-Sent Events, infine Long Polling. Questo meccanismo di fallback garantisce compatibilità su tutti i browser e configurazioni di rete senza alcuna modifica al codice.

Come gli Hub SignalR Abilitano la Comunicazione Server-Client

Un Hub funziona come una pipeline ad alto livello che gestisce le invocazioni di metodi tra client e server. I client chiamano metodi sull'hub, e l'hub può invocare metodi sui client connessi. Questa astrazione elimina la complessità della gestione manuale delle connessioni WebSocket.

L'esempio seguente dimostra un hub di chat basilare che trasmette messaggi a tutti i client connessi:

ChatHub.cscsharp
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Called when a client sends a message
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to ALL connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    // Called automatically when a client connects
    public override async Task OnConnectedAsync()
    {
        await Clients.Caller.SendAsync("Connected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    // Called automatically when a client disconnects
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        // Clean up resources, notify other users, etc.
        await base.OnDisconnectedAsync(exception);
    }
}

La proprietà Clients fornisce accesso a tutti i client connessi attraverso varie opzioni di targeting: All, Caller, Others, Group e User.

Configurazione di SignalR in ASP.NET Core 9

La registrazione di SignalR in ASP.NET Core 9 segue il pattern middleware standard. La configurazione seguente abilita i protocolli JSON e MessagePack con dimensioni buffer personalizzate:

Program.cscsharp
var builder = WebApplication.CreateBuilder(args);

// Add SignalR services with configuration
builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
    options.StreamBufferCapacity = 10;
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddJsonProtocol(options =>
{
    options.PayloadSerializerOptions.PropertyNamingPolicy = null; // PascalCase
});

var app = builder.Build();

// Map the hub endpoint
app.MapHub<ChatHub>("/chathub");

app.Run();

Le impostazioni KeepAliveInterval e ClientTimeoutInterval controllano il monitoraggio dello stato della connessione. I client che non rispondono entro il periodo di timeout vengono considerati disconnessi.

Gestione dei Gruppi per la Consegna Mirata dei Messaggi

I gruppi forniscono un meccanismo per organizzare le connessioni e inviare messaggi a sottoinsiemi di client. Un caso d'uso tipico coinvolge le chat room, dove gli utenti si uniscono a stanze specifiche e ricevono solo messaggi da quelle stanze.

GroupChatHub.cscsharp
public class GroupChatHub : Hub
{
    // Add the current connection to a group
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        
        // Notify the group that someone joined
        await Clients.Group(roomName).SendAsync(
            "UserJoined", 
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Remove the current connection from a group
    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name ?? "Anonymous"
        );
    }

    // Send a message to a specific group only
    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync(
            "ReceiveMessage",
            Context.User?.Identity?.Name,
            message
        );
    }
}

L'appartenenza ai gruppi è legata alle connessioni, non agli utenti. Quando un utente si riconnette, deve rientrare nei suoi gruppi. Per un'appartenenza persistente ai gruppi, le associazioni devono essere memorizzate in un database e ripristinate automaticamente in OnConnectedAsync.

Persistenza dei Gruppi

I gruppi esistono solo in memoria e non vengono persistiti attraverso riavvii del server o in scenari scalati senza backplane. Per deployment in produzione con più server, utilizzare Redis o Azure SignalR Service come backplane.

Protezione delle Connessioni SignalR con Autenticazione

SignalR si integra con l'autenticazione di ASP.NET Core. L'attributo [Authorize] limita l'accesso all'hub agli utenti autenticati, e Context.User fornisce accesso ai claim dell'utente autenticato.

SecureHub.cscsharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;

[Authorize] // Require authentication for the entire hub
public class SecureHub : Hub
{
    // Access user claims through Context.User
    public async Task SendPrivateMessage(string recipientUserId, string message)
    {
        var senderName = Context.User?.Identity?.Name 
            ?? throw new HubException("User not authenticated");
        
        // Send to a specific user (all their connections)
        await Clients.User(recipientUserId).SendAsync(
            "PrivateMessage",
            senderName,
            message
        );
    }

    [Authorize(Roles = "Admin")] // Role-based authorization on method
    public async Task BroadcastAnnouncement(string announcement)
    {
        await Clients.All.SendAsync("Announcement", announcement);
    }
}

Per le connessioni WebSocket, i token di autenticazione devono essere passati tramite query string poiché WebSocket non supporta header personalizzati. Il client JavaScript viene configurato per includere il token:

signalr-client.tstypescript
import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/securehub", {
        accessTokenFactory: () => localStorage.getItem("authToken") || ""
    })
    .withAutomaticReconnect()
    .build();

await connection.start();

Sul server, l'autenticazione JWT viene configurata per leggere il token dalla query string:

Program.cs - JWT configuration for SignalRcsharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Read token from query string for SignalR
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) && 
                    path.StartsWithSegments("/securehub"))
                {
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    });

Pronto a superare i tuoi colloqui su .NET?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Scalabilità di SignalR con Redis Backplane

In un ambiente con load balancing, i client potrebbero connettersi a diverse istanze del server. Senza un backplane, i messaggi inviati da un server raggiungono solo i client connessi a quel server. Redis serve come backplane per sincronizzare i messaggi attraverso tutte le istanze del server.

Program.cs - Redis backplane configurationcsharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(options =>
    {
        options.Configuration = builder.Configuration
            .GetConnectionString("Redis");
        
        // Optional: configure Redis-specific options
        options.Configuration.ChannelPrefix = 
            RedisChannel.Literal("MyApp_SignalR_");
    });

Il backplane Redis pubblica tutti i messaggi SignalR sui canali Redis. Ogni istanza del server si iscrive a questi canali e consegna i messaggi ai suoi client locali. Questo approccio aggiunge una latenza minima (tipicamente 1-2ms) abilitando al contempo la scalabilità orizzontale.

Per deployment enterprise, Azure SignalR Service fornisce un'alternativa gestita che gestisce automaticamente scalabilità, gestione delle connessioni e disponibilità.

Hub Fortemente Tipizzati per Sicurezza a Compile-Time

Gli hub fortemente tipizzati sostituiscono le magic string con metodi di interfaccia, abilitando la verifica a compile-time delle chiamate ai metodi client:

INotificationClient.cscsharp
public interface INotificationClient
{
    Task ReceiveNotification(string title, string message, string severity);
    Task UpdateProgress(int percentage);
    Task TaskCompleted(Guid taskId, bool success);
}

// NotificationHub.cs
public class NotificationHub : Hub<INotificationClient>
{
    public async Task NotifyAll(string title, string message)
    {
        // Compile-time checking - no magic strings
        await Clients.All.ReceiveNotification(title, message, "info");
    }

    public async Task ReportProgress(string groupName, int percentage)
    {
        // IDE autocomplete works here
        await Clients.Group(groupName).UpdateProgress(percentage);
    }
}

Il refactoring diventa più sicuro perché rinominare un metodo nell'interfaccia mostra immediatamente tutti i punti di chiamata che necessitano aggiornamenti.

Streaming di Dati con SignalR Channels

SignalR supporta lo streaming per scenari dove i dati vengono prodotti incrementalmente, come aggiornamenti di progresso, tailing dei log o dati di sensori in tempo reale. Sono supportati sia lo streaming server-to-client che client-to-server.

StreamingHub.cscsharp
public class StreamingHub : Hub
{
    // Server-to-client streaming with IAsyncEnumerable
    public async IAsyncEnumerable<StockPrice> StreamStockPrices(
        string[] symbols,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        var random = new Random();
        
        while (!cancellationToken.IsCancellationRequested)
        {
            foreach (var symbol in symbols)
            {
                yield return new StockPrice
                {
                    Symbol = symbol,
                    Price = random.NextDouble() * 1000,
                    Timestamp = DateTime.UtcNow
                };
            }
            
            await Task.Delay(1000, cancellationToken);
        }
    }

    // Client-to-server streaming
    public async Task UploadStream(IAsyncEnumerable<LogEntry> stream)
    {
        await foreach (var entry in stream)
        {
            // Process each log entry as it arrives
            await ProcessLogEntry(entry);
        }
    }
}

public record StockPrice(string Symbol, double Price, DateTime Timestamp);
public record LogEntry(string Level, string Message, DateTime Timestamp);

Il client JavaScript consuma lo stream utilizzando un iteratore asincrono:

streaming-client.tstypescript
const stream = connection.stream("StreamStockPrices", ["AAPL", "MSFT"]);

stream.subscribe({
    next: (price) => console.log(`${price.symbol}: $${price.price}`),
    error: (err) => console.error(err),
    complete: () => console.log("Stream completed")
});

Domande Frequenti sui Colloqui SignalR

I colloqui tecnici trattano frequentemente l'architettura SignalR e scenari del mondo reale. Le seguenti domande appaiono regolarmente nelle sessioni di colloquio .NET:

Suggerimento per il Colloquio

Spiegare il processo di negoziazione del trasporto: SignalR tenta prima WebSockets, poi Server-Sent Events, poi Long Polling. Comprendere quando viene utilizzato ogni trasporto e le sue limitazioni.

D: Come gestisce SignalR lo stato della connessione quando un client perde temporaneamente la connettività?

SignalR mantiene lo stato della connessione sul server per un periodo configurabile. Con la riconnessione automatica abilitata, il client tenta di riconnettersi utilizzando lo stesso ID di connessione. In caso di successo, la connessione riprende senza perdere le appartenenze ai gruppi. Se la disconnessione supera il timeout, viene stabilita una nuova connessione e il client deve rientrare nei gruppi.

D: Qual è la differenza tra Clients.User() e Clients.Client()?

Clients.User(userId) mira a tutte le connessioni appartenenti a un utente autenticato specifico, utile quando un utente ha più tab del browser aperte. Clients.Client(connectionId) mira a una singola connessione specifica. Il targeting basato sull'utente richiede autenticazione e utilizza IUserIdProvider per mappare le connessioni agli ID utente.

D: Come si implementerebbe il rilevamento della presenza (mostrare chi è online)?

Tracciare le connessioni in OnConnectedAsync e OnDisconnectedAsync. Memorizzare le mappature utente-connessione in un dizionario concorrente o cache distribuita. Per deployment scalati, utilizzare uno storage condiviso come Redis. Trasmettere gli aggiornamenti di presenza ai gruppi rilevanti quando gli utenti si connettono o disconnettono.

D: Spiegare lo scopo di un backplane SignalR.

Un backplane sincronizza i messaggi attraverso più istanze del server in un ambiente con load balancing. Senza di esso, un messaggio inviato dal server A raggiunge solo i client connessi al server A. Redis e Azure SignalR Service sono opzioni di backplane comuni. Il backplane pubblica messaggi su un canale condiviso a cui tutti i server sono iscritti.

Strategie di Ottimizzazione delle Performance

Le performance di SignalR dipendono dalla dimensione dei messaggi, dalla frequenza e dal numero di connessioni. La documentazione delle best practice per le performance di ASP.NET Core fornisce benchmark dettagliati.

  • Utilizzare il protocollo MessagePack per la serializzazione binaria, riduce la dimensione del payload del 30-50% rispetto a JSON
  • Raggruppare i messaggi quando possibile invece di inviare molti messaggi piccoli
  • Limitare le dimensioni dei gruppi a migliaia di membri; per audience più ampie, considerare pattern pub/sub
  • Abilitare la compressione per payload pesanti di testo
  • Impostare timeout appropriati per rilasciare prontamente le risorse dalle connessioni obsolete
csharp
// MessagePack configuration for smaller payloads
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = 
            MessagePackSerializerOptions.Standard
                .WithCompression(MessagePackCompression.Lz4Block);
    });

Conclusione

  • SignalR astrae la selezione del trasporto (WebSockets, SSE, Long Polling) e fornisce un modello di programmazione consistente per la comunicazione real-time
  • Gli hub centralizzano la gestione delle connessioni; gli hub fortemente tipizzati aggiungono sicurezza a compile-time per le chiamate ai metodi client
  • I gruppi abilitano la consegna mirata dei messaggi a sottoinsiemi di connessioni, necessario rientrare nei gruppi dopo la riconnessione
  • L'autenticazione si integra con ASP.NET Core Identity; passare i token tramite query string per le connessioni WebSocket
  • Scalare orizzontalmente con backplane Redis o Azure SignalR Service per sincronizzare i messaggi attraverso le istanze del server
  • Lo streaming supporta la consegna incrementale dei dati con IAsyncEnumerable sia per scenari server-to-client che client-to-server

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Sviluppatore fullstack, fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 12 agosto 2026

Condividi

Articoli correlati