Esercitazione: Trasmissione server con ASP.NET SignalR 1.x

di Patrick Fletcher, Tom Dykstra

Avviso

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

Questa esercitazione illustra come creare un'applicazione Web che usa ASP.NET SignalR per fornire funzionalità di trasmissione server. Trasmissione server significa che le comunicazioni inviate ai client vengono avviate dal server. Questo scenario richiede un approccio di programmazione diverso rispetto agli scenari peer-to-peer, ad esempio le applicazioni di chat, in cui le comunicazioni inviate ai client vengono avviate da uno o più client.

L'applicazione che verrà creata in questa esercitazione simula un ticker azionario, uno scenario tipico per la funzionalità di trasmissione server.

I commenti sull'esercitazione sono benvenuti. Se si hanno domande che non sono direttamente correlate all'esercitazione, è possibile pubblicarle nel forum di ASP.NET SignalR o StackOverflow.com.

Panoramica

Il pacchetto NuGet Microsoft.AspNet.SignalR.Sample installa un'applicazione ticker di magazzino simulata di esempio in un progetto di Visual Studio. Nella prima parte di questa esercitazione si creerà una versione semplificata di tale applicazione da zero. Nella parte restante dell'esercitazione si installerà il pacchetto NuGet e si esamineranno le funzionalità e il codice aggiuntivi creati.

L'applicazione ticker stock è un rappresentante di un tipo di applicazione in tempo reale in cui si vuole eseguire periodicamente il push o la trasmissione delle notifiche dal server a tutti i client connessi.

L'applicazione che verrà compilata nella prima parte di questa esercitazione visualizza una griglia con dati azionari.

Versione iniziale di StockTicker

Periodicamente il server aggiorna in modo casuale i prezzi delle azioni e inserisce gli aggiornamenti a tutti i client connessi. Nel browser i numeri e i simboli nelle colonne Cambia e % cambiano dinamicamente in risposta alle notifiche dal server. Se si aprono altri browser allo stesso URL, vengono visualizzati tutti gli stessi dati e le stesse modifiche ai dati contemporaneamente.

Questa esercitazione contiene le sezioni seguenti:

Nota

Se non si vuole eseguire i passaggi della compilazione dell'applicazione, è possibile installare il pacchetto SignalR.Sample in un nuovo progetto Applicazione Web vuota ASP.NET e leggere questi passaggi per ottenere spiegazioni del codice. La prima parte dell'esercitazione illustra un subset del codice SignalR.Sample e la seconda parte illustra le funzionalità principali delle funzionalità aggiuntive nel pacchetto SignalR.Sample.

Prerequisiti

Prima di iniziare, assicurarsi di avere installato Visual Studio 2012 o 2010 SP1 nel computer. Se Visual Studio non è disponibile, vedere ASP.NET Download per ottenere la versione gratuita di Visual Studio 2012 Express for Web.

Se visual Studio 2010 è installato, assicurarsi che NuGet sia installato.

Creare il progetto

  1. Scegliere Nuovo progetto dal menu File.

  2. Nella finestra di dialogo Nuovo progetto espandere C# in Modelli e selezionare Web.

  3. Selezionare il ASP.NET modello Applicazione Web vuota , assegnare al progetto il nome SignalR.StockTicker e fare clic su OK.

    Finestra di dialogo Nuovo progetto

Aggiungere i pacchetti NuGet SignalR

Aggiungere i pacchetti NuGet SignalR e JQuery

È possibile aggiungere funzionalità SignalR a un progetto installando un pacchetto NuGet.

  1. Fare clic su Strumenti | Gestione pacchetti NuGet | Console di Gestione pacchetti.

  2. Immettere il comando seguente nella gestione pacchetti.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Il pacchetto SignalR installa diversi altri pacchetti NuGet come dipendenze. Al termine dell'installazione, tutti i componenti server e client necessari per usare SignalR in un'applicazione ASP.NET.

Configurare il codice del server

In questa sezione viene configurato il codice eseguito nel server.

Creare la classe Stock

Per iniziare, creare la classe modello Stock che verrà usata per archiviare e trasmettere informazioni su un titolo.

  1. Creare un nuovo file di classe nella cartella del progetto, denominarlo Stock.cs e quindi sostituire il codice del modello con il codice seguente:

    using System;
    
    namespace SignalR.StockTicker
    {
        public class Stock
        {
            private decimal _price;
    
            public string Symbol { get; set; }
    
            public decimal Price
            {
                get
                {
                    return _price;
                }
                set
                {
                    if (_price == value)
                    {
                        return;
                    }
    
                    _price = value;
    
                    if (DayOpen == 0)
                    {
                        DayOpen = _price;
                    }
                }
            }
    
            public decimal DayOpen { get; private set; }
    
            public decimal Change
            {
                get
                {
                    return Price - DayOpen;
                }
            }
    
            public double PercentChange
            {
                get
                {
                    return (double)Math.Round(Change / Price, 4);
                }
            }
        }
    }
    

    Le due proprietà che verranno impostate quando si creano azioni sono Symbol (ad esempio, MSFT per Microsoft) e Price. Le altre proprietà dipendono da come e quando si imposta Price. La prima volta che si imposta Price, il valore viene propagato a DayOpen. Le ore successive in cui si imposta Price, i valori delle proprietà Change e PercentChange vengono calcolati in base alla differenza tra Price e DayOpen.

Creare le classi StockTicker e StockTickerHub

Si userà l'API dell'hub SignalR per gestire l'interazione da server a client. Una classe StockTickerHub che deriva dalla classe Hub SignalR gestirà la ricezione di connessioni e chiamate di metodo dai client. È anche necessario mantenere i dati azionari ed eseguire un oggetto Timer per attivare periodicamente gli aggiornamenti dei prezzi, indipendentemente da connessioni client. Non è possibile inserire queste funzioni in una classe Hub, perché le istanze dell'hub sono temporanee. Viene creata un'istanza della classe Hub per ogni operazione nell'hub, ad esempio connessioni e chiamate dal client al server. Pertanto, il meccanismo che mantiene i dati azionari, aggiorna i prezzi e trasmette gli aggiornamenti dei prezzi deve essere eseguito in una classe separata, denominata StockTicker.

Trasmissione da StockTicker

Si vuole eseguire solo un'istanza della classe StockTicker nel server, quindi è necessario configurare un riferimento da ogni istanza di StockTickerHub all'istanza di StockTicker StockTicker. La classe StockTicker deve essere in grado di trasmettere ai client perché contiene i dati azionari e attiva gli aggiornamenti, ma StockTicker non è una classe Hub. Pertanto, la classe StockTicker deve ottenere un riferimento all'oggetto contesto di connessione dell'hub SignalR. Può quindi usare l'oggetto contesto di connessione SignalR per trasmettere ai client.

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto e scegliere Aggiungi nuovo elemento.

  2. Se Visual Studio 2012 è disponibile con l'aggiornamento ASP.NET and Web Tools 2012.2, fare clic su Web in Visual C# e selezionare il modello di elemento Classe hub SignalR. In caso contrario, selezionare il modello Classe .

  3. Assegnare alla nuova classe il nome StockTickerHub.cs e quindi fare clic su Aggiungi.

    Aggiungere StockTickerHub.cs

  4. Sostituire il codice del modello con il codice seguente:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        [HubName("stockTickerMini")]
        public class StockTickerHub : Hub
        {
            private readonly StockTicker _stockTicker;
    
            public StockTickerHub() : this(StockTicker.Instance) { }
    
            public StockTickerHub(StockTicker stockTicker)
            {
                _stockTicker = stockTicker;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stockTicker.GetAllStocks();
            }
        }
    }
    

    La classe Hub viene usata per definire i metodi che i client possono chiamare sul server. Si definisce un metodo: GetAllStocks(). Quando un client si connette inizialmente al server, chiamerà questo metodo per ottenere un elenco di tutti gli stock con i prezzi correnti. Il metodo può essere eseguito in modo sincrono e restituito IEnumerable<Stock> perché restituisce dati dalla memoria. Se il metodo deve ottenere i dati eseguendo un'operazione che comporta l'attesa, ad esempio una ricerca di database o una chiamata al servizio Web, è necessario specificare Task<IEnumerable<Stock>> come valore restituito per abilitare l'elaborazione asincrona. Per altre informazioni, vedere ASP.NET Guida api hub SignalR - Server - Quando eseguire in modo asincrono.

    L'attributo HubName specifica come verrà fatto riferimento all'hub nel codice JavaScript nel client. Il nome predefinito nel client se non si usa questo attributo è una versione con maiuscole e minuscole camel del nome della classe, che in questo caso sarà stockTickerHub.

    Come si vedrà più avanti quando si crea la classe StockTicker, viene creata un'istanza singleton di tale classe nella relativa proprietà Instance statica. L'istanza singleton di StockTicker rimane in memoria indipendentemente dal numero di client che si connettono o si disconnettono e tale istanza è l'uso del metodo GetAllStocks per restituire le informazioni sulle scorte correnti.

  5. Creare un nuovo file di classe nella cartella del progetto, denominarlo StockTicker.cs e quindi sostituire il codice del modello con il codice seguente:

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    
    namespace SignalR.StockTicker
    {
        public class StockTicker
        {
            // Singleton instance
            private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
            private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
            private readonly object _updateStockPricesLock = new object();
    
            //stock can go up or down by a percentage of this factor on each change
            private readonly double _rangePercent = .002;
    
            private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
            private readonly Random _updateOrNotRandom = new Random();
    
            private readonly Timer _timer;
            private volatile bool _updatingStockPrices = false;
    
            private StockTicker(IHubConnectionContext clients)
            {
                Clients = clients;
    
                _stocks.Clear();
                var stocks = new List<Stock>
                {
                    new Stock { Symbol = "MSFT", Price = 30.31m },
                    new Stock { Symbol = "APPL", Price = 578.18m },
                    new Stock { Symbol = "GOOG", Price = 570.30m }
                };
                stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
                _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
            }
    
            public static StockTicker Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
    
            private IHubConnectionContext Clients
            {
                get;
                set;
            }
    
            public IEnumerable<Stock> GetAllStocks()
            {
                return _stocks.Values;
            }
    
            private void UpdateStockPrices(object state)
            {
                lock (_updateStockPricesLock)
                {
                    if (!_updatingStockPrices)
                    {
                        _updatingStockPrices = true;
    
                        foreach (var stock in _stocks.Values)
                        {
                            if (TryUpdateStockPrice(stock))
                            {
                                BroadcastStockPrice(stock);
                            }
                        }
    
                        _updatingStockPrices = false;
                    }
                }
            }
    
            private bool TryUpdateStockPrice(Stock stock)
            {
                // Randomly choose whether to update this stock or not
                var r = _updateOrNotRandom.NextDouble();
                if (r > .1)
                {
                    return false;
                }
    
                // Update the stock price by a random factor of the range percent
                var random = new Random((int)Math.Floor(stock.Price));
                var percentChange = random.NextDouble() * _rangePercent;
                var pos = random.NextDouble() > .51;
                var change = Math.Round(stock.Price * (decimal)percentChange, 2);
                change = pos ? change : -change;
    
                stock.Price += change;
                return true;
            }
    
            private void BroadcastStockPrice(Stock stock)
            {
                Clients.All.updateStockPrice(stock);
            }
    
        }
    }
    

    Poiché più thread eseguono la stessa istanza del codice StockTicker, la classe StockTicker deve essere threadsafe.

    Archiviazione dell'istanza singleton in un campo statico

    Il codice inizializza il campo statico _instance che esegue il backup della proprietà Instance con un'istanza della classe e questa è l'unica istanza della classe che può essere creata, perché il costruttore è contrassegnato come privato. L'inizializzazione differita viene usata per il campo _instance, non per motivi di prestazioni, ma per garantire che la creazione dell'istanza sia threadsafe.

    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    public static StockTicker Instance
    {
        get
        {
            return _instance.Value;
        }
    }
    

    Ogni volta che un client si connette al server, una nuova istanza della classe StockTickerHub in esecuzione in un thread separato ottiene l'istanza singleton di StockTicker dalla proprietà statica StockTicker.Instance, come illustrato in precedenza nella classe StockTickerHub.

    Archiviazione dei dati azionari in un oggetto ConcurrentDictionary

    Il costruttore inizializza la raccolta _stocks con alcuni dati azionari di esempio e GetAllStocks restituisce le azioni. Come si è visto in precedenza, questa raccolta di titoli è a sua volta restituita da StockTickerHub.GetAllStocks, che è un metodo server nella classe Hub che i client possono chiamare.

    private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
    
    private StockTicker(IHubConnectionContext clients)
    {
        Clients = clients;
    
        _stocks.Clear();
        var stocks = new List<Stock>
        {
            new Stock { Symbol = "MSFT", Price = 30.31m },
            new Stock { Symbol = "APPL", Price = 578.18m },
            new Stock { Symbol = "GOOG", Price = 570.30m }
        };
        stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock));
    
        _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    }
    
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stocks.Values;
    }
    

    La raccolta delle scorte è definita come tipo ConcurrentDictionary per la sicurezza dei thread. In alternativa, è possibile usare un oggetto Dictionary e bloccare in modo esplicito il dizionario quando si apportano modifiche.

    Per questa applicazione di esempio, è OK archiviare i dati dell'applicazione in memoria e perdere i dati quando l'istanza di StockTicker viene eliminata. In un'applicazione reale è possibile usare un archivio dati back-end, ad esempio un database.

    Aggiornamento periodico dei prezzi delle scorte

    Il costruttore avvia un oggetto Timer che chiama periodicamente metodi che aggiornano i prezzi delle scorte in base casuale.

    _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
    
    private void UpdateStockPrices(object state)
    {
        lock (_updateStockPricesLock)
        {
            if (!_updatingStockPrices)
            {
                _updatingStockPrices = true;
    
                foreach (var stock in _stocks.Values)
                {
                    if (TryUpdateStockPrice(stock))
                    {
                        BroadcastStockPrice(stock);
                    }
                }
    
                _updatingStockPrices = false;
            }
        }
    }
    
    private bool TryUpdateStockPrice(Stock stock)
    {
        // Randomly choose whether to update this stock or not
        var r = _updateOrNotRandom.NextDouble();
        if (r > .1)
        {
            return false;
        }
    
        // Update the stock price by a random factor of the range percent
        var random = new Random((int)Math.Floor(stock.Price));
        var percentChange = random.NextDouble() * _rangePercent;
        var pos = random.NextDouble() > .51;
        var change = Math.Round(stock.Price * (decimal)percentChange, 2);
        change = pos ? change : -change;
    
        stock.Price += change;
        return true;
    }
    

    UpdateStockPrices viene chiamato dal timer, che passa null nel parametro di stato. Prima di aggiornare i prezzi, viene eseguito un blocco sull'oggetto _updateStockPricesLock. Il codice verifica se un altro thread sta già aggiornando i prezzi e quindi chiama TryUpdateStockPrice su ogni stock nell'elenco. Il metodo TryUpdateStockPrice decide se modificare il prezzo delle azioni e quanto modificarlo. Se il prezzo delle scorte viene modificato, BroadcastStockPrice viene chiamato per trasmettere la modifica del prezzo delle scorte a tutti i clienti connessi.

    Il flag _updatingStockPrices è contrassegnato come volatile per garantire che l'accesso a esso sia threadsafe.

    private volatile bool _updatingStockPrices = false;
    

    In un'applicazione reale, il metodo TryUpdateStockPrice chiamerebbe un servizio Web per cercare il prezzo; in questo codice usa un generatore di numeri casuali per apportare modifiche in modo casuale.

    Recupero del contesto SignalR in modo che la classe StockTicker possa trasmettere ai client

    Poiché le modifiche al prezzo provengono qui nell'oggetto StockTicker, questo è l'oggetto che deve chiamare un metodo updateStockPrice su tutti i client connessi. In una classe Hub è disponibile un'API per chiamare i metodi client, ma StockTicker non deriva dalla classe Hub e non ha un riferimento a alcun oggetto Hub. Pertanto, per trasmettere ai client connessi, la classe StockTicker deve ottenere l'istanza del contesto SignalR per la classe StockTickerHub e usarla per chiamare i metodi nei client.

    Il codice ottiene un riferimento al contesto SignalR quando crea l'istanza della classe singleton, passa tale riferimento al costruttore e il costruttore lo inserisce nella proprietà Client.

    Esistono due motivi per cui si vuole ottenere il contesto una sola volta: ottenere il contesto è un'operazione costosa e ottenere una volta assicura che l'ordine previsto di messaggi inviati ai client venga mantenuto.

    private readonly static Lazy<StockTicker> _instance =
        new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
    
    private StockTicker(IHubConnectionContext clients)
    {
        Clients = clients;
    
        // Remainder of constructor ...
    }
    
    private IHubConnectionContext Clients
    {
        get;
        set;
    }
    
    private void BroadcastStockPrice(Stock stock)
    {
        Clients.All.updateStockPrice(stock);
    }
    

    Ottenere la proprietà Client del contesto e inserirla nella proprietà StockTickerClient consente di scrivere codice per chiamare i metodi client che sembrano uguali a quello in una classe Hub. Ad esempio, per trasmettere a tutti i client è possibile scrivere Client.All.updateStockPrice(stock).

    Il metodo updateStockPrice che si sta chiamando in BroadcastStockPrice non esiste ancora; verrà aggiunto in un secondo momento quando si scrive codice in esecuzione nel client. È possibile fare riferimento a UpdateStockPrice qui perché Client.All è dinamico, il che significa che l'espressione verrà valutata in fase di esecuzione. Quando questa chiamata al metodo viene eseguita, SignalR invierà il nome del metodo e il valore del parametro al client e, se il client ha un metodo denominato updateStockPrice, tale metodo verrà chiamato e il valore del parametro verrà passato al client.

    Client.All significa inviare a tutti i client. SignalR offre altre opzioni per specificare quali client o gruppi di client inviare. Per altre informazioni, vedere HubConnectionContext.

Registrare la route SignalR

Il server deve conoscere quale URL intercettare e indirizzare a SignalR. A tale scopo, si aggiungerà un codice al file Global.asax .

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto e quindi scegliere Aggiungi nuovo elemento.

  2. Selezionare il modello di elemento classe applicazione globale e quindi fare clic su Aggiungi.

    Aggiungere global.asax

  3. Aggiungere il codice di registrazione della route SignalR al metodo Application_Start:

    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
    

    Per impostazione predefinita, l'URL di base per tutto il traffico SignalR è "/signalr" e "/signalr/hubs" viene usato per recuperare un file JavaScript generato dinamicamente che definisce i proxy per tutti gli hub presenti nell'applicazione. Il metodo MapHubs include overload che consentono di specificare un URL di base diverso e alcune opzioni SignalR in un'istanza della classe HubConfiguration .

  4. Aggiungere un'istruzione using nella parte superiore del file:

    using System.Web.Routing;
    
  5. Salvare e chiudere il file Global.asax e compilare il progetto.

La configurazione del codice del server è stata completata. Nella sezione successiva verrà configurato il client.

Configurare il codice client

  1. Creare un nuovo file HTML nella cartella del progetto e assegnarne il nomeStockTicker.html.

  2. Sostituire il codice del modello con il codice seguente:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>ASP.NET SignalR Stock Ticker</title>
        <style>
            body {
                font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
                font-size: 16px;
            }
            #stockTable table {
                border-collapse: collapse;
            }
                #stockTable table th, #stockTable table td {
                    padding: 2px 6px;
                }
                #stockTable table td {
                    text-align: right;
                }
            #stockTable .loading td {
                text-align: left;
            }
        </style>
    </head>
    <body>
        <h1>ASP.NET SignalR Stock Ticker Sample</h1>
    
        <h2>Live Stock Table</h2>
        <div id="stockTable">
            <table border="1">
                <thead>
                    <tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr>
                </thead>
                <tbody>
                    <tr class="loading"><td colspan="5">loading...</td></tr>
                </tbody>
            </table>
        </div>
    
        <!--Script references. -->
        <!--Reference the jQuery library. -->
        <script src="/Scripts/jquery-1.8.2.min.js" ></script>
        <!--Reference the SignalR library. -->
        <script src="/Scripts/jquery.signalR-1.0.1.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <!--Reference the StockTicker script. -->
        <script src="StockTicker.js"></script>
    </body>
    </html>
    

    L'HTML crea una tabella con 5 colonne, una riga di intestazione e una riga di dati con una singola cella che si estende su tutte le 5 colonne. La riga di dati visualizza "caricamento..." e verrà visualizzato solo momentaneamente all'avvio dell'applicazione. Il codice JavaScript rimuoverà tale riga e aggiungerà le righe al suo posto con i dati di magazzino recuperati dal server.

    I tag di script specificano il file di script jQuery, il file di script core SignalR, il file di script SignalR e un file di script StockTicker che verrà creato in seguito. Il file di script signalR, che specifica l'URL "/signalr/hubs", viene generato dinamicamente e definisce i metodi proxy della classe Hub, in questo caso per StockTickerHub.GetAllStocks. Se si preferisce, è possibile generare manualmente questo file JavaScript usando SignalR Utilities e disabilitare la creazione dinamica del file nella chiamata al metodo MapHubs.

  3. Importante

    Assicurarsi che i riferimenti al file JavaScript in StockTicker.html siano corretti. In questo caso, assicurarsi che la versione jQuery nel tag script (1.8.2 nell'esempio) sia uguale alla versione jQuery nella cartella Script del progetto e assicurarsi che la versione SignalR nel tag script sia uguale alla versione SignalR nella cartella Script del progetto. Modificare i nomi dei file nei tag di script, se necessario.

  4. In Esplora soluzioni fare clic con il pulsante destro del mouse suStockTicker.htmle quindi scegliere Imposta come pagina iniziale.

  5. Creare un nuovo file JavaScript nella cartella del progetto e denominarlo StockTicker.js..

  6. Sostituire il codice del modello con il codice seguente:

    // A simple templating method for replacing placeholders enclosed in curly braces.
    if (!String.prototype.supplant) {
        String.prototype.supplant = function (o) {
            return this.replace(/{([^{}]*)}/g,
                function (a, b) {
                    var r = o[b];
                    return typeof r === 'string' || typeof r === 'number' ? r : a;
                }
            );
        };
    }
    
    $(function () {
    
        var ticker = $.connection.stockTickerMini, // the generated client-side hub proxy
            up = '▲',
            down = '▼',
            $stockTable = $('#stockTable'),
            $stockTableBody = $stockTable.find('tbody'),
            rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>';
    
        function formatStock(stock) {
            return $.extend(stock, {
                Price: stock.Price.toFixed(2),
                PercentChange: (stock.PercentChange * 100).toFixed(2) + '%',
                Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down
            });
        }
    
        function init() {
            ticker.server.getAllStocks().done(function (stocks) {
                $stockTableBody.empty();
                $.each(stocks, function () {
                    var stock = formatStock(this);
                    $stockTableBody.append(rowTemplate.supplant(stock));
                });
            });
        }
    
        // Add a client-side hub method that the server will call
        ticker.client.updateStockPrice = function (stock) {
            var displayStock = formatStock(stock),
                $row = $(rowTemplate.supplant(displayStock));
    
            $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
                .replaceWith($row);
            }
    
        // Start the connection
        $.connection.hub.start().done(init);
    
    });
    

    $.connection fa riferimento ai proxy SignalR. Il codice ottiene un riferimento al proxy per la classe StockTickerHub e lo inserisce nella variabile ticker. Il nome del proxy è il nome impostato dall'attributo [HubName]:

    var ticker = $.connection.stockTickerMini
    
    [HubName("stockTickerMini")]
    public class StockTickerHub : Hub
    

    Dopo aver definito tutte le variabili e le funzioni, l'ultima riga di codice nel file inizializza la connessione SignalR chiamando la funzione start SignalR. La funzione start esegue in modo asincrono e restituisce un oggetto jQuery Deferred, che significa che è possibile chiamare la funzione completata per specificare la funzione da chiamare al termine dell'operazione asincrona.

    $.connection.hub.start().done(init);
    

    La funzione init chiama la funzione getAllStocks nel server e usa le informazioni restituite dal server per aggiornare la tabella di magazzino. Si noti che per impostazione predefinita, è necessario usare il caseing camel sul client, anche se il nome del metodo è pascal-cased nel server. La regola camel-casing si applica solo ai metodi, non agli oggetti. Ad esempio, si fa riferimento alle scorte. Simbolo e magazzino. Prezzo, non stock.symbol o stock.price.

    function init() {
        ticker.server.getAllStocks().done(function (stocks) {
            $stockTableBody.empty();
            $.each(stocks, function () {
                var stock = formatStock(this);
                $stockTableBody.append(rowTemplate.supplant(stock));
            });
        });
    }
    
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
    

    Se si vuole usare maiuscole e minuscole pascal nel client o se si vuole usare un nome di metodo completamente diverso, è possibile decorare il metodo Hub con l'attributo HubMethodName allo stesso modo in cui è stata decorata la classe Hub stessa con l'attributo HubName.

    Nel metodo init, HTML per una riga di tabella viene creato per ogni oggetto stock ricevuto dal server chiamando formatStock per formattare le proprietà dell'oggetto stock e quindi chiamando il supplente (definito nella parte superiore di StockTicker.js) per sostituire i segnaposto nella variabile rowTemplate con i valori delle proprietà dell'oggetto stock. Il codice HTML risultante viene quindi aggiunto alla tabella di magazzino.

    Si chiama init passandolo come funzione di callback che viene eseguita dopo il completamento della funzione di avvio asincrona. Se si chiama init come istruzione JavaScript separata dopo aver chiamato l'avvio, la funzione avrà esito negativo perché verrà eseguita immediatamente senza attendere che la funzione iniziale finisca la definizione della connessione. In tal caso, la funzione init tenterebbe di chiamare la funzione getAllStocks prima che venga stabilita la connessione del server.

    Quando il server modifica il prezzo di un magazzino, chiama updateStockPrice nei client connessi. La funzione viene aggiunta alla proprietà client del proxy stockTicker per renderla disponibile per le chiamate dal server.

    ticker.client.updateStockPrice = function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock));
    
        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        }
    

    La funzione updateStockPrice formatta un oggetto stock ricevuto dal server in una riga di tabella allo stesso modo della funzione init. Tuttavia, anziché aggiungere la riga alla tabella, trova la riga corrente della tabella e sostituisce la riga con quella nuova.

Test dell'applicazione

  1. Premere F5 per eseguire l'applicazione in modalità debug.

    La tabella di magazzino visualizza inizialmente il "caricamento..." riga, quindi dopo un breve ritardo vengono visualizzati i dati iniziali delle scorte e quindi i prezzi delle azioni iniziano a cambiare.

    Caricamento

    Tabella di magazzino iniziale

    Tabella stock che riceve modifiche dal server

  2. Copiare l'URL dalla barra degli indirizzi del browser e incollarlo in una o più finestre del browser.

    La visualizzazione iniziale delle scorte è la stessa del primo browser e le modifiche si verificano simultaneamente.

  3. Chiudere tutti i browser e aprire un nuovo browser, quindi passare allo stesso URL.

    L'oggetto Singleton StockTicker ha continuato a essere eseguito nel server, quindi la tabella delle scorte mostra che le scorte hanno continuato a cambiare. Non viene visualizzata la tabella iniziale con cifre di modifica zero.

  4. Chiudere il browser.

Abilitazione della registrazione

SignalR ha una funzione di registrazione predefinita che è possibile abilitare nel client per facilitare la risoluzione dei problemi. In questa sezione si abilita la registrazione e vengono visualizzati esempi che illustrano in che modo i log indicano quali dei metodi di trasporto seguenti vengono usati da SignalR:

Per qualsiasi connessione specificata, SignalR sceglie il metodo di trasporto migliore che sia il server che il supporto client.

  1. Aprire StockTicker.js e aggiungere una riga di codice per abilitare la registrazione immediatamente prima del codice che inizializza la connessione alla fine del file:

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  2. Premere F5 per eseguire il progetto.

  3. Aprire la finestra degli strumenti di sviluppo del browser e selezionare la console per visualizzare i log. Potrebbe essere necessario aggiornare la pagina per visualizzare i log di Signalr che negoziano il metodo di trasporto per una nuova connessione.

    Se si esegue Internet Explorer 10 in Windows 8 (IIS 8), il metodo di trasporto è WebSockets.

    Console iis 8 di Internet Explorer 10

    Se si esegue Internet Explorer 10 in Windows 7 (IIS 7.5), il metodo di trasporto è iframe.

    Console di Internet Explorer 10, IIS 7.5

    In Firefox installare il componente aggiuntivo Firebug per ottenere una finestra della console. Se si esegue Firefox 19 in Windows 8 (IIS 8), il metodo di trasporto è WebSockets.

    Firefox 19 IIS 8 Websocket

    Se si esegue Firefox 19 in Windows 7 (IIS 7.5), il metodo di trasporto è eventi inviati dal server.

    Firefox 19 IIS 7.5 Console

Installare ed esaminare l'esempio StockTicker completo

L'applicazione StockTicker installata dal pacchetto NuGet Microsoft.AspNet.SignalR.Sample include più funzionalità rispetto alla versione semplificata appena creata da zero. In questa sezione dell'esercitazione viene installato il pacchetto NuGet e vengono esaminate le nuove funzionalità e il codice che li implementa.

Installare il pacchetto NuGet SignalR.Sample

  1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto e scegliere Gestisci pacchetti NuGet.

  2. Nella finestra di dialogo Gestisci pacchetti NuGet fare clic su Online, immettere SignalR.Sample nella casella Cerca online e quindi fare clic su Installa nel pacchetto SignalR.Sample .

    Installare il pacchetto SignalR.Sample

  3. Nel file Global.asax impostare come commento RouteTable.Routes.MapHubs(); riga aggiunta in precedenza nel metodo Application_Start.

    Il codice in Global.asax non è più necessario perché il pacchetto SignalR.Sample registra la route SignalR nel file App_Start/RegisterHubs.cs :

    [assembly: WebActivator.PreApplicationStartMethod(typeof(SignalR.StockTicker.RegisterHubs), "Start")]
    
    namespace SignalR.StockTicker
    {
        public static class RegisterHubs
        {
            public static void Start()
            {
                // Register the default hubs route: ~/signalr/hubs
                RouteTable.Routes.MapHubs();
            }
        }
    }
    

    La classe WebActivator a cui fa riferimento l'attributo assembly è inclusa nel pacchetto NuGet WebActivatorEx, installato come dipendenza del pacchetto SignalR.Sample.

  4. In Esplora soluzioni espandere la cartella SignalR.Sample creata installando il pacchetto SignalR.Sample.

  5. Nella cartella SignalR.Sample fare clic con il pulsante destro del mouse suStockTicker.htmle quindi scegliere Imposta come pagina iniziale.

    Nota

    L'installazione del pacchetto NuGet SignalR.Sample potrebbe modificare la versione di jQuery presente nella cartella Scripts . Il nuovo file StockTicker.html installato dal pacchetto nella cartella SignalR.Sample sarà sincronizzato con la versione jQuery installata dal pacchetto, ma se si vuole eseguire nuovamente il file diStockTicker.html originale, potrebbe essere necessario aggiornare prima il riferimento jQuery nel tag script.

Eseguire l'applicazione

  1. Premere F5 per eseguire l'applicazione.

    Oltre alla griglia visualizzata in precedenza, l'applicazione ticker completa mostra una finestra di scorrimento orizzontale che visualizza gli stessi dati azionari. Quando si esegue l'applicazione per la prima volta, il "mercato" è "chiuso" e viene visualizzata una griglia statica e una finestra ticker che non scorre.

    Schermata StockTicker start

    Quando si fa clic su Open Market, la casella Live Stock Ticker inizia a scorrere orizzontalmente e il server inizia a trasmettere periodicamente le variazioni dei prezzi delle azioni in modo casuale. Ogni volta che un prezzo azionario cambia, vengono aggiornate sia la griglia Tabella azionaria live che la casella Live Stock Ticker . Quando il cambio di prezzo di un titolo è positivo, il titolo viene visualizzato con uno sfondo verde e quando la modifica è negativa, il titolo viene visualizzato con uno sfondo rosso.

    App StockTicker, mercato aperto

    Il pulsante Chiudi mercato arresta le modifiche e arresta lo scorrimento del ticker e il pulsante Reimposta reimposta tutti i dati azionari sullo stato iniziale prima dell'inizio delle modifiche al prezzo. Se si aprono più finestre del browser e si passa allo stesso URL, vengono visualizzati gli stessi dati aggiornati dinamicamente in ogni browser. Quando si fa clic su uno dei pulsanti, tutti i browser rispondono allo stesso modo contemporaneamente.

Visualizzazione di Live Stock Ticker

La visualizzazione Live Stock Ticker è un elenco non ordinato in un elemento div formattato in una singola riga in base agli stili CSS. Il ticker viene inizializzato e aggiornato allo stesso modo della tabella: sostituendo i segnaposto in una <stringa di modello li> e aggiungendo dinamicamente gli <elementi li> all'elemento <ul> . Lo scorrimento viene eseguito usando la funzione di animazione jQuery per variare il margine sinistro dell'elenco non ordinato all'interno del div.

Il codice HTML di ticker azionario:

<h2>Live Stock Ticker</h2>
<div id="stockTicker">
    <div class="inner">
        <ul>
            <li class="loading">loading...</li>
        </ul>
    </div>
</div>

Il titolo azionario CSS:

#stockTicker {
    overflow: hidden;
    width: 450px;
    height: 24px;
    border: 1px solid #999;
    }

    #stockTicker .inner {
        width: 9999px;
    }

    #stockTicker ul {
        display: inline-block;
        list-style-type: none;
        margin: 0;
        padding: 0;
    }

    #stockTicker li {
        display: inline-block;
        margin-right: 8px;   
    }

    /*<li data-symbol="{Symbol}"><span class="symbol">{Symbol}</span><span class="price">{Price}</span><span class="change">{PercentChange}</span></li>*/
    #stockTicker .symbol {
        font-weight: bold;
    }

    #stockTicker .change {
        font-style: italic;
    }

Il codice jQuery che lo rende scorrevole:

function scrollTicker() {
    var w = $stockTickerUl.width();
    $stockTickerUl.css({ marginLeft: w });
    $stockTickerUl.animate({ marginLeft: -w }, 15000, 'linear', scrollTicker);
}

Metodi aggiuntivi nel server che il client può chiamare

La classe StockTickerHub definisce quattro metodi aggiuntivi che il client può chiamare:

public string GetMarketState()
{
    return _stockTicker.MarketState.ToString();
}

public void OpenMarket()
{
    _stockTicker.OpenMarket();
}

public void CloseMarket()
{
    _stockTicker.CloseMarket();
}

public void Reset()
{
    _stockTicker.Reset();
}

OpenMarket, CloseMarket e Reset vengono chiamati in risposta ai pulsanti nella parte superiore della pagina. Illustrano il modello di un client che attiva una modifica dello stato che viene propagato immediatamente a tutti i client. Ognuno di questi metodi chiama un metodo nella classe StockTicker che influisce sulla modifica dello stato del mercato e quindi trasmette il nuovo stato.

Nella classe StockTicker lo stato del mercato viene gestito da una proprietà MarketState che restituisce un valore di enumerazione MarketState:

public MarketState MarketState
{
    get { return _marketState; }
    private set { _marketState = value; }
}

public enum MarketState
{
    Closed,
    Open
}

Ognuno dei metodi che modificano lo stato del mercato esegue questa operazione all'interno di un blocco di blocco perché la classe StockTicker deve essere threadsafe:

public void OpenMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Open)
        {
            _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
            MarketState = MarketState.Open;
            BroadcastMarketStateChange(MarketState.Open);
        }
    }
}

public void CloseMarket()
{
    lock (_marketStateLock)
    {
        if (MarketState == MarketState.Open)
        {
            if (_timer != null)
            {
                _timer.Dispose();
            }
            MarketState = MarketState.Closed;
            BroadcastMarketStateChange(MarketState.Closed);
        }
    }
}

public void Reset()
{
    lock (_marketStateLock)
    {
        if (MarketState != MarketState.Closed)
        {
            throw new InvalidOperationException("Market must be closed before it can be reset.");
        }
        LoadDefaultStocks();
        BroadcastMarketReset();
    }
}

Per assicurarsi che questo codice sia threadsafe, il campo _marketState che esegue il backup della proprietà MarketState è contrassegnato come volatile.

private volatile MarketState _marketState;

I metodi BroadcastMarketStateChange e BroadcastMarketReset sono simili al metodo BroadcastStockPrice già visto, tranne che chiamano metodi diversi definiti nel client:

private void BroadcastMarketStateChange(MarketState marketState)
{
    switch (marketState)
    {
        case MarketState.Open:
            Clients.All.marketOpened();
            break;
        case MarketState.Closed:
            Clients.All.marketClosed();
            break;
        default:
            break;
    }
}

private void BroadcastMarketReset()
{
    Clients.All.marketReset();
}

Funzioni aggiuntive nel client che il server può chiamare

La funzione updateStockPrice ora gestisce sia la griglia che la visualizzazione ticker e usa jQuery.Color per lampeggiare i colori rosso e verde.

Nuove funzioni in SignalR.StockTicker.js abilitare e disabilitare i pulsanti in base allo stato del mercato e arrestano o avviano lo scorrimento orizzontale della finestra ticker. Poiché vengono aggiunte più funzioni a ticker.client, la funzione extend jQuery viene usata per aggiungerle.

$.extend(ticker.client, {
    updateStockPrice: function (stock) {
        var displayStock = formatStock(stock),
            $row = $(rowTemplate.supplant(displayStock)),
            $li = $(liTemplate.supplant(displayStock)),
            bg = stock.LastChange === 0
                ? '255,216,0' // yellow
                : stock.LastChange > 0
                    ? '154,240,117' // green
                    : '255,148,148'; // red

        $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
            .replaceWith($row);
        $stockTickerUl.find('li[data-symbol=' + stock.Symbol + ']')
            .replaceWith($li);

        $row.flash(bg, 1000);
        $li.flash(bg, 1000);
    },

    marketOpened: function () {
        $("#open").prop("disabled", true);
        $("#close").prop("disabled", false);
        $("#reset").prop("disabled", true);
        scrollTicker();
    },

    marketClosed: function () {
        $("#open").prop("disabled", false);
        $("#close").prop("disabled", true);
        $("#reset").prop("disabled", false);
        stopTicker();
    },

    marketReset: function () {
        return init();
    }
});

Configurazione client aggiuntiva dopo aver stabilito la connessione

Dopo che il client ha stabilito la connessione, ha alcune operazioni aggiuntive da eseguire: scoprire se il mercato è aperto o chiuso per chiamare la funzione marketOpened o marketClosed appropriata e collegare le chiamate al metodo server ai pulsanti.

$.connection.hub.start()
    .pipe(init)
    .pipe(function () {
        return ticker.server.getMarketState();
    })
    .done(function (state) {
        if (state === 'Open') {
            ticker.client.marketOpened();
        } else {
            ticker.client.marketClosed();
        }

        // Wire up the buttons
        $("#open").click(function () {
            ticker.server.openMarket();
        });

        $("#close").click(function () {
            ticker.server.closeMarket();
        });

        $("#reset").click(function () {
            ticker.server.reset();
        });
    });

I metodi del server non vengono collegati ai pulsanti fino a quando non viene stabilita la connessione, in modo che il codice non possa provare a chiamare i metodi del server prima che siano disponibili.

Passaggi successivi

In questa esercitazione si è appreso come programmare un'applicazione SignalR che trasmette i messaggi dal server a tutti i client connessi, sia periodicamente che in risposta alle notifiche da qualsiasi client. Il modello di utilizzo di un'istanza singleton multithread per mantenere lo stato del server può essere usato anche negli scenari di gioco online multi-giocatore. Per un esempio, vedi il gioco ShootR basato su SignalR.

Per esercitazioni che illustrano scenari di comunicazione peer-to-peer, vedere Introduzione con SignalR e Aggiornamento in tempo reale con SignalR.

Per informazioni sui concetti di sviluppo di SignalR più avanzati, visitare i siti seguenti per il codice sorgente e le risorse di SignalR: