Dynamiser l'affichage sur son site avec SignalR 2

Arnaud Leclerc

Dans une optique de dynamisation et de notification de notre site web, nous avons vu précédemment comment SQL Server pouvait nous aider à décentraliser le traitement de certaines données avec SQL Service Broker. Dans cet article, nous allons voir comment mettre en place SignalR 2 sur un site web, comment le configurer côté serveur et comment l'utiliser côté client.

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : Dynamiser l'affichage sur son site avec SignalR 2

- Auteur : Arnaud Leclerc

- Parution : 23 novembre 2016

- Licence :

Sommaire

- I. SignalR : qu'est ce que c'est ?

- II. SignalR 2 VS SignalR 1

- III. Mettre en place SignalR 2 sur son site

- IV. Côté serveur : le Hub

- V. Côté client : JavaScript

- VI. Utiliser SignalR sur une ferme de serveurs

- VII. Conclusion

- VIII. Remerciements

I - SignalR : qu'est ce que c'est ?

Reprenons la définition officielle de Microsoft sur ce qu'est SignalR :

ASP.NET SignalR is a new library for ASP.NET developers that makes developing real-time web functionality easy. SignalR allows bidirectional communication between server and client. Servers can now push content to connected clients instantly as it becomes available. SignalR supports Web Sockets, and falls back to other compatible techniques for older browsers. SignalR includes APIs for connection management (for instance, connect and disconnect events), grouping connections, and authorization.

SignalR nous permet donc de faire de la communication bidirectionnelle (client-serveur et serveur-client) en temps réel. IIS 8 utilise les WebSockets pour l'échange de données, cela permettra à notre serveur de notifier notre client de la disponibilité d'un message. Pour les versions inférieures à IIS 8 ou pour les navigateurs ne gérant pas les WebSockets, des fallbacks (c'est-à-dire des méthodes de substitution) en polling sont utilisés. Dans ce cas, notre client effectuera automatiquement des requêtes vers notre serveur pour récupérer les messages.

Nous allons nous intéresser uniquement à SignalR 2 dans cet article. Sa mise en place se faisant exclusivement avec Owin, je vous conseille de vous renseigner sur cette technologie avant d'aller plus loin dans cet article.

II - SignalR 2 VS SignalR 1

Les principales nouveautés de SignalR 2 sont :

- l'intégration obligatoire avec Owin. De fait, la V2 ne fonctionne qu'avec le framework .Net 4.5. SignalR 1 doit par contre se contenter du framework .Net 4.0 ;

- la possibilité de gérer des versions serveur plus récentes que la version présente sur le client. Dans la V1, les versions clients et serveurs devaient être les mêmes pour assurer le bon fonctionnement de SignalR, ce qui pouvait rendre la maintenance difficile. À noter que la réciproque n'est pas vraie, SignalR ne supporte pas les serveurs avec une version plus ancienne que les clients ;

- le support des plateformes Silverlight 5, Win RT et Windows Phone 8 ;

- le support d'Android et IOS via MonoTouch et MonoDroid de Xamarin.

Vous pourrez trouver ici la liste complète des évolutions et corrections de bugs sur chaque version de SignalR, ainsi que des exemples pour effectuer une migration de SignalR 1 vers SignalR 2.

III - Mettre en place SignalR 2 sur son site

Commençons par installer le package Nuget Microsoft.ASP.Net.SignalR. Celui-ci va installer automatiquement les dépendances dont nous avons besoin pour Owin et SignalR.

Nous allons commencer par créer une URL SignalR sur notre site. Pour cela, on va utiliser une classe Startup d'Owin qui sera lancée au démarrage de notre site. En voici le contenu :

1. using System;

2. using System.Collections.Generic;

3. using System.Linq;

4. using System.Text;

5. using System.Threading.Tasks;

6. using Microsoft.AspNet.SignalR.Owin;

7. using Microsoft.Owin.Extensions;

8. using Owin;

10. [assembly: Microsoft.Owin.OwinStartup(typeof(SignalRSample.Business.Startup))]

11. namespace SignalRSample.Business

12. {

13. public class Startup

14. {

15. public void Configuration(Owin.IAppBuilder app)

16. {

17. app.MapSignalR();

18. }

19. }

20. }

Cette classe est détectée automatiquement au démarrage de notre site web et la fonction Configuration est appelée à ce moment-là. L'appel à la méthode MapSignalR va permettre de générer le JavaScript nécessaire au client pour le fonctionnement de SignalR (http://monsiteweb/signalr), et une première URL pour la communication client / serveur (http://monsiteweb/signalr/hubs).

IV - Côté serveur : le Hub

Un hub est une classe dans laquelle nous allons mettre en place différentes fonctions qui seront appelées soit directement par notre client JavaScript pour effectuer une action côté serveur, soit par notre code serveur afin d'effectuer une action sur le navigateur client. Une classe hub doit hériter de la classe Microsoft.AspNet.SignalR.Hub.

Pour commencer simple, nous allons créer un hub qui nous permettra de mettre en place un « système de chat ». Définissons donc notre classe ChatHub comme ceci :

1. namespace SignalRSample.Business.Hubs

2. {

3. [HubName("chatHub")]

4. public class ChatHub : Hub

5. {

6. public ChatHub()

7. {

8. }

10. [HubMethodName("sendMessage")]

11. public void SendMessage(string username, string message)

12. {

13. Clients.All.displayMessage(string.Format("{0} dit : {1}", username, message));

14. }

16. public override Task OnConnected()

17. {

18. Clients.Client(Context.ConnectionId).promptForLogin();

19. return base.OnConnected();

20. }

21. }

Nous avons ici défini une méthode SendMessage qui prend une chaîne de caractères en paramètre. Cette méthode diffusera le message envoyé à tous les clients connectés sur ce hub. L'appel à Clients.All.displayMessage effectuera un appel à la méthode displayMessage sur les navigateurs clients concernés. Nous verrons plus loin comment mettre en place la partie JavaScript.

Plusieurs choses sont à noter dans cette classe :

- les méthodes appelées par *Clients.All.** sont de type dynamique. Il faut donc être rigoureux sur le nommage des méthodes côté JavaScript, car vous n'aurez ni erreur de compilation, ni erreur d'exécution lors de cet appel ;

- les attributs HubName et HubMethodName permettent de familiariser les noms des hubs et méthodes à utiliser côté JavaScript. Vous pouvez donc créer des hubs / méthodes aux noms complexes et simplifier leur utilisation côté client en leur donnant des noms plus familiers via ces attributs ;

- hériter de la classe Hub (et par cette dernière, de la classe abstraite HubBase) nous permet de pouvoir gérer certains événements sur le hub : connexion, déconnexion, reconnexion. Dans notre classe ChatHub, nous demanderons la saisie d'un login au client qui a appelé le hub sur le déclenchement de l'événement OnConnected.

V - Côté client : JavaScript

Maintenant que notre serveur est prêt à gérer de la communication SignalR, voyons comment l'utiliser depuis notre client.

Commençons par référencer les scripts dont nous avons besoin. L'ajout du package Nuget SignalR a dû créer un script jquery.signalR-{numversion}.js et sa version minifiée. Il nous faut aussi référencer le script généré via la méthode MapSignalR dans notre classe Startup. Voici le code de ma page de chat :

1. @{

2. Layout = "~/_SiteLayout.cshtml";

3. Page.Title = "Chat";

4. }

6. <div class="chat_block">

7. <input type="text" id="txtMessage" />

8. <input type="button" id="btnSend" value="Envoyer" />

9. <input type="hidden" id="username" />

10. <ul id="messages">

12. </ul>

13. </div>

15. <script src="Scripts/jquery-2.1.1.min.js"></script>

16. <script src="Scripts/jquery.signalR-2.1.2.min.js"></script>

17. <script src="signalr/hubs"></script>

19. <script type="text/javascript">

20. $(function () {

21. //Déclaration de notre proxy ChatHub

22. var chathub = $.connection.chatHub;

23. //La méthode displayMessage est appelée depuis le serveur lorsqu'un utilisateur a cliqué sur "Envoyer"

24. chathub.client.displayMessage = function (message) {

25. $("#messages").append("<li>" + message + "</li>");

26. }

28. chathub.client.promptForLogin = function () {

29. $("#username").val(prompt('Quel est votre nom?'));

30. chathub.server.sendMessage($("#username").val(), "s'est connecté");

31. }

33. //Démarrage de la connexion au hub

34. $.connection.hub.start().done(function () {

35. $("#btnSend").on("click", function () {

36. chathub.server.sendMessage($("#username").val(), $("#txtMessage").val());

37. $(this).empty();

38. })

39. });

40. });

41. </script>

C'est ici qu'interviennent les noms que nous avons donnés à notre hub et nos méthodes via les attributs. Pour récupérer le hub que nous voulons appeler, ici ChatHub, on va donc utiliser la variable JavaScript $.connection.chatHub.

Nous allons définir à partir de la variable chatHub.client toutes les méthodes qui seront appelées depuis le serveur. Il faut donc dans notre cas créer les fonctions promptForLogin (appelée lors de l'événement de connexion) et displayMessage qui affichera le message reçu sur notre interface. Les appels à notre hub se feront via la variable chatHub.server. Pour appeler notre méthode SendMessage, nous allons donc utiliser chathub.server.sendMessage. À noter que par défaut, si l'attribut HubMethodName n'est pas utilisé, le nom de la méthode à utiliser côté JavaScript est le même que celui côté serveur. À noter que les noms de méthodes sont sensibles à la casse.

Enfin, il nous suffit d'utiliser l'appel à $.connection.hub.start() pour se connecter au hub, ce qui va avoir pour effet de déclencher l'événement OnConnected côté serveur.

Comme on peut le voir sur la capture d'écran ci-dessous, notre événement OnConnected est bien déclenché une fois notre page chargée, et la méthode promptForLogin est appelée dans le code JavaScript.

Comme nous utilisons Clients.All lors de l'appel à displayMessage, tous les clients connectés au hub recevront les messages postés lors de l'appel à sendMessage. Si l'on veut déclencher l'appel aux méthodes JavaScript uniquement sur le client qui a appelé le Hub, il faut utiliser Clients.Client(Context.ConnectionId) comme lors de l'événement OnConnected dans notre exemple. Il est également possible de rattacher un utilisateur à un groupe et de ne prévenir que les clients d'un groupe spécifique via Clients.Group.

VI - Utiliser SignalR sur une ferme de serveurs

Notre exemple ci-dessus fonctionne très bien sur un site web hébergé sur un seul serveur. Mais que se passerait-il si l'on rajoutait plusieurs serveurs afin de pouvoir absorber la charge de notre site qui est visité par des millions de personnes ? Un client connecté sur un serveur A ne recevrait pas les messages envoyés depuis le serveur B.

Pour pallier ce problème, on peut utiliser un backplane. Il s'agit d'un composant de notre application qui nous permettra de pouvoir distribuer un message envoyé par un serveur à tous les autres serveurs de notre ferme. Lors de l'envoi du message depuis notre serveur B, le backplane va envoyer ce message au serveur A sur lequel le client que l'on veut prévenir est connecté. Il existe trois types de backplanes pouvant être utilisés pour SignalR :

- Windows Azure Service Bus : conseillé si votre application est hébergée sur Windows Azure ;

- Redis : système de gestion de données NoSQL. Implémente un pattern publish/subscribe pour l'envoi de message. Plus d'informations ici ;

- SQL Server Service Broker : nous allons nous intéresser ici uniquement à ce dernier. Après avoir créé notre base de données, il faut activer SQL Service Broker :

ALTER DATABASE SIGNALR SET ENABLE_BROKER

Il nous suffit maintenant de dire à notre application que nous voulons utiliser un backplane SQL. Pour cela, ajoutons ce package Nuget à notre application : Microsoft.AspNet.SignalR.SqlServer.

Une fois le package installé, il nous suffit d'appeler la méthode UseSqlServer en lui donnant la ConnectionString de notre base de données :

1. [assembly: Microsoft.Owin.OwinStartup(typeof(SignalRSample.Business.Startup))]

2. namespace SignalRSample.Business

3. {

4. public class Startup

5. {

6. public void Configuration(Owin.IAppBuilder app)

7. {

8. Microsoft.AspNet.SignalR.GlobalHost.DependencyResolver.UseSqlServer("Data Source=(localdb)\\v11.0;Initial Catalog=SignalR;Integrated Security=True");

9. app.MapSignalR();

10. }

11. }

12. }

Et le tour est joué ! Le framework se chargera de créer les tables, files d'attente et autres objets SQL dont aura besoin l'application dans la base de données utilisée pour le backplane.

Pour information, voici les autorisations dont aura besoin votre user SQL pour pouvoir utiliser SQL Service Broker.

1. GRANT CREATE TABLE TO [USER]

2. GRANT ALTER ON SCHEMA :: SignalR TO [USER]

3. GRANT INSERT ON SCHEMA :: SignalR TO [USER]

4. GRANT SELECT ON SCHEMA :: SignalR TO [USER]

5. GRANT UPDATE ON SCHEMA :: SignalR TO [USER]

À part l'insertion en base de données, vous ne verrez pas de différences de fonctionnement sur votre site en local, mais le backplane SQL se chargera de redistribuer le message à tous les serveurs. Si vous hébergez votre site sur différentes instances de IIS, toutes les instances recevront les messages postés depuis les autres serveurs. Chaque serveur se chargera ensuite de vérifier si le message est destiné à un de ses clients connectés et effectuera l'envoi du message aux clients cibles.

VII - Conclusion

SignalR est un outil simple d'utilisation et d'implémentation, qui permet rapidement de dynamiser un site web qui a des besoins spécifiques. On peut par exemple utiliser SignalR lorsque le client lance une requête longue au serveur pour le tenir informé de l'évolution de sa requête, ou s'il y a une nécessité d'interaction avec le client lors d'un traitement. Avec les backplanes, cette technologie est également facilement utilisable sur des sites hébergés sur différents serveurs. Vous pouvez trouver le code de l'exemple utilisé dans cet article sur mon github.

VIII - Remerciements

Ce tutoriel a été publié avec l'aimable autorisation de la société Soat.

Nous tenons à remercier Maxy35 pour la relecture orthographique, et Malick SECK pour la mise au gabarit.