Share via


Tutoriel : Diffusion par le serveur avec ASP.NET SignalR 1.x

par Patrick Fletcher, Tom Dykstra

Avertissement

Cette documentation ne concerne pas la dernière version de SignalR. Jetez un coup d’œil à ASP.NET Core SignalR.

Ce tutoriel montre comment créer une application web qui utilise ASP.NET SignalR pour fournir des fonctionnalités de diffusion de serveur. La diffusion du serveur signifie que les communications envoyées aux clients sont lancées par le serveur. Ce scénario nécessite une approche de programmation différente de celle des scénarios d’égal à égal comme les applications de conversation, dans lesquels les communications envoyées aux clients sont initiées par un ou plusieurs des clients.

L’application que vous allez créer dans ce tutoriel simule un ticker boursier, un scénario classique pour la fonctionnalité de diffusion de serveur.

Les commentaires sur le tutoriel sont les bienvenus. Si vous avez des questions qui ne sont pas directement liées au tutoriel, vous pouvez les publier sur le forum ASP.NET SignalR ou StackOverflow.com.

Vue d’ensemble

Le package NuGet Microsoft.AspNet.SignalR.Sample installe un exemple d’application de ticker de stock simulé dans un projet Visual Studio. Dans la première partie de ce tutoriel, vous allez créer une version simplifiée de cette application à partir de zéro. Dans le reste du tutoriel, vous allez installer le package NuGet et passer en revue les fonctionnalités et le code supplémentaires qu’il crée.

L’application stock ticker est un représentant d’une sorte d’application en temps réel dans laquelle vous souhaitez régulièrement envoyer (push) ou diffuser des notifications à partir du serveur à tous les clients connectés.

L’application que vous allez générer dans la première partie de ce tutoriel affiche une grille avec des données boursières.

Version initiale de StockTicker

Régulièrement, le serveur met à jour de manière aléatoire les cours des actions et envoie les mises à jour à tous les clients connectés. Dans le navigateur, les nombres et les symboles dans changent et % les colonnes changent dynamiquement en réponse aux notifications du serveur. Si vous ouvrez des navigateurs supplémentaires à la même URL, ils affichent tous les mêmes données et les mêmes modifications apportées aux données simultanément.

Ce didacticiel contient les sections suivantes :

Notes

Si vous ne souhaitez pas suivre les étapes de création de l’application, vous pouvez installer le package SignalR.Sample dans un nouveau projet d’application web vide ASP.NET et lire ces étapes pour obtenir des explications sur le code. La première partie du didacticiel couvre un sous-ensemble du code SignalR.Sample, et la deuxième partie explique les principales fonctionnalités des fonctionnalités supplémentaires du package SignalR.Sample.

Prérequis

Avant de commencer, vérifiez que Visual Studio 2012 ou 2010 SP1 est installé sur votre ordinateur. Si vous n’avez pas Visual Studio, consultez téléchargements ASP.NET pour obtenir gratuitement Visual Studio 2012 Express pour le web.

Si vous disposez de Visual Studio 2010, assurez-vous que NuGet est installé.

Créer le projet

  1. Dans le menu Fichier , cliquez sur Nouveau projet.

  2. Dans la boîte de dialogue Nouveau projet , développez C# sous Modèles , puis sélectionnez Web.

  3. Sélectionnez le ASP.NET modèle Application web vide , nommez le projet SignalR.StockTicker, puis cliquez sur OK.

    Boîte de dialogue Nouveau projet

Ajouter les packages NuGet SignalR

Ajouter les packages NuGet SignalR et JQuery

Vous pouvez ajouter la fonctionnalité SignalR à un projet en installant un package NuGet.

  1. Cliquez sur Outils | Gestionnaire de package NuGet | Console du Gestionnaire de package.

  2. Entrez la commande suivante dans le gestionnaire de package.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Le package SignalR installe un certain nombre d’autres packages NuGet en tant que dépendances. Une fois l’installation terminée, vous disposez de tous les composants serveur et client nécessaires pour utiliser SignalR dans une application ASP.NET.

Configurer le code du serveur

Dans cette section, vous configurez le code qui s’exécute sur le serveur.

Créer la classe Stock

Vous commencez par créer la classe de modèle Stock que vous utiliserez pour stocker et transmettre des informations sur un stock.

  1. Créez un fichier de classe dans le dossier du projet, nommez-le Stock.cs, puis remplacez le code du modèle par le code suivant :

    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);
                }
            }
        }
    }
    

    Les deux propriétés que vous définirez lorsque vous créerez des actions sont le symbole (par exemple, MSFT pour Microsoft) et le prix. Les autres propriétés dépendent de la façon et du moment où vous définissez Price. La première fois que vous définissez Price, la valeur est propagée à DayOpen. Par la suite, lorsque vous définissez Price, les valeurs des propriétés Change et PercentChange sont calculées en fonction de la différence entre Price et DayOpen.

Créer les classes StockTicker et StockTickerHub

Vous allez utiliser l’API Hub SignalR pour gérer l’interaction de serveur à client. Une classe StockTickerHub qui dérive de la classe SignalR Hub gère la réception des connexions et des appels de méthode des clients. Vous devez également gérer les données boursières et exécuter un objet Timer pour déclencher régulièrement des mises à jour de prix, indépendamment des connexions clientes. Vous ne pouvez pas placer ces fonctions dans une classe Hub, car les instances hub sont temporaires. Une classe Hub instance est créée pour chaque opération sur le hub, comme les connexions et les appels du client au serveur. Par conséquent, le mécanisme qui conserve les données boursières, met à jour les prix et diffuse les mises à jour de prix doit s’exécuter dans une classe distincte, que vous nommerez StockTicker.

Diffusion à partir de StockTicker

Vous souhaitez qu’un seul instance de la classe StockTicker s’exécute sur le serveur. Vous devez donc configurer une référence à partir de chaque instance StockTickerHub vers le instance StockTicker singleton. La classe StockTicker doit être en mesure de diffuser aux clients, car elle contient les données boursières et déclenche des mises à jour, mais StockTicker n’est pas une classe Hub. Par conséquent, la classe StockTicker doit obtenir une référence à l’objet de contexte de connexion SignalR Hub. Il peut ensuite utiliser l’objet de contexte de connexion SignalR pour diffuser aux clients.

  1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis cliquez sur Ajouter un nouvel élément.

  2. Si vous avez Visual Studio 2012 avec la mise à jour ASP.NET et Web Tools 2012.2, cliquez sur Web sous Visual C# et sélectionnez le modèle d’élément Classe SignalR Hub. Sinon, sélectionnez le modèle Classe .

  3. Nommez la nouvelle classe StockTickerHub.cs, puis cliquez sur Ajouter.

    Ajouter StockTickerHub.cs

  4. Remplacez le code du modèle par le code suivant :

    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 est utilisée pour définir les méthodes que les clients peuvent appeler sur le serveur. Vous définissez une méthode : GetAllStocks(). Lorsqu’un client se connecte initialement au serveur, il appelle cette méthode pour obtenir la liste de toutes les actions avec leurs prix actuels. La méthode peut s’exécuter de manière synchrone et retourner IEnumerable<Stock> , car elle retourne des données à partir de la mémoire. Si la méthode devait obtenir les données en effectuant quelque chose qui impliquerait l’attente, comme une recherche de base de données ou un appel de service web, vous devez spécifier Task<IEnumerable<Stock>> comme valeur de retour pour activer le traitement asynchrone. Pour plus d’informations, consultez ASP.NET Guide de l’API SignalR Hubs - Serveur - Quand s’exécuter de manière asynchrone.

    L’attribut HubName spécifie comment le hub sera référencé dans le code JavaScript sur le client. Le nom par défaut sur le client si vous n’utilisez pas cet attribut est une version à casse mixte du nom de la classe, qui dans ce cas serait stockTickerHub.

    Comme vous le verrez plus tard lorsque vous créerez la classe StockTicker, une instance singleton de cette classe est créée dans sa propriété d’instance statique. Ce singleton instance de StockTicker reste en mémoire, quel que soit le nombre de clients qui se connectent ou se déconnectent, et cette instance est ce que la méthode GetAllStocks utilise pour retourner les informations boursières actuelles.

  5. Créez un fichier de classe dans le dossier du projet, nommez-le StockTicker.cs, puis remplacez le code du modèle par le code suivant :

    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);
            }
    
        }
    }
    

    Étant donné que plusieurs threads exécutent la même instance du code StockTicker, la classe StockTicker doit être threadsafe.

    Stockage de la instance singleton dans un champ statique

    Le code initialise le champ de _instance statique qui sauvegarde la propriété Instance avec un instance de la classe , et il s’agit du seul instance de la classe qui peut être créé, car le constructeur est marqué comme privé. L’initialisation différée est utilisée pour le champ _instance, non pour des raisons de performances, mais pour garantir que la création instance est 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;
        }
    }
    

    Chaque fois qu’un client se connecte au serveur, une nouvelle instance de la classe StockTickerHub s’exécutant dans un thread distinct obtient le singleton StockTicker instance à partir de la propriété statique StockTicker.Instance, comme vous l’avez vu précédemment dans la classe StockTickerHub.

    Stockage des données boursières dans un concurrentDictionary

    Le constructeur initialise la collection _stocks avec des exemples de données boursières, et GetAllStocks retourne les stocks. Comme vous l’avez vu précédemment, cette collection d’actions est à son tour retournée par StockTickerHub.GetAllStocks, qui est une méthode serveur dans la classe Hub que les clients peuvent appeler.

    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 collection d’actions est définie comme un type ConcurrentDictionary pour la sécurité des threads. Vous pouvez également utiliser un objet Dictionary et verrouiller explicitement le dictionnaire lorsque vous y apportez des modifications.

    Pour cet exemple d’application, il est acceptable de stocker les données d’application en mémoire et de les perdre lorsque le instance StockTicker est supprimé. Dans une application réelle, vous travaillez avec un magasin de données back-end tel qu’une base de données.

    Mise à jour périodique des cours boursiers

    Le constructeur démarre un objet Timer qui appelle régulièrement des méthodes qui mettent à jour le cours des actions de manière aléatoire.

    _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 est appelé par le minuteur, qui passe la valeur null dans le paramètre state. Avant de mettre à jour les prix, un verrou est pris sur l’objet _updateStockPricesLock. Le code vérifie si un autre thread met déjà à jour les prix, puis appelle TryUpdateStockPrice sur chaque action de la liste. La méthode TryUpdateStockPrice détermine s’il faut modifier le cours de l’action et le montant à modifier. Si le cours de l’action est modifié, BroadcastStockPrice est appelé pour diffuser le changement de cours de l’action à tous les clients connectés.

    L’indicateur _updatingStockPrices est marqué comme volatile pour garantir que l’accès à celui-ci est threadsafe.

    private volatile bool _updatingStockPrices = false;
    

    Dans une application réelle, la méthode TryUpdateStockPrice appelle un service web pour rechercher le prix ; dans ce code, il utilise un générateur de nombres aléatoires pour apporter des modifications aléatoires.

    Obtention du contexte SignalR afin que la classe StockTicker puisse diffuser vers les clients

    Étant donné que les modifications de prix proviennent ici de l’objet StockTicker, il s’agit de l’objet qui doit appeler une méthode updateStockPrice sur tous les clients connectés. Dans une classe Hub, vous disposez d’une API pour appeler des méthodes clientes, mais StockTicker ne dérive pas de la classe Hub et n’a pas de référence à un objet Hub. Par conséquent, pour pouvoir diffuser sur les clients connectés, la classe StockTicker doit obtenir le contexte SignalR instance pour la classe StockTickerHub et l’utiliser pour appeler des méthodes sur les clients.

    Le code obtient une référence au contexte SignalR lorsqu’il crée la classe singleton instance, passe cette référence au constructeur et que le constructeur la place dans la propriété Clients.

    Il existe deux raisons pour lesquelles vous souhaitez obtenir le contexte une seule fois : obtenir le contexte est une opération coûteuse et l’obtenir une fois garantit que l’ordre prévu des messages envoyés aux clients est conservé.

    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);
    }
    

    L’obtention de la propriété Clients du contexte et son placement dans la propriété StockTickerClient vous permettent d’écrire du code pour appeler des méthodes clientes qui ressemblent à celles d’une classe Hub. Par instance, pour diffuser sur tous les clients, vous pouvez écrire Clients.All.updateStockPrice(stock).

    La méthode updateStockPrice que vous appelez dans BroadcastStockPrice n’existe pas encore ; vous l’ajouterez ultérieurement lorsque vous écrirez du code qui s’exécute sur le client. Vous pouvez faire référence à updateStockPrice ici, car Clients.All est dynamique, ce qui signifie que l’expression sera évaluée au moment de l’exécution. Lorsque cet appel de méthode s’exécute, SignalR envoie le nom de la méthode et la valeur du paramètre au client, et si le client a une méthode nommée updateStockPrice, cette méthode est appelée et la valeur du paramètre lui est transmise.

    Clients.All signifie envoyer à tous les clients. SignalR vous offre d’autres options pour spécifier les clients ou groupes de clients à envoyer. Pour plus d’informations, consultez HubConnectionContext.

Inscrire l’itinéraire SignalR

Le serveur doit savoir quelle URL intercepter et diriger vers SignalR. Pour ce faire, vous allez ajouter du code au fichier Global.asax .

  1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis cliquez sur Ajouter un nouvel élément.

  2. Sélectionnez le modèle d’élément Classe d’application globale , puis cliquez sur Ajouter.

    Ajouter global.asax

  3. Ajoutez le code d’inscription de route SignalR à la méthode Application_Start :

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

    Par défaut, l’URL de base de tout le trafic SignalR est « /signalr » et « /signalr/hubs » est utilisée pour récupérer un fichier JavaScript généré dynamiquement qui définit des proxys pour tous les hubs que vous avez dans votre application. La méthode MapHubs inclut des surcharges qui vous permettent de spécifier une AUTRE URL de base et certaines options SignalR dans un instance de la classe HubConfiguration.

  4. Ajoutez une instruction using en haut du fichier :

    using System.Web.Routing;
    
  5. Enregistrez et fermez le fichier Global.asax , puis générez le projet.

Vous avez maintenant terminé la configuration du code du serveur. Dans la section suivante, vous allez configurer le client.

Configurer le code client

  1. Créez un fichier HTML dans le dossier du projet et nommez-leStockTicker.html.

  2. Remplacez le code du modèle par le code suivant :

    <!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>
    

    Le code HTML crée une table avec 5 colonnes, une ligne d’en-tête et une ligne de données avec une seule cellule qui s’étend sur les 5 colonnes. La ligne de données affiche « chargement... » et ne s’affichent momentanément qu’au démarrage de l’application. Le code JavaScript supprime cette ligne et ajoute à sa place des lignes avec des données de stock récupérées à partir du serveur.

    Les balises de script spécifient le fichier de script jQuery, le fichier de script principal SignalR, le fichier de script de proxys SignalR et un fichier de script StockTicker que vous créerez ultérieurement. Le fichier de script des proxys SignalR, qui spécifie l’URL « /signalr/hubs », est généré dynamiquement et définit des méthodes proxy pour les méthodes de la classe Hub, dans ce cas pour StockTickerHub.GetAllStocks. Si vous préférez, vous pouvez générer ce fichier JavaScript manuellement à l’aide des utilitaires SignalR et désactiver la création de fichiers dynamiques dans l’appel de méthode MapHubs.

  3. Important

    Assurez-vous que les références de fichier JavaScript dans StockTicker.html sont correctes. Autrement dit, assurez-vous que la version jQuery de votre balise de script (1.8.2 dans l’exemple) est identique à la version jQuery dans le dossier Scripts de votre projet, et assurez-vous que la version de SignalR dans votre balise de script est identique à la version signalR dans le dossier Scripts de votre projet. Modifiez les noms de fichiers dans les balises de script si nécessaire.

  4. Dans Explorateur de solutions, cliquez avec le bouton droit sur StockTicker.html, puis cliquez sur Définir comme page de démarrage.

  5. Créez un fichier JavaScript dans le dossier du projet et nommez-leStockTicker.js..

  6. Remplacez le code du modèle par le code suivant :

    // 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 fait référence aux proxys SignalR. Le code obtient une référence au proxy pour la classe StockTickerHub et le place dans la variable ticker. Le nom du proxy est le nom qui a été défini par l’attribut [HubName] :

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

    Une fois toutes les variables et fonctions définies, la dernière ligne de code du fichier initialise la connexion SignalR en appelant la fonction de démarrage SignalR. La fonction de début s’exécute de manière asynchrone et retourne un objet jQuery Deferred, ce qui signifie que vous pouvez appeler la fonction done pour spécifier la fonction à appeler une fois l’opération asynchrone terminée..

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

    La fonction init appelle la fonction getAllStocks sur le serveur et utilise les informations que le serveur retourne pour mettre à jour la table stock. Notez que par défaut, vous devez utiliser la casse camel sur le client, bien que le nom de la méthode soit pascal-cased sur le serveur. La règle de casse de chameau s’applique uniquement aux méthodes, pas aux objets. Par exemple, vous faites référence au stock. Symbole et stock. Prix, et non stock.symbol ou 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();
    }
    

    Si vous souhaitez utiliser la casse pascal sur le client, ou si vous souhaitez utiliser un nom de méthode complètement différent, vous pouvez décorer la méthode Hub avec l’attribut HubMethodName de la même façon que vous avez décoré la classe Hub elle-même avec l’attribut HubName.

    Dans la méthode init, le code HTML d’une ligne de table est créé pour chaque objet stock reçu du serveur en appelant formatStock pour mettre en forme les propriétés de l’objet stock, puis en appelant supplant (qui est défini en haut de StockTicker.js) pour remplacer les espaces réservés dans la variable rowTemplate par les valeurs de propriété de l’objet stock. Le code HTML résultant est ensuite ajouté à la table stock.

    Vous appelez init en le transmettant en tant que fonction de rappel qui s’exécute une fois la fonction de démarrage asynchrone terminée. Si vous appelez init en tant qu’instruction JavaScript distincte après l’appel du démarrage, la fonction échouerait, car elle s’exécuterait immédiatement sans attendre que la fonction de démarrage termine l’établissement de la connexion. Dans ce cas, la fonction init essaie d’appeler la fonction getAllStocks avant que la connexion au serveur soit établie.

    Lorsque le serveur modifie le cours d’une action, il appelle updateStockPrice sur les clients connectés. La fonction est ajoutée à la propriété cliente du proxy stockTicker afin de la rendre disponible pour les appels à partir du serveur.

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

    La fonction updateStockPrice met en forme un objet stock reçu du serveur dans une ligne de table de la même façon que dans la fonction init. Toutefois, au lieu d’ajouter la ligne à la table, il recherche la ligne actuelle de l’action dans la table et remplace cette ligne par la nouvelle.

Tester l’application

  1. Appuyez sur F5 pour exécuter l'application en mode débogage.

    La table stock affiche initialement le « chargement... » line, puis après un court délai, les données initiales sur les actions sont affichées, puis les cours des actions commencent à changer.

    Chargement

    Table boursière initiale

    Table stock recevant des modifications du serveur

  2. Copiez l’URL à partir de la barre d’adresses du navigateur et collez-la dans une ou plusieurs nouvelles fenêtres de navigateur.

    L’affichage initial du stock est identique à celui du premier navigateur et les modifications se produisent simultanément.

  3. Fermez tous les navigateurs et ouvrez un nouveau navigateur, puis accédez à la même URL.

    L’objet singleton StockTicker a continué à s’exécuter dans le serveur, de sorte que l’affichage de la table boursière indique que les actions ont continué à changer. (Vous ne voyez pas la table initiale avec zéro changement.)

  4. Fermez le navigateur.

Activation de la journalisation

SignalR dispose d’une fonction de journalisation intégrée que vous pouvez activer sur le client pour faciliter la résolution des problèmes. Dans cette section, vous allez activer la journalisation et voir des exemples qui montrent comment les journaux vous indiquent les méthodes de transport suivantes que SignalR utilise :

Pour une connexion donnée, SignalR choisit la meilleure méthode de transport prise en charge par le serveur et le client.

  1. Ouvrez StockTicker.js et ajoutez une ligne de code pour activer la journalisation immédiatement avant le code qui initialise la connexion à la fin du fichier :

    // Start the connection
    $.connection.hub.logging = true;
    $.connection.hub.start().done(init);
    
  2. Appuyez sur F5 pour exécuter le projet.

  3. Ouvrez la fenêtre Outils de développement de votre navigateur, puis sélectionnez la console pour afficher les journaux. Vous devrez peut-être actualiser la page pour voir les journaux de Signalr qui négocient la méthode de transport pour une nouvelle connexion.

    Si vous exécutez Internet Explorer 10 sur Windows 8 (IIS 8), la méthode de transport est WebSockets.

    Console IIS 8 d’Internet Explorer 10

    Si vous exécutez Internet Explorer 10 sur Windows 7 (IIS 7.5), la méthode de transport est iframe.

    Console Internet Explorer 10, IIS 7.5

    Dans Firefox, installez le complément Firebug pour obtenir une fenêtre console. Si vous exécutez Firefox 19 sur Windows 8 (IIS 8), la méthode de transport est WebSockets.

    Firefox 19 IIS 8 Websockets

    Si vous exécutez Firefox 19 sur Windows 7 (IIS 7.5), la méthode de transport est les événements envoyés par le serveur.

    Firefox 19 IIS 7.5 Console

Installer et passer en revue l’exemple StockTicker complet

L’application StockTicker installée par le package NuGet Microsoft.AspNet.SignalR.Sample inclut plus de fonctionnalités que la version simplifiée que vous venez de créer à partir de zéro. Dans cette section du tutoriel, vous installez le package NuGet et passez en revue les nouvelles fonctionnalités et le code qui les implémente.

Installer le package NuGet SignalR.Sample

  1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis cliquez sur Gérer les packages NuGet.

  2. Dans la boîte de dialogue Gérer les packages NuGet , cliquez sur En ligne, entrez SignalR.Sample dans la zone Rechercher en ligne , puis cliquez sur Installer dans le package SignalR.Sample .

    Installer le package SignalR.Sample

  3. Dans le fichier Global.asax , commentez RouteTable.Routes.MapHubs(); ligne que vous avez ajoutée précédemment dans la méthode Application_Start.

    Le code dans Global.asax n’est plus nécessaire, car le package SignalR.Sample inscrit l’itinéraire SignalR dans le fichier 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 référencée par l’attribut assembly est incluse dans le package NuGet WebActivatorEx, qui est installé en tant que dépendance du package SignalR.Sample.

  4. Dans Explorateur de solutions, développez le dossier SignalR.Sample créé en installant le package SignalR.Sample.

  5. Dans le dossier SignalR.Sample , cliquez avec le bouton droit sur StockTicker.html, puis cliquez sur Définir comme page de démarrage.

    Notes

    L’installation du package NuGet SignalR.Sample peut changer la version de jQuery que vous avez dans votre dossier Scripts . Le nouveau fichier StockTicker.html que le package installe dans le dossier SignalR.Sample sera synchronisé avec la version jQuery installée par le package, mais si vous souhaitez réexécuter votre fichier StockTicker.htmld’origine , vous devrez peut-être d’abord mettre à jour la référence jQuery dans la balise de script.

Exécution de l'application

  1. Appuyez sur F5 pour exécuter l'application.

    En plus de la grille que vous avez vue précédemment, l’application de ticker boursier complet affiche une fenêtre à défilement horizontal qui affiche les mêmes données boursières. Lorsque vous exécutez l’application pour la première fois, le « marché » est « fermé » et vous voyez une grille statique et une fenêtre de coche qui ne défile pas.

    Démarrage de l’écran StockTicker

    Lorsque vous cliquez sur Ouvrir le marché, la zone Descripteur d’actions actives commence à défiler horizontalement et le serveur commence à diffuser régulièrement les changements de cours des actions sur une base aléatoire. Chaque fois qu’un cours change, la grille Live Stock Table et la case Live Stock Ticker sont mises à jour. Lorsque le changement de prix d’une action est positif, l’action est affichée avec un arrière-plan vert et, lorsque la modification est négative, l’action est affichée avec un arrière-plan rouge.

    Application StockTicker, marché ouvert

    Le bouton Fermer le marché arrête les modifications et arrête le défilement du ticker, et le bouton Réinitialiser réinitialise toutes les données boursières à l’état initial avant le début des modifications de prix. Si vous ouvrez d’autres fenêtres de navigateur et accédez à la même URL, vous voyez les mêmes données mises à jour dynamiquement en même temps dans chaque navigateur. Lorsque vous cliquez sur l’un des boutons, tous les navigateurs répondent de la même façon en même temps.

Affichage live Stock Ticker

L’affichage Live Stock Ticker est une liste non triée dans un élément div qui est mis en forme en une seule ligne par styles CSS. Le ticker est initialisé et mis à jour de la même façon que la table : en remplaçant les espaces réservés dans une <chaîne de modèle li> et en ajoutant dynamiquement les <éléments li> à l’élément <ul> . Le défilement est effectué à l’aide de la fonction d’animation jQuery pour faire varier la marge gauche de la liste non triée dans la div.

Code HTML du ticker boursier :

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

Le ticker boursier 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;
    }

Code jQuery qui fait défiler :

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

Méthodes supplémentaires sur le serveur que le client peut appeler

La classe StockTickerHub définit quatre méthodes supplémentaires que le client peut appeler :

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

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

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

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

OpenMarket, CloseMarket et Reset sont appelés en réponse aux boutons en haut de la page. Ils illustrent le modèle d’un client déclenchant un changement d’état qui est immédiatement propagé à tous les clients. Chacune de ces méthodes appelle une méthode dans la classe StockTicker qui affecte le changement d’état du marché, puis diffuse le nouvel état.

Dans la classe StockTicker, l’état du marché est maintenu par une propriété MarketState qui retourne une valeur d’énumération MarketState :

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

public enum MarketState
{
    Closed,
    Open
}

Chacune des méthodes qui modifient l’état du marché le fait à l’intérieur d’un bloc de verrouillage, car la classe StockTicker doit être 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();
    }
}

Pour garantir que ce code est threadsafe, le champ _marketState qui sauvegarde la propriété MarketState est marqué comme volatile,

private volatile MarketState _marketState;

Les méthodes BroadcastMarketStateChange et BroadcastMarketReset sont similaires à la méthode BroadcastStockPrice que vous avez déjà vue, sauf qu’elles appellent différentes méthodes définies au niveau du 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();
}

Fonctions supplémentaires sur le client que le serveur peut appeler

La fonction updateStockPrice gère désormais à la fois la grille et l’affichage du ticker, et elle utilise jQuery.Color pour flasher les couleurs rouge et verte.

Les nouvelles fonctions dans SignalR.StockTicker.js activer et désactiver les boutons en fonction de l’état du marché, et elles arrêtent ou démarrent le défilement horizontal de la fenêtre de coche. Étant donné que plusieurs fonctions sont ajoutées à ticker.client, la fonction d’extension jQuery est utilisée pour les ajouter.

$.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();
    }
});

Configuration du client supplémentaire après l’établissement de la connexion

Une fois la connexion établie par le client, il doit effectuer des tâches supplémentaires : déterminer si le marché est ouvert ou fermé afin d’appeler la fonction marketOpened ou marketClosed appropriée, et attacher les appels de méthode serveur aux boutons.

$.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();
        });
    });

Les méthodes serveur ne sont pas connectées aux boutons tant qu’une fois la connexion établie, le code ne peut donc pas essayer d’appeler les méthodes serveur avant qu’elles ne soient disponibles.

Étapes suivantes

Dans ce tutoriel, vous avez appris à programmer une application SignalR qui diffuse des messages du serveur vers tous les clients connectés, à la fois de manière périodique et en réponse aux notifications d’un client. Le modèle d’utilisation d’un instance singleton multithread pour maintenir l’état du serveur peut également être utilisé dans des scénarios de jeu en ligne multi-joueurs. Pour obtenir un exemple, consultez le jeu ShootR basé sur SignalR.

Pour obtenir des tutoriels qui montrent les scénarios de communication d’égal à égal, consultez Prise en main avec SignalR et Mise à jour en temps réel avec SignalR.

Pour en savoir plus sur les concepts de développement de SignalR plus avancés, visitez les sites suivants pour le code source et les ressources SignalR :