Partager via


À la pointe

Créer une barre de progression avec SignalR

Dino Esposito

Télécharger l'exemple de code

Dino EspositoDans les deux articles précédents de cette chronique, j'ai expliqué comment construire une solution ASP.NET afin de répondre au problème récurrent de surveillance de la progression d'une tâche à distance du côté client d'une application Web. Certes l'adoption d'AJAX s'est révélée un succès, mais il manque toujours une solution qui soit largement acceptée et complète pour afficher une barre de progression contextuelle dans une application Web sans avoir à recourir à Silverlight ou à Flash.

À vrai dire, il n'y a pas trente six solutions. Vous pouvez créer votre propre solution si vous le souhaitez, mais le modèle sous-jacent ne sera pas vraiment différent de celui que j'ai présenté (surtout en ciblant ASP.NET MVC) dans les chroniques précédentes. Ce mois-ci, je reviens sur le même sujet, mais j'explique comment créer une barre de progression à l'aide d'une nouvelle bibliothèque toujours en cours de création : SignalR.

SignalR se compose d'une bibliothèque Microsoft .NET Framework et d'un plug-in jQuery développé par l'équipe ASP.NET. Elle sera peut-être à inclure dans les prochaines versions de la plateforme ASP.NET. Elle offre des fonctionnalités très prometteuses, actuellement absentes de .NET Framework et qu'un nombre croissant de développeurs réclame.

SignalR en bref

SignalR est une bibliothèque client/serveur intégrée qui permet aux clients basés sur un navigateur et aux composants serveur basés sur ASP.NET d'avoir une conversation à étapes multiples et bidirectionnelle. En d'autres termes, la conversation ne se limite pas à un seul échange de requêtes sans état/de données de réponse, mais elle se poursuit jusqu'à se fermer explicitement. La conversation se déroule sur une connexion persistante et permet au client d'envoyer plusieurs messages au serveur, celui-ci répond ensuite, et surtout envoie des messages asynchrones au client.

Il n'est donc pas surprenant que je fasse appel à une application de discussion pour illustrer les fonctionnalités principales de SignalR dans la démonstration classique. Le client démarre la conversation en envoyant un message au serveur. Le serveur (un point de terminaison ASP.NET) répond et continue à écouter les nouvelles requêtes.

SignalR est spécialement conçu pour un scénario Web et nécessite jQuery 1.6 (ou une version plus récente) sur le client et ASP.NET sur le serveur. Vous pouvez installer SignalR via NuGet ou en téléchargeant les éléments directement à partir du répertoire GitHub à cette adresse : github.com/SignalR/SignalR. La figure 1 présente la page NuGet avec tous les packages SignalR. Vous devez au minimum télécharger SignalR, qui a des dépendances sur SignalR.Server pour la partie côté serveur de l'infrastructure et SignalR.Js pour la partie client Web de celle-ci. Les autres packages que vous voyez dans la figure 1 répondent à des besoins plus spécifiques, comme fournir un client .NET, un résolveur de dépendance Ninject et servir de mécanisme de transport de remplacement basé sur des sockets Web HTML5.

SignalR Packages Available on the NuGet Platform
Figure 1 Packages SignalR disponibles sur la plateforme NuGet

Au cœur de l'exemple Discussion

Avant d'essayer de créer une solution de barre de progression, il est utile de se familiariser avec la bibliothèque en consultant l'exemple de discussion fourni avec le code source téléchargeable (archive.msdn.microsoft.com/mag201203CuttingEdge) et d'autres informations répertoriées dans les quelques articles actuellement disponibles sur le Web. Notez cependant que SignalR n'est pas un projet publié.

Dans le cadre d'un projet ASP.NET MVC, vous devez commencer par référencer plusieurs fichiers de script, comme indiqué ici :

    <script src="@Url.Content("~/Scripts/jquery-1.6.4.min.js")"
      type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.signalr.min.js")"
      type="text/javascript"></script>
    <script src="@Url.Content("~/signalr/hubs")"
      type="text/javascript"></script>

Notez qu'il n'y a rien de spécifique à ASP.NET MVC dans SignalR, et que cette bibliothèque peut aussi bien être utilisée avec les applications Web Forms.

Il est important de souligner que les deux premiers liens font référence à un fichier script spécifique. Le troisième lien, en revanche, fait toujours référence à du contenu JavaScript, mais qui est généré instantanément et dépend d'autres codes que vous avez écrits dans l'application ASP.NET hôte. Notez également que vous avez besoin de la bibliothèque JSON2 pour prendre en charge des versions d'Internet Explorer antérieures à la version 8.

Une fois la page chargée, vous devez finaliser la configuration cliente et ouvrir la connexion. La figure 2 montre le code dont vous avez besoin. Vous voudrez peut-être appeler ce code à partir de l'événement ready de jQuery. Le code lie des gestionnaires de script aux éléments HTML (JavaScript discret) et prépare la conversation SignalR.

Figure 2 Configuration de la bibliothèque SignalR pour un exemple de type Discussion

    <script type="text/javascript">
      $(document).ready(function () {    // Add handler to Send button
        $("#sendButton").click(function () {
          chat.send($('#msg').val());
        });
        // Create a proxy for the server endpoint
        var chat = $.connection.chat; 
        // Add a client-side callback to process any data
        // received from the server
        chat.addMessage = function (message) {
          $('#messages').append('<li>' + message + '</li>');
        };
        // Start the conversation
        $.connection.hub.start();
      });
    </script>

Il est important de noter que l'objet $.connection est défini dans le fichier de script SignalR. L'objet chat, en revanche, est un objet dynamique dans le sens où son code est généré instantanément et est injecté dans la page du client via la référence de script Hub. L'objet chat est en fin de compte un proxy JavaScript pour un objet côté serveur. Il est clair à ce stade que le code client de la figure 2 a peu d'importance et ne peut pas faire grand-chose sans un homologue puissant côté serveur.

Le projet ASP.NET doit inclure une référence vers l'assembly SignalR et ses dépendances, telles que Microsoft.Web.Infrastructure. Le code côté serveur inclut une classé gérée qui correspond à l'objet JavaScript que vous avez créé. Concernant le code de la figure 2, vous devez avoir un objet côté serveur ayant la même interface que l'objet Chat côté client. Cette classe serveur va hériter de la classe Hub définie dans l'assembly SignalR. Voici la signature de la classe :

using System;
using SignalR.Hubs;
namespace SignalrProgressDemo.Progress
{
  public class Chat : Hub
  {
    public void Send(String message)
    {
      Clients.addMessage(message);
    }
  }
}

Chaque méthode publique de la classe doit correspondre à une méthode JavaScript sur le client. Sinon, une méthode appelée sur l'objet JavaScript doit au moins avoir une méthode correspondante sur la classe serveur. Ainsi, la méthode Send qui est appelée dans le code de script de la figure 2 finit par passer un appel à la méthode Send de l'objet Chat, comme défini précédemment. Pour renvoyer les données au client, le code serveur utilise la propriété Clients de la classe Hub. Le membre Clients est de type dynamique, ce qui lui permet de référencer de manière dynamique des objets déterminés. La propriété Clients en particulier contient une référence à un objet côté serveur conçu après l'interface de l'objet client : l'objet Chat. L'objet Chat de la figure 2 ayant une méthode addMessage, la même méthode addMessage est attendue également par l'objet Chat côté serveur.

Démonstration d'une barre de progression

Utilisons à présent SignalR pour créer un système de notification qui signale au client tous les progrès effectués côté serveur au cours d'une longue tâche. Commençons par créer une classe côté serveur qui encapsule la tâche. Le nom que vous attribuez à cette classe (même choisi de façon arbitraire) affecte le code client que vous rédigerez par la suite. Cela constitue simplement une raison supplémentaire pour choisir méticuleusement le nom de la classe. Il est encore plus important de noter que cette classe va hériter d'une classe fournie par SignalR nommée Hub. Voici la signature :

public class BookingHub : Hub
{
  ...
}

La classe BookingHub aura plusieurs méthodes publiques, en majorité des méthodes void acceptant toutes les séquences de paramètres de saisie, pertinentes pour leurs objectifs. Chaque méthode publique sur une classe Hub représente un point de terminaison possible à appeler pour le client. À titre d'exemple, ajoutons une méthode pour réserver un vol :

public void BookFlight(String from, String to)
{
  ...
}

Cette méthode est censée contenir toute la logique qui exécute l'action donnée (à savoir, la réservation d'un vol). Le code va également contenir des appels aux différentes étapes, qui d'une certaine façon signalent les progrès au client. Supposons que le squelette de la méthode BookFlight ressemble à ceci :

public void BookFlight(String from, String to)
{
  // Book first leg  var ref1 = BookFlight(from, to);  // Book return flight
  var ref2 = BookFlight(to, from);
  // Handle payment
  PayFlight(ref1, ref2);
}

Conjointement à ces opérations principales, vous souhaitez informer l'utilisateur des progrès effectués. La classe de base Hub propose une propriété appelée Clients, définie pour être de type dynamique. En d’autres termes, vous allez appeler une méthode sur cet objet pour rappeler le client. La forme de cette méthode est toutefois déterminée par le client lui-même. Passons à présent au client.

Comme mentionné, du code du script s'exécute au chargement dans la page du client. Si vous utilisez jQuery, l'événement $(document).ready constitue un bon emplacement pour exécuter ce code. Tout d'abord, vous devez obtenir un proxy vers l'objet serveur :

var bookingHub = $.connection.bookingHub;
// Some config work
...
// Open the connection
$.connection.hub.start();

Le nom de l'objet référencé sur le composant SignalR $.connection natif est simplement un proxy créé de façon dynamique qui expose l'interface publique de l'objet BookingHub au client. Le proxy est généré via le lien signalr/hubs qui figure dans la section <script> de la page. La convention d'affectation de noms utilisée pour les noms est la casse mixte, ce qui signifie que la classe BookingHub dans C# devient l'objet bookingHub dans JavaScript. Sur cet objet, vous trouvez des méthodes qui correspondent à l'interface publique de l'objet serveur. En outre pour les méthodes, la convention d'affectation de noms utilise les mêmes noms, mais en casse mixte. Vous pouvez ajouter un gestionnaire de clic à un bouton HTML et démarrer une opération serveur via AJAX, comme indiqué ici :

bookingHub.bookFlight("fco", "jfk");

Vous pouvez à présent définir des méthodes client pour gérer toutes les réponses. Vous pouvez, par exemple, définir sur le proxy client une méthode displayMessage qui reçoit un message et l'affiche via une balise span HTML :

bookingHub.displayMessage = function (message) {
  $("#msg").html(message);
};

Notez que vous êtes responsable de la signature de la méthode display­Message. Vous devez choisir les éléments à transmettre et le type d'interaction attendu.

Il reste une dernière chose pour terminer : qui appelle displayMessage et qui est en fin de compte responsable de la transmission des données ? Il s'agit du code Hub côté serveur. Vous devez appeler displayMessage (ou toute autre méthode de rappel que vous souhaitez mettre en place) à partir de l'objet Hub via l'objet Clients. La Figure 3 présente la version finale de la classe Hub.

Figure 3 Version finale de la classe Hub

public void BookFlight(String from, String to)
{
  // Book first leg
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", from, to));
  Thread.Sleep(2000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(3000);
  // Book return
  Clients.displayMessage(    String.Format("Booking flight: {0}-{1} ...", to, from));
  Thread.Sleep(2000);
  // Some return value
  Clients.displayMessage("Flight booked successfully.");
}

Notez que dans ce cas, le nom displayMessage doit correspondre parfaitement à celui utilisé dans le code JavaScript. Si vous faites une erreur de saisie telle que DisplayMessage, aucune exception n'est déclenchée, mais aucun code n'est exécuté non plus.

Le code Hub est implémenté en tant qu'objet Task, il obtient ainsi sa propre thread à exécuter et n'affecte pas le pool de threads ASP.NET.

Si une tâche serveur aboutit à la planification d'un travail asynchrone, une thread est prélevée à partir du pool de travail standard. L'avantage est que les gestionnaires de requêtes SignalR sont asynchrones, ce qui signifie que pendant qu'ils sont en état d'attente de nouveaux messages, ils n'utilisent pas du tout de thread. Lorsqu'un message arrive et qu'une opération doit être effectuée, une thread de travail ASP.NET est utilisée.

Une véritable barre de progression avec HTML

Dans des chroniques précédentes et dans celle-ci, j'emploie souvent le terme barre de progression sans toutefois montrer une barre d'indicateur classique comme exemple de l’interface utilisateur du client. Une barre d'indicateur fournit simplement un effet visuel agréable et ne nécessite pas de code plus complexe dans l'infrastructure asynchrone. Toutefois, la figure 4 illustre le code JavaScript qui créé une barre d’indicateur instantanément, en fonction d'une valeur de pourcentage. Vous pouvez modifier l'apparence des éléments HTML via les classes CSS nécessaires.

Figure 4 Création d'une barre d’indicateur basée HTML

var GaugeBar = GaugeBar || {};
GaugeBar.generate = function (percentage) {
  if (typeof (percentage) != "number")
    return;
  if (percentage > 100 || percentage < 0)
    return;
  var colspan = 1;
  var markup = "<table class='gauge-bar-table'><tr>" +
    "<td style='width:" + percentage.toString() +
    "%' class='gauge-bar-completed'></td>";
  if (percentage < 100) {
    markup += "<td class='gauge-bar-tobedone' style='width:" +
      (100 - percentage).toString() +
      "%'></td>";
    colspan++;
  }
  markup += "</tr><tr class='gauge-bar-statusline'><td colspan='" +
    colspan.toString() +
    "'>" +
    percentage.toString() +
    "% completed</td></tr></table>";
  return markup;
}

Vous devez appeler cette méthode à partir d'un gestionnaire de clic de bouton :

bookingHub.updateGaugeBar = function (perc) {
  $("#bar").html(GaugeBar.generate(perc));
};

La méthode updateGaugeBar est par conséquent appelée à partir d'une autre méthode Hub qui utilise simplement un rappel client différent pour indiquer la progression. Il vous suffit de remplacer displayMessage utilisé précédemment par updateGaugeBar dans une méthode Hub.

Pas simplement des clients Web

J'ai présenté SignalR principalement comme une API qui nécessite un site Web frontal. Bien que ce soit le scénario le plus probable dans lequel vous allez l'utiliser, SignalR ne se limite pourtant pas à prendre en charge uniquement des clients Web. Vous pouvez télécharger un client pour les applications de bureau .NET. Un autre client sera publié bientôt pour prendre en charge les clients Windows Phone.

Cette chronique ne fait qu'aborder tout l'éventail des possibilités de SignalR. Elle ne présente en effet que l'approche la plus simple et la meilleure pour la programmer. Dans une prochaine chronique, je vous proposerai d'examiner une partie de sa magie sous-jacente et de voir comment les paquets se déplacent le long de la ligne. Restez connecté.

Dino Esposito est l'auteur de « Programming Microsoft ASP.NET MVC3 » (Microsoft Press, 2011) et co-auteur de « Microsoft .NET: Architecting Applications for the Enterprise » (Microsoft Press, 2008). Basé en Italie, il participe régulièrement aux différentes manifestations du secteur organisées aux quatre coins du monde. Vous pouvez le suivre sur Twitter à l'adresse twitter.com/despos.

Merci à l'expert technique suivant d'avoir relu cet article : Damian Edwards