Guida api ASP.NET Hub SignalR - Server (C#)

di Patrick Fletcher, Tom Dykstra

Avviso

Questa documentazione non è per la versione più recente di SignalR. Esaminare ASP.NET Core SignalR.

Questo documento fornisce un'introduzione alla programmazione del lato server dell'API hub SignalR ASP.NET per SignalR versione 2, con esempi di codice che illustrano le opzioni comuni.

L'API SignalR Hubs consente di effettuare chiamate di procedura remota da un server ai client connessi e dai client al server. Nel codice del server si definiscono i metodi che possono essere chiamati dai client e si chiamano i metodi eseguiti nel client. Nel codice client si definiscono i metodi che possono essere chiamati dal server e si chiamano i metodi eseguiti nel server. SignalR si occupa automaticamente di tutte le operazioni di idraulica da client a server.

SignalR offre anche un'API di livello inferiore denominata Connessioni persistenti. Per un'introduzione a SignalR, Hub e Connessioni persistenti, vedere Introduzione a SignalR 2.

Versioni software usate in questo argomento

Versioni degli argomenti

Per informazioni sulle versioni precedenti di SignalR, vedere Versioni precedenti di SignalR.

Domande e commenti

Lasciare commenti e suggerimenti su come è piaciuta questa esercitazione e cosa è possibile migliorare nei commenti nella parte inferiore della pagina. Se si hanno domande che non sono direttamente correlate all'esercitazione, è possibile pubblicarle nel forum di ASP.NET SignalR o StackOverflow.com.

Panoramica

Questo documento contiene le seguenti sezioni:

Per la documentazione su come programmare i client, vedere le risorse seguenti:

I componenti server per SignalR 2 sono disponibili solo in .NET 4.5. I server che eseguono .NET 4.0 devono usare SignalR v1.x.

Come registrare il middleware SignalR

Per definire la route che i client useranno per connettersi all'hub, chiamare il MapSignalR metodo all'avvio dell'applicazione. MapSignalR è un metodo di estensione per la OwinExtensions classe . L'esempio seguente illustra come definire la route di Hub SignalR usando una classe di avvio OWIN.

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // Any connection or hub wire up and configuration should go here
            app.MapSignalR();
        }

    }
}

Se si aggiunge la funzionalità SignalR a un'applicazione MVC ASP.NET, assicurarsi che la route SignalR venga aggiunta prima delle altre route. Per altre informazioni, vedere Esercitazione: Introduzione con SignalR 2 e MVC 5.

The /signalr URL

Per impostazione predefinita, l'URL di route che i client useranno per connettersi all'hub è "/signalr". (Non confondere questo URL con l'URL "/signalr/hubs", che è per il file JavaScript generato automaticamente. Per altre informazioni sul proxy generato, vedere Guida all'API di SignalR Hubs - Client JavaScript - Proxy generato e cosa fa per l'utente.

Potrebbero esserci circostanze straordinarie che rendono questo URL di base non utilizzabile per SignalR; Ad esempio, si dispone di una cartella nel progetto denominato signalr e non si vuole modificare il nome. In questo caso, è possibile modificare l'URL di base, come illustrato negli esempi seguenti (sostituire "/signalr" nel codice di esempio con l'URL desiderato).

Codice server che specifica l'URL

app.MapSignalR("/signalr", new HubConfiguration());

Codice client JavaScript che specifica l'URL (con il proxy generato)

$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);

Codice client JavaScript che specifica l'URL (senza il proxy generato)

var connection = $.hubConnection("/signalr", { useDefaultPath: false });

Codice client .NET che specifica l'URL

var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);

Configurazione delle opzioni di SignalR

Gli overload del MapSignalR metodo consentono di specificare un URL personalizzato, un resolver di dipendenze personalizzato e le opzioni seguenti:

Nell'esempio seguente viene illustrato come specificare l'URL di connessione SignalR e queste opzioni in una chiamata al MapSignalR metodo . Per specificare un URL personalizzato, sostituire "/signalr" nell'esempio con l'URL che si vuole usare.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);

Come creare e usare classi hub

Per creare un hub, creare una classe che deriva da Microsoft.Aspnet.Signalr.Hub. L'esempio seguente illustra una semplice classe Hub per un'applicazione di chat.

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}

In questo esempio un client connesso può chiamare il NewContosoChatMessage metodo e, quando lo fa, i dati ricevuti vengono trasmessi a tutti i client connessi.

Durata dell'oggetto hub

Non si crea un'istanza della classe Hub o si chiamano i metodi dal proprio codice nel server; tutto ciò che viene fatto per l'utente dalla pipeline dell'hub SignalR. SignalR crea una nuova istanza della classe hub ogni volta che deve gestire un'operazione hub, ad esempio quando un client si connette, disconnette o effettua una chiamata al metodo al server.

Poiché le istanze della classe Hub sono temporanee, non è possibile usarle per mantenere lo stato da una chiamata di metodo alla successiva. Ogni volta che il server riceve una chiamata al metodo da un client, una nuova istanza della classe Hub elabora il messaggio. Per mantenere lo stato tramite più connessioni e chiamate di metodo, usare un altro metodo, ad esempio un database o una variabile statica nella classe Hub o una classe diversa che non deriva da Hub. Se i dati vengono mantenuti in memoria, usando un metodo come una variabile statica nella classe Hub, i dati verranno persi quando il dominio dell'app viene riciclato.

Se si desidera inviare messaggi ai client dal proprio codice che viene eseguito all'esterno della classe Hub, non è possibile crearne un'istanza creando un'istanza di classe hub, ma è possibile ottenere un riferimento all'oggetto context SignalR per la classe hub. Per altre informazioni, vedere Come chiamare i metodi client e gestire i gruppi dall'esterno della classe Hub più avanti in questo argomento.

Camel-casing dei nomi dell'hub nei client JavaScript

Per impostazione predefinita, i client JavaScript fanno riferimento a Hub usando una versione camel-cased del nome della classe. SignalR apporta automaticamente questa modifica in modo che il codice JavaScript possa essere conforme alle convenzioni JavaScript. L'esempio precedente viene definito contosoChatHub nel codice JavaScript.

Server

public class ContosoChatHub : Hub

Client JavaScript con proxy generato

var contosoChatHubProxy = $.connection.contosoChatHub;

Se si vuole specificare un nome diverso per i client da usare, aggiungere l'attributo HubName . Quando si usa un attributo, non esiste alcuna HubName modifica del nome nel caso camel nei client JavaScript.

Server

[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub

Client JavaScript con proxy generato

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

Più hub

È possibile definire più classi hub in un'applicazione. Quando si esegue questa operazione, la connessione è condivisa ma i gruppi sono separati:

  • Tutti i client useranno lo stesso URL per stabilire una connessione SignalR con il servizio ("/signalr" o l'URL personalizzato se specificato uno) e tale connessione viene usata per tutti gli hub definiti dal servizio.

    Non esiste alcuna differenza di prestazioni per più hub rispetto alla definizione di tutte le funzionalità dell'hub in una singola classe.

  • Tutti gli hub ottengono le stesse informazioni sulla richiesta HTTP.

    Poiché tutti gli hub condividono la stessa connessione, l'unica informazione sulla richiesta HTTP che il server ottiene è la richiesta HTTP originale che stabilisce la connessione SignalR. Se si usa la richiesta di connessione per passare informazioni dal client al server specificando una stringa di query, non è possibile fornire stringhe di query diverse a hub diversi. Tutti gli hub riceveranno le stesse informazioni.

  • Il file proxy JavaScript generato conterrà proxy per tutti gli hub in un file.

    Per informazioni sui proxy JavaScript, vedere Guida api dell'hub SignalR - Client JavaScript - Proxy generato e cosa fa per l'utente.

  • I gruppi vengono definiti all'interno di Hub.

    In SignalR è possibile definire i gruppi denominati da trasmettere ai subset dei client connessi. I gruppi vengono mantenuti separatamente per ogni hub. Ad esempio, un gruppo denominato "Administrators" includerebbe un set di client per la ContosoChatHub classe e lo stesso nome del gruppo fa riferimento a un set diverso di client per la StockTickerHub classe.

hub Strongly-Typed

Per definire un'interfaccia per i metodi hub a cui il client può fare riferimento (e abilitare Intellisense nei metodi hub), derivare l'hub da Hub<T> (introdotto in SignalR 2.1) anziché Hub:

public class StrongHub : Hub<IClient>
{
    public async Task Send(string message)
    {
        await Clients.All.NewMessage(message);
    }
}

public interface IClient
{
    Task NewMessage(string message);
}

Come definire i metodi nella classe Hub che i client possono chiamare

Per esporre un metodo nell'hub che si vuole chiamare dal client, dichiarare un metodo pubblico, come illustrato negli esempi seguenti.

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}
public class StockTickerHub : Hub
{
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
}

È possibile specificare un tipo restituito e parametri, inclusi tipi complessi e matrici, come si farebbe in qualsiasi metodo C#. Tutti i dati ricevuti nei parametri o restituiti al chiamante vengono comunicati tra il client e il server usando JSON e SignalR gestisce automaticamente l'associazione di oggetti complessi e matrici di oggetti.

Camel-casing dei nomi dei metodi nei client JavaScript

Per impostazione predefinita, i client JavaScript fanno riferimento ai metodi hub usando una versione camel-cased del nome del metodo. SignalR apporta automaticamente questa modifica in modo che il codice JavaScript possa essere conforme alle convenzioni JavaScript.

Server

public void NewContosoChatMessage(string userName, string message)

Client JavaScript con proxy generato

contosoChatHubProxy.server.newContosoChatMessage(userName, message);

Se si vuole specificare un nome diverso per i client da usare, aggiungere l'attributo HubMethodName .

Server

[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)

Client JavaScript con proxy generato

contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

Quando eseguire in modo asincrono

Se il metodo sarà a esecuzione prolungata o deve eseguire operazioni che comportano l'attesa, ad esempio una ricerca del database o una chiamata al servizio Web, effettuare il metodo Hub asincrono restituendo un oggetto Task (al posto di restituito) o Task<T> (al posto del Tvoid tipo restituito). Quando si restituisce un Task oggetto dal metodo, SignalR attende Task il completamento dell'oggetto e quindi invia nuovamente il risultato non elaborato al client, pertanto non esiste alcuna differenza nel modo in cui si codifica la chiamata al metodo nel client.

Rendere asincrono un metodo hub evita di bloccare la connessione quando usa il trasporto WebSocket. Quando un metodo hub viene eseguito in modo sincrono e il trasporto è WebSocket, le chiamate successive dei metodi nell'hub dello stesso client vengono bloccate fino al completamento del metodo Hub.

L'esempio seguente mostra lo stesso metodo codificato per eseguire in modo sincrono o asincrono, seguito dal codice client JavaScript che funziona per chiamare una o più versioni.

Sincrono

public IEnumerable<Stock> GetAllStocks()
{
    // Returns data from memory.
    return _stockTicker.GetAllStocks();
}

Asincrona

public async Task<IEnumerable<Stock>> GetAllStocks()
{
    // Returns data from a web service.
    var uri = Util.getServiceUri("Stocks");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
    }
}

Client JavaScript con proxy generato

stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
    $.each(stocks, function () {
        alert(this.Symbol + ' ' + this.Price);
    });
});

Per altre informazioni su come usare metodi asincroni in ASP.NET 4.5, vedere Uso di metodi asincroni in ASP.NET MVC 4.

Definizione degli overload

Se si desidera definire overload per un metodo, il numero di parametri in ogni overload deve essere diverso. Se si differenzia un overload solo specificando tipi di parametri diversi, la classe hub verrà compilata ma il servizio SignalR genererà un'eccezione in fase di esecuzione quando i client tentano di chiamare uno degli overload.

Segnalazione dello stato di avanzamento dalle chiamate al metodo hub

SignalR 2.1 aggiunge il supporto per il modello di report dello stato introdotto in .NET 4.5. Per implementare la creazione di report sullo stato, definire un IProgress<T> parametro per il metodo hub a cui è possibile accedere il client:

public class ProgressHub : Hub
{
    public async Task<string> DoLongRunningThing(IProgress<int> progress)
    {
        for (int i = 0; i <= 100; i+=5)
        {
            await Task.Delay(200);
            progress.Report(i);
        }
        return "Job complete!";
    }
}

Quando si scrive un metodo server a esecuzione prolungata, è importante usare un modello di programmazione asincrona come Async/Await anziché bloccare il thread hub.

Come chiamare i metodi client dalla classe Hub

Per chiamare i metodi client dal server, usare la Clients proprietà in un metodo nella classe Hub. Nell'esempio seguente viene illustrato il codice del server che chiama addNewMessageToPage tutti i client connessi e il codice client che definisce il metodo in un client JavaScript.

Server

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}

Richiamare un metodo client è un'operazione asincrona e restituisce un Taskoggetto . Usare await:

  • Per assicurarsi che il messaggio venga inviato senza errore.
  • Per abilitare l'rilevamento e la gestione degli errori in un blocco try-catch.

Client JavaScript con proxy generato

contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + htmlEncode(name)
        + '</strong>: ' + htmlEncode(message) + '<li>');
};

Non è possibile ottenere un valore restituito da un metodo client; sintassi come int x = Clients.All.add(1,1) non funziona.

È possibile specificare tipi complessi e matrici per i parametri. Nell'esempio seguente viene passato un tipo complesso al client in un parametro del metodo.

Codice server che chiama un metodo client usando un oggetto complesso

public async Task SendMessage(string name, string message)
{
    await Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}

Codice server che definisce l'oggetto complesso

public class ContosoChatMessage
{
    public string UserName { get; set; }
    public string Message { get; set; }
}

Client JavaScript con proxy generato

var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
    console.log(message.UserName + ' ' + message.Message);
});

Selezione dei client che riceveranno RPC

La proprietà Client restituisce un oggetto HubConnectionContext che fornisce diverse opzioni per specificare i client che riceveranno il codice RPC:

  • Tutti i client connessi.

    Clients.All.addContosoChatMessageToPage(name, message);
    
  • Solo il client chiamante.

    Clients.Caller.addContosoChatMessageToPage(name, message);
    
  • Tutti i client tranne il client chiamante.

    Clients.Others.addContosoChatMessageToPage(name, message);
    
  • Un client specifico identificato dall'ID connessione.

    Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
    

    Questo esempio chiama addContosoChatMessageToPage il client chiamante e ha lo stesso effetto dell'uso di Clients.Caller.

  • Tutti i client connessi, ad eccezione dei client specificati, identificati dall'ID connessione.

    Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi in un gruppo specificato.

    Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi in un gruppo specificato, ad eccezione dei client specificati, identificati dall'ID connessione.

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi in un gruppo specificato, ad eccezione del client chiamante.

    Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);
    
  • Un utente specifico, identificato da userId.

    Clients.User(userid).addContosoChatMessageToPage(name, message);
    

    Per impostazione predefinita, questa operazione IPrincipal.Identity.Namepuò essere modificata registrando un'implementazione di IUserIdProvider con l'host globale.

  • Tutti i client e i gruppi in un elenco di ID connessione.

    Clients.Clients(ConnectionIds).broadcastMessage(name, message);
    
  • Elenco di gruppi.

    Clients.Groups(GroupIds).broadcastMessage(name, message);
    
  • Un utente per nome.

    Clients.Client(username).broadcastMessage(name, message);
    
  • Elenco dei nomi utente (introdotti in SignalR 2.1).

    Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
    

Nessuna convalida in fase di compilazione per i nomi dei metodi

Il nome del metodo specificato viene interpretato come oggetto dinamico, il che significa che non esiste alcuna convalida IntelliSense o in fase di compilazione. L'espressione viene valutata in fase di esecuzione. Quando viene eseguita la chiamata al metodo, SignalR invia il nome del metodo e i valori dei parametri al client e, se il client ha un metodo che corrisponde al nome, tale metodo viene chiamato e i valori dei parametri vengono passati. Se nel client non viene trovato alcun metodo corrispondente, non viene generato alcun errore. Per informazioni sul formato dei dati trasmessi da SignalR al client in background quando si chiama un metodo client, vedere Introduzione a SignalR.

Corrispondenza del nome del metodo senza distinzione tra maiuscole e minuscole

La corrispondenza dei nomi dei metodi non fa distinzione tra maiuscole e minuscole. Ad esempio, Clients.All.addContosoChatMessageToPage nel server verrà eseguito AddContosoChatMessageToPage, addcontosochatmessagetopageo addContosoChatMessageToPage nel client.

Esecuzione asincrona

Il metodo chiamato viene eseguito in modo asincrono. Qualsiasi codice riportato dopo una chiamata al metodo a un client verrà eseguito immediatamente senza attendere che SignalR finisca di trasmettere i dati ai client, a meno che non si specifichi che le righe di codice successive devono attendere il completamento del metodo. Nell'esempio di codice seguente viene illustrato come eseguire due metodi client in sequenza.

Uso di Await (.NET 4.5)

public async Task NewContosoChatMessage(string name, string message)
{
    await Clients.Others.addContosoChatMessageToPage(data);
    await Clients.Caller.notifyMessageSent();
}

Se si usa await per attendere il completamento di un metodo client prima dell'esecuzione della riga di codice successiva, ciò non significa che i client riceveranno effettivamente il messaggio prima dell'esecuzione della riga di codice successiva. "Completamento" di una chiamata al metodo client significa solo che SignalR ha eseguito tutto il necessario per inviare il messaggio. Se è necessaria la verifica che i client abbiano ricevuto il messaggio, è necessario programmare manualmente tale meccanismo. Ad esempio, è possibile scrivere il codice di un MessageReceived metodo nell'hub e nel addContosoChatMessageToPage metodo nel client che è possibile chiamare MessageReceived dopo aver eseguito qualsiasi operazione da eseguire nel client. Nell'hub MessageReceived è possibile eseguire qualsiasi operazione dipende dalla ricezione effettiva del client e dall'elaborazione della chiamata al metodo originale.

Come usare una variabile stringa come nome del metodo

Per richiamare un metodo client usando una variabile stringa come nome del metodo, eseguire il cast Clients.All (o Clients.Others, Clients.Callere così via) a IClientProxy e quindi chiamare Invoke(methodName, args...).

public async Task NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    await proxy.Invoke(methodToCall, name, message);
}

Come gestire l'appartenenza ai gruppi dalla classe Hub

I gruppi in SignalR forniscono un metodo per la trasmissione di messaggi ai subset specificati di client connessi. Un gruppo può avere un numero qualsiasi di client e un client può essere membro di un numero qualsiasi di gruppi.

Per gestire l'appartenenza ai gruppi, usare i metodi Add e Remove forniti dalla Groups proprietà della classe Hub. L'esempio seguente illustra i Groups.Add metodi e Groups.Remove usati nei metodi Hub chiamati dal codice client, seguiti dal codice client JavaScript che li chiama.

Server

public class ContosoChatHub : Hub
{
    public Task JoinGroup(string groupName)
    {
        return Groups.Add(Context.ConnectionId, groupName);
    }

    public Task LeaveGroup(string groupName)
    {
        return Groups.Remove(Context.ConnectionId, groupName);
    }
}

Client JavaScript con proxy generato

contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);

Non è necessario creare in modo esplicito i gruppi. In effetti, un gruppo viene creato automaticamente la prima volta che si specifica il nome in una chiamata a Groups.Adde viene eliminato quando si rimuove l'ultima connessione dall'appartenenza.

Non esiste alcuna API per ottenere un elenco di appartenenze a gruppi o un elenco di gruppi. SignalR invia messaggi a client e gruppi basati su un modello pub/sub e il server non gestisce elenchi di gruppi o appartenenze a gruppi. Ciò consente di ottimizzare la scalabilità, perché ogni volta che si aggiunge un nodo a una Web farm, qualsiasi stato gestito da SignalR deve essere propagato al nuovo nodo.

Esecuzione asincrona dei metodi Add e Remove

I Groups.Add metodi e Groups.Remove vengono eseguiti in modo asincrono. Se si vuole aggiungere un client a un gruppo e inviare immediatamente un messaggio al client usando il gruppo, è necessario assicurarsi che il Groups.Add metodo venga completato per primo. Nell'esempio di codice seguente viene illustrato come eseguire questa operazione.

Aggiunta di un client a un gruppo e quindi messaggistica del client

public async Task JoinGroup(string groupName)
{
    await Groups.Add(Context.ConnectionId, groupName);
    await Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}

Persistenza dell'appartenenza a gruppi

SignalR tiene traccia delle connessioni, non degli utenti, quindi se si vuole che un utente si trovi nello stesso gruppo ogni volta che l'utente stabilisce una connessione, è necessario chiamare Groups.Add ogni volta che l'utente stabilisce una nuova connessione.

Dopo una perdita temporanea di connettività, a volte SignalR può ripristinare automaticamente la connessione. In tal caso, SignalR ripristina la stessa connessione, non stabilisce una nuova connessione e quindi viene ripristinata automaticamente l'appartenenza al gruppo del client. Ciò è possibile anche quando l'interruzione temporanea è il risultato di un riavvio o di un errore del server, perché lo stato della connessione per ogni client, incluse le appartenenze ai gruppi, viene eseguito il round-tripped al client. Se un server si arresta e viene sostituito da un nuovo server prima del timeout della connessione, un client può riconnettersi automaticamente al nuovo server e registrarlo nuovamente nei gruppi di cui è membro.

Quando una connessione non può essere ripristinata automaticamente dopo una perdita di connettività o quando si verifica il timeout della connessione o quando il client si disconnette (ad esempio, quando un browser passa a una nuova pagina), le appartenenze ai gruppi vengono perse. La volta successiva che l'utente si connette sarà una nuova connessione. Per mantenere le appartenenze ai gruppi quando lo stesso utente stabilisce una nuova connessione, l'applicazione deve tenere traccia delle associazioni tra utenti e gruppi e ripristinare le appartenenze ai gruppi ogni volta che un utente stabilisce una nuova connessione.

Per altre informazioni sulle connessioni e le riconnessioni, vedere Come gestire gli eventi di durata della connessione nella classe Hub più avanti in questo argomento.

Gruppi di utenti singoli

Le applicazioni che usano SignalR in genere devono tenere traccia delle associazioni tra utenti e connessioni per sapere quale utente ha inviato un messaggio e quali utenti devono ricevere un messaggio. I gruppi vengono usati in uno dei due modelli comunemente usati per eseguire questa operazione.

  • Gruppi di utenti singoli.

    È possibile specificare il nome utente come nome del gruppo e aggiungere l'ID connessione corrente al gruppo ogni volta che l'utente si connette o si riconnette. Per inviare messaggi all'utente inviato al gruppo. Uno svantaggio di questo metodo è che il gruppo non fornisce un modo per scoprire se l'utente è online o offline.

  • Tenere traccia delle associazioni tra i nomi utente e gli ID di connessione.

    È possibile archiviare un'associazione tra ogni nome utente e uno o più ID di connessione in un dizionario o un database e aggiornare i dati archiviati ogni volta che l'utente si connette o si disconnette. Per inviare messaggi all'utente, specificare gli ID di connessione. Uno svantaggio di questo metodo è che richiede più memoria.

Come gestire gli eventi di durata della connessione nella classe Hub

I motivi tipici per la gestione degli eventi di durata della connessione sono tenere traccia del fatto che un utente sia connesso o meno e tenere traccia dell'associazione tra i nomi utente e gli ID connessione. Per eseguire codice personalizzato quando i client si connettono o si disconnettono, eseguire l'override dei OnConnectedmetodi virtuali , OnDisconnectede OnReconnected della classe Hub, come illustrato nell'esempio seguente.

public class ContosoChatHub : Hub
{
    public override Task OnConnected()
    {
        // Add your own code here.
        // For example: in a chat application, record the association between
        // the current connection ID and user name, and mark the user as online.
        // After the code in this method completes, the client is informed that
        // the connection is established; for example, in a JavaScript client,
        // the start().done callback is executed.
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        // Add your own code here.
        // For example: in a chat application, mark the user as offline, 
        // delete the association between the current connection id and user name.
        return base.OnDisconnected(stopCalled);
    }

    public override Task OnReconnected()
    {
        // Add your own code here.
        // For example: in a chat application, you might have marked the
        // user as offline after a period of inactivity; in that case 
        // mark the user as online again.
        return base.OnReconnected();
    }
}

Quando OnConnected, OnDisconnected e OnReconnected vengono chiamati

Ogni volta che un browser passa a una nuova pagina, deve essere stabilita una nuova connessione, il che significa che SignalR eseguirà il OnDisconnected metodo seguito dal OnConnected metodo . SignalR crea sempre un nuovo ID di connessione quando viene stabilita una nuova connessione.

Il OnReconnected metodo viene chiamato quando si è verificata un'interruzione temporanea della connettività da cui SignalR può eseguire automaticamente il ripristino, ad esempio quando un cavo viene temporaneamente disconnesso e riconnesso prima del timeout della connessione. Il OnDisconnected metodo viene chiamato quando il client viene disconnesso e SignalR non può riconnettersi automaticamente, ad esempio quando un browser passa a una nuova pagina. Pertanto, una possibile sequenza di eventi per un determinato client è OnConnected, , OnReconnectedOnDisconnected; o . OnDisconnectedOnConnected La sequenza OnConnected, OnDisconnected, OnReconnected non verrà visualizzata per una determinata connessione.

Il OnDisconnected metodo non viene chiamato in alcuni scenari, ad esempio quando un server si arresta o il dominio app viene riciclato. Quando un altro server viene attivato in linea o il dominio app completa il riciclo, alcuni client potrebbero essere in grado di riconnettersi e generare l'evento OnReconnected .

Per altre informazioni, vedere Understanding and Handling Connection Lifetime Events in SignalR.For more information, see Understanding and Handling Connection Lifetime Events in SignalR.

Stato del chiamante non popolato

I metodi del gestore eventi della durata della connessione vengono chiamati dal server, il che significa che qualsiasi stato inserito nell'oggetto state nel client non verrà popolato nella Caller proprietà nel server. Per informazioni sull'oggetto state e sulla Caller proprietà, vedere Come passare lo stato tra i client e la classe Hub più avanti in questo argomento.

Come ottenere informazioni sul client dalla proprietà Context

Per ottenere informazioni sul client, usare la Context proprietà della classe Hub. La Context proprietà restituisce un oggetto HubCallerContext che fornisce l'accesso alle informazioni seguenti:

  • ID connessione del client chiamante.

    string connectionID = Context.ConnectionId;
    

    L'ID connessione è un GUID assegnato da SignalR (non è possibile specificare il valore nel proprio codice). Esiste un ID di connessione per ogni connessione e lo stesso ID di connessione viene usato da tutti gli hub se nell'applicazione sono presenti più hub.

  • Dati di intestazione HTTP.

    System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
    

    È anche possibile ottenere intestazioni HTTP da Context.Headers. Il motivo di più riferimenti alla stessa cosa è che Context.Headers è stato creato per primo, la Context.Request proprietà è stata aggiunta in un secondo momento ed Context.Headers è stata mantenuta per la compatibilità con le versioni precedenti.

  • Eseguire query sui dati della stringa.

    System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString;
    string parameterValue = queryString["parametername"]
    

    È anche possibile ottenere dati di stringa di query da Context.QueryString.

    La stringa di query che si ottiene in questa proprietà è quella usata con la richiesta HTTP che ha stabilito la connessione SignalR. È possibile aggiungere parametri di stringa di query nel client configurando la connessione, che è un modo pratico per passare i dati sul client dal client al server. Nell'esempio seguente viene illustrato un modo per aggiungere una stringa di query in un client JavaScript quando si usa il proxy generato.

    $.connection.hub.qs = { "version" : "1.0" };
    

    Per altre informazioni sull'impostazione dei parametri della stringa di query, vedere le guide API per i client JavaScript e .NET .

    È possibile trovare il metodo di trasporto usato per la connessione nei dati della stringa di query, insieme ad altri valori usati internamente da SignalR:

    string transportMethod = queryString["transport"];
    

    Il valore di transportMethod sarà "webSockets", "serverSentEvents", "foreverFrame" o "longPolling". Si noti che se si controlla questo valore nel metodo del OnConnected gestore eventi, in alcuni scenari è possibile ottenere inizialmente un valore di trasporto che non è il metodo di trasporto negoziato finale per la connessione. In tal caso, il metodo genererà un'eccezione e verrà chiamato di nuovo in un secondo momento quando viene stabilito il metodo di trasporto finale.

  • Biscotti.

    System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
    

    È anche possibile ottenere i cookie da Context.RequestCookies.

  • informazioni utente.

    System.Security.Principal.IPrincipal user = Context.User;
    
  • Oggetto HttpContext per la richiesta :

    System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
    

    Usare questo metodo invece di ottenere HttpContext.Current l'oggetto HttpContext per la connessione SignalR.

Come passare lo stato tra i client e la classe Hub

Il proxy client fornisce un state oggetto in cui è possibile archiviare i dati da trasmettere al server con ogni chiamata al metodo. Nel server è possibile accedere a questi dati nella Clients.Caller proprietà nei metodi hub chiamati dai client. La Clients.Caller proprietà non viene popolata per i metodi OnConnecteddel gestore eventi della durata della connessione , OnDisconnectede OnReconnected.

La creazione o l'aggiornamento dei dati nell'oggetto state e la Clients.Caller proprietà funziona in entrambe le direzioni. È possibile aggiornare i valori nel server e vengono passati al client.

Nell'esempio seguente viene illustrato il codice client JavaScript che archivia lo stato per la trasmissione al server con ogni chiamata al metodo.

contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";

Nell'esempio seguente viene illustrato il codice equivalente in un client .NET.

contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";

Nella classe Hub è possibile accedere a questi dati nella Clients.Caller proprietà. Nell'esempio seguente viene illustrato il codice che recupera lo stato indicato nell'esempio precedente.

public async Task NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    await Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}

Nota

Questo meccanismo per la conservazione dello stato non è destinato a grandi quantità di dati, poiché tutto ciò che si inserisce nella state proprietà o Clients.Caller è arrotondato con ogni chiamata al metodo. È utile per elementi più piccoli, ad esempio nomi utente o contatori.

In VB.NET o in un hub fortemente tipizzato, l'oggetto stato del chiamante non può essere accessibile tramite Clients.Caller; invece, usare Clients.CallerState (introdotto in SignalR 2.1):

Uso di CallerState in C#

public async Task NewContosoChatMessage(string data)
{
    string userName = Clients.CallerState.userName;
    string computerName = Clients.CallerState.computerName;
    await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
}

Uso di CallerState in Visual Basic

Public Async Function NewContosoChatMessage(message As String) As Task
    Dim userName As String = Clients.CallerState.userName
    Dim computerName As String = Clients.CallerState.computerName
    Await Clients.Others.addContosoChatMessageToPage(message, userName, computerName)
End Sub

Come gestire gli errori nella classe Hub

Per gestire gli errori che si verificano nei metodi della classe hub, assicurarsi innanzitutto di "osservare" eventuali eccezioni dalle operazioni asincrone (ad esempio richiamando i metodi client) usando await. Usare quindi uno o più dei metodi seguenti:

  • Eseguire il wrapping del codice del metodo in blocchi try-catch e registrare l'oggetto eccezione. Ai fini del debug è possibile inviare l'eccezione al client, ma per motivi di sicurezza l'invio di informazioni dettagliate ai client in produzione non è consigliato.

  • Creare un modulo della pipeline hub che gestisce il metodo OnIncomingError . Nell'esempio seguente viene illustrato un modulo della pipeline che registra gli errori, seguito dal codice in Startup.cs che inserisce il modulo nella pipeline hub.

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
        {
            Debug.WriteLine("=> Exception " + exceptionContext.Error.Message);
            if (exceptionContext.Error.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
            }
            base.OnIncomingError(exceptionContext, invokerContext);
    
        }
    }
    
    public void Configuration(IAppBuilder app)
    {
        // Any connection or hub wire up and configuration should go here
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        app.MapSignalR();
    }
    
  • Usare la HubException classe (introdotta in SignalR 2). Questo errore può essere generato da qualsiasi chiamata dell'hub. Il costruttore accetta un messaggio stringa e un oggetto per l'archiviazione HubError di dati di errore aggiuntivi. SignalR serializzerà automaticamente l'eccezione e la invierà al client, in cui verrà usata per rifiutare o non riuscire la chiamata al metodo hub.

    Gli esempi di codice seguenti illustrano come generare un HubException oggetto durante una chiamata hub e come gestire l'eccezione nei client JavaScript e .NET.

    Codice del server che illustra la classe HubException

    public class MyHub : Hub
    {
        public async Task Send(string message)
        {
            if(message.Contains("<script>"))
            {
                throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
            }
    
            await Clients.All.send(message);
        }
    }
    

    Codice client JavaScript che illustra la risposta alla generazione di un hubException in un hub

    myHub.server.send("<script>")
                .fail(function (e) {
                    if (e.source === 'HubException') {
                        console.log(e.message + ' : ' + e.data.user);
                    }
                });
    

    Codice client .NET che illustra la risposta alla generazione di un hubException in un hub

    try
    {
        await myHub.Invoke("Send", "<script>");
    }
    catch(HubException ex)
    {
        Console.WriteLine(ex.Message);
    }
    

Per altre informazioni sui moduli della pipeline hub, vedere Come personalizzare la pipeline hub più avanti in questo argomento.

Come abilitare la traccia

Per abilitare la traccia lato server, aggiungere un elemento system.diagnostics al file Web.config, come illustrato in questo esempio:

<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit https://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="SignalRSamples" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;" />
    <add name="SignalRSamplesWithMARS" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;MultipleActiveResultSets=true;" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <system.diagnostics>
    <sources>
      <source name="SignalR.SqlMessageBus">
        <listeners>
          <add name="SignalR-Bus" />
        </listeners>
      </source>
     <source name="SignalR.ServiceBusMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
     </source>
     <source name="SignalR.ScaleoutMessageBus">
        <listeners>
            <add name="SignalR-Bus" />
        </listeners>
      </source>
      <source name="SignalR.Transports.WebSocketTransport">
        <listeners>
          <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.ServerSentEventsTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.ForeverFrameTransport">
          <listeners>
              <add name="SignalR-Transports" />
          </listeners>
      </source>
      <source name="SignalR.Transports.LongPollingTransport">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
      <source name="SignalR.Transports.TransportHeartBeat">
        <listeners>
            <add name="SignalR-Transports" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SignalRSwitch" value="Verbose" />
    </switches>
    <sharedListeners>
      <add name="SignalR-Transports" 
           type="System.Diagnostics.TextWriterTraceListener" 
           initializeData="transports.log.txt" />
        <add name="SignalR-Bus"
           type="System.Diagnostics.TextWriterTraceListener"
           initializeData="bus.log.txt" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
  </entityFramework>
</configuration>

Quando si esegue l'applicazione in Visual Studio, è possibile visualizzare i log nella finestra Output .

Come chiamare i metodi client e gestire i gruppi dall'esterno della classe Hub

Per chiamare i metodi client da una classe diversa dalla classe Hub, ottenere un riferimento all'oggetto contesto SignalR per l'hub e usarlo per chiamare i metodi nel client o gestire i gruppi.

La classe di esempio StockTicker seguente ottiene l'oggetto context, la archivia in un'istanza della classe, archivia l'istanza della classe in una proprietà statica e usa il contesto dall'istanza della classe singleton per chiamare il updateStockPrice metodo nei client connessi a un hub denominato StockTickerHub.

// For the complete example, go to 
// http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
    // Singleton instance
    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
        () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));

    private IHubContext _context;

    private StockTicker(IHubContext context)
    {
        _context = context;
    }

    // This method is invoked by a Timer object.
    private void UpdateStockPrices(object state)
    {
        foreach (var stock in _stocks.Values)
        {
            if (TryUpdateStockPrice(stock))
            {
                _context.Clients.All.updateStockPrice(stock);
            }
        }
    }

Se è necessario usare il contesto più volte in un oggetto di lunga durata, ottenere il riferimento una sola volta e salvarlo invece di recuperarlo di nuovo ogni volta. Ottenere il contesto una volta assicura che SignalR invii messaggi ai client nella stessa sequenza in cui i metodi hub fanno chiamate al metodo client. Per un'esercitazione che illustra come usare il contesto SignalR per un hub, vedere Trasmissione server con ASP.NET SignalR.

Chiamata dei metodi client

È possibile specificare quali client riceveranno RPC, ma sono disponibili meno opzioni rispetto a quando si chiama da una classe Hub. Il motivo è che il contesto non è associato a una chiamata specifica da un client, pertanto tutti i metodi che richiedono conoscenza dell'ID connessione corrente, ad esempio Clients.Others, o Clients.Callero Clients.OthersInGroup, non sono disponibili. Sono disponibili le opzioni seguenti:

  • Tutti i client connessi.

    context.Clients.All.addContosoChatMessageToPage(name, message);
    
  • Un client specifico identificato dall'ID connessione.

    context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi, ad eccezione dei client specificati, identificati dall'ID connessione.

    context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi in un gruppo specificato.

    context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
    
  • Tutti i client connessi in un gruppo specificato, ad eccezione dei client specificati, identificati dall'ID connessione.

    Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
    

Se si sta chiamando nella classe non hub dai metodi nella classe Hub, è possibile passare l'ID connessione corrente e usarlo con Clients.Client, o per simulare Clients.Caller, Clients.Otherso Clients.GroupClients.OthersInGroup. Clients.AllExcept Nell'esempio seguente la MoveShapeHub classe passa l'ID connessione alla Broadcaster classe in modo che la Broadcaster classe possa simulare Clients.Others.

// For the complete example, see
// http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
{
    // Code not shown puts a singleton instance of Broadcaster in this variable.
    private Broadcaster _broadcaster;

    public void UpdateModel(ShapeModel clientModel)
    {
        clientModel.LastUpdatedBy = Context.ConnectionId;
        // Update the shape model within our broadcaster
        _broadcaster.UpdateShape(clientModel);
    }
}

public class Broadcaster
{
    public Broadcaster()
    {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    }

    public void UpdateShape(ShapeModel clientModel)
    {
        _model = clientModel;
        _modelUpdated = true;
    }

    // Called by a Timer object.
    public void BroadcastShape(object state)
    {
        if (_modelUpdated)
        {
            _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
            _modelUpdated = false;
        }
    }
}

Gestione dell'appartenenza a gruppi

Per la gestione dei gruppi sono disponibili le stesse opzioni eseguite in una classe Hub.

  • Aggiungere un client a un gruppo

    context.Groups.Add(connectionID, groupName);
    
  • Rimuovere un client da un gruppo

    context.Groups.Remove(connectionID, groupName);
    

Come personalizzare la pipeline hub

SignalR consente di inserire il proprio codice nella pipeline dell'hub. Nell'esempio seguente viene illustrato un modulo della pipeline hub personalizzato che registra ogni chiamata al metodo in ingresso ricevuta dal client e dalla chiamata al metodo in uscita richiamata nel client:

public class LoggingPipelineModule : HubPipelineModule 
{ 
    protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context) 
    { 
        Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name); 
        return base.OnBeforeIncoming(context); 
    }   
    protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context) 
    { 
        Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub); 
        return base.OnBeforeOutgoing(context); 
    } 
}

Il codice seguente nel file Startup.cs registra il modulo da eseguire nella pipeline hub:

public void Configuration(IAppBuilder app) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    app.MapSignalR();
}

Esistono molti metodi diversi che è possibile eseguire l'override. Per un elenco completo, vedere Metodi HubPipelineModule.