Développons un Twitter-like avec ASP.NET MVC5 et SignalR 2

SignalR est le framework web temps réel de Microsoft. Et c’est une petite merveille. Il permet d’établir des canaux de communication bidirectionnels entre un serveur et ses clients (plus ou moins) en claquant des doigts. Dans cet article, nous allons voir comment implémenter un Twitter-like dans un site ASP.NET MVC5. Et nous l’appellerons TwignalR.
Bootstrapping
Avant de commencer, il vous faut les ingrédients suivants :
- un Visual Studio 2012+ (et même une version express)
- un framework .NET 4.5 ou 4.5.1
- un navigateur “récent” : IE8+, et si vous êtes sous Chrome, Firefox ou Safari, vous devriez être tranquilles
SignalR s’occupera d’établir le meilleur canal de communication possible entre le serveur et le client en testant dans l’ordre :
- les WebSockets
- les Server-Side Events
- le ForeverFrame
- le Long Polling
Une note particulière pour Internet Explorer : Microsoft a fait l’impasse sur le support des Server-Side Events. Si le client est sous IE8 ou 9, SignalR utilisera ForeverFrame ou le Long Polling. A partir d’IE10, on passera sur les WebSockets qui sont de toute façon la solution la plus pérenne.
Il vous faut maintenant un site pour héberger notre Twitter-like. Creez un projet MVC.
Dans le HomeController, ajoutez la méthode suivante :
public ActionResult TwignalR() { return View(); }
Ajoutez ensuite une vue TwignalR.cshtml dans le répertoire Views/Home avec le contenu suivant :
@{ ViewBag.Title = "TwignalR"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>TwignalR</h2> <h3>Un Twitter-like avec ASP.NET SignalR 2</h3>
Un rapide F5 pour vérifier que notre page existe et s’affiche correctement, et on passe à la suite.
Le hub
Avant de commencer, il faut ajouter une référence à SignalR pour le projet MVC via NuGet, à l’aide de l’interface ou de la console NuGet :
install-package Microsoft.AspNet.SignalR
La partie serveur d’une application SignalR s’appelle le Hub. La première version du notre sera extrêmement simple. Ajoutez une nouvelle classe TwignalRHub dont le code est le suivant :
using Microsoft.AspNet.SignalR; using System.Collections.Generic; namespace SignalRChat { public class TwignalRHub : Hub { public void SendToHub(string name, string message) { Clients.All.sendToClients(name, message); } } }
Son but est simple : de recevoir un message de la part d’un client et de le diffuser à tous les clients (y compris l’expéditeur).
Une chose à savoir à propos du hub : vous en faites ce que vous voulez. All est une classe dynamique. La méthode sendToClients(name, message, nbMessages) est donc elle aussi dynamique. On devra la retrouver côté client. Nous le verrons dans la vue.
Afin de démarrer le hub au lancement de l’application, ajoutez la ligne app.MapSignalR(); à la méthode Startup.Configuration() dans Startup.cs à la racine de l’application :
using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(TwitterWithSignalR.Startup))] namespace TwitterWithSignalR { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); app.MapSignalR(); } } }
La vue
Le code de la première version de la vue est le suivant :
@{ ViewBag.Title = "Twitter with SignalR"; } <h2 id="h2Title">Twitter with SignalR</h2> <div class="container"> <input type="text" id="message"> <input type="submit" id="sendmessage" value="Send"> <input type="hidden" id="displayname"> <div style="width: 30%; float: left; border: solid 1px #dad7d7; margin-right: 20px; padding: 5px; " id="discussion" onclick="vider('#h3Messages', 'Fil de discussion', '#nbMessages')"> <h3 id="h3Messages">Fil de discussion</h3> </div> </div> @section scripts { <!--Script references. --> <!--The jQuery library is required and is referenced by default in _Layout.cshtml. --> <!--Reference the SignalR library. --> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22~%2FScripts%2Fjquery.signalR-2.0.1.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>"> <!--Reference the autogenerated SignalR hub script. --> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22~%2Fsignalr%2Fhubs%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>"> <!--SignalR script to update the chat page and send messages.--> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%24(function%20()%20%7B%0A%2F%2F%20Reference%20the%20auto-generated%20proxy%20for%20the%20hub.%0Avar%20chat%20%3D%20%24.connection.twignalRHub%3B%0A%2F%2F%20Create%20a%20function%20that%20the%20hub%20can%20call%20back%20to%20display%20messages.%0Achat.client.sendToClients%20%3D%20function%20(name%2C%20message)%20%7B%0A%24('%23h3Messages').after('%3C%2Fp%3E%0A%3Cdiv%20style%3D%22border%3A%20solid%201px%20%23dad7d7%3B%20margin-top%3A5px%3B%20padding%3A5px%22%3E%3Cp%3E%3Cstrong%3E'%20%2B%20htmlEncode(name)%0A%2B%20'%3C%2Fstrong%3E%3E%20'%20%2B%20htmlEncode(message))%3B%0A%7D%0A%2F%2F%20Get%20the%20user%20name%20and%20store%20it%20to%20prepend%20to%20messages.%0A%24('%23displayname').val(prompt('Enter%20your%20name%3A'%2C%20''))%3B%0A%2F%2F%20Set%20initial%20focus%20to%20message%20input%20box.%0A%24('%23message').focus()%3B%0A%0A%24('%23h2Title').after('%3C%2Fp%3E%0A%3Ch3%3EBienvenue%20'%20%2B%20%24('%23displayname').val()%20%2B%20'%20!%3C%2Fh3%3E%0A%3Cp%3E')%0Awindow.alert('Displayname%20%3D%20'%20%2B%20%24('%23displayname').val())%0A%2F%2F%20Start%20the%20connection.%0A%24.connection.hub.start().done(function%20()%20%7B%0A%24('%23sendmessage').click(function%20()%20%7B%0A%2F%2F%20Call%20the%20Send%20method%20on%20the%20hub.%0Achat.server.sendToHub(%24('%23displayname').val()%2C%20%24('%23message').val())%3B%0A%2F%2F%20Clear%20text%20box%20and%20reset%20focus%20for%20next%20comment.%0A%24('%23message').val('').focus()%3B%0A%7D)%3B%0A%7D)%3B%0A%0A%7D)%3B%0A%2F%2F%20This%20optional%20function%20html-encodes%20messages%20for%20display%20in%20the%20page.%0Afunction%20htmlEncode(value)%20%7B%0Avar%20encodedValue%20%3D%20%24('%3C%2Fp%3E%0A%3Cdiv%20%2F%3E').text(value).html()%3B%0Areturn%20encodedValue%3B%0A%7D%0A%0Afunction%20vider(h3%2C%20texte%2C%20hidden)%20%7B%0A%24(hidden).val(0)%0A%24(h3).text(texte)%0A%7D%0A%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>"> }
C’est un peu long, donc détaillons un peu :
<h2 id="h2Title">Twitter with SignalR</h2> < div class="container"> <input type="text" id="message"> <input type="submit" id="sendmessage" value="Send"> <input type="hidden" id="displayname"> <div style="width: 30%; float: left; border: solid 1px #dad7d7; margin-right: 20px; padding: 5px; " id="discussion" onclick="vider('#h3Messages', 'Fil de discussion', '#nbMessages')"> <h3 id="h3Messages">Fil de discussion</h3> </div> < /div>
Le HTML lui-même est extrêmement simple : un champ texte pour saisir le message, un bouton Send et un champ caché contenant le nom de l’utilisateur connecté. Nous avons ensuite une zone div qui contiendra les messages qui arriveront. Le reste est du Javascript. D’abord ce qui est exécuté au chargement de la page :
$(function () { // Reference the auto-generated proxy for the hub. var chat = $.connection.twignalRHub; // Create a function that the hub can call back to display messages. chat.client.sendToClients = function (name, message, nbMessages) { $('#h3Messages').after(' <div style="border: solid 1px #dad7d7; margin-top:5px; padding:5px"> <strong>' + nbMessages + ' - ' + htmlEncode(name) + '</strong>> ' + htmlEncode(message)); } // Get the user name and store it to prepend to messages. $('#displayname').val(prompt('Enter your name:', '')); // Set initial focus to message input box. $('#message').focus(); $('#h2Title').after(' <h3>Bienvenue ' + $('#displayname').val() + ' !</h3> ') // Start the connection. $.connection.hub.start().done(function () { $('#sendmessage').click(function () { // Call the Send method on the hub. chat.server.sendToHub($('#displayname').val(), $('#message').val()); // Clear text box and reset focus for next comment. $('#message').val('').focus(); }); }); });
On commence par initialiser une variable qui pointe vers le hub. Remarquons que par défaut, SignalR raisonne par convention. Le hub s’appelle TwignalRHub, en Pascal Case. Mais côté Javascript, nous l’appelons avec $.connection.twignalRHub en Camel Case. SignalR s’occupe de faire le mapping entre les deux.
Ensuite, on définit ce qui doit être exécuté à l’appel de la méthode sendToClients du hub. Nous appelons la méthode jquery After() qui va empiler les messages au fur et à mesure qu’ils arrivent juste après la balise #h3Messages sous la forme d’une zone div.
Ensuite, nous demandons à l’utilisateur de donner son nom avec un prompt, et nous stockons cette valeur dans la base hidden #displayname et nous donnons le focus au champ de saisie afin d’engager l’utilisateur à taper un message.
Enfin, après la connexion réussie au hub, nous attachons au clic sur le bouton #sendmessage la méthode sendToHub du hub.
Conclusion
Nous avons maintenant un flux de messages en temps réel. Ouvrez plusieurs fenêtres de navigateur afin de la tester. Vous verrez apparaitre simultanément sur chaque fenêtre les messages postés.
Dans la prochaine partie, nous implémenterons la gestion des mentions comme dans Twitter, ainsi que quelques améliorations ergonomiques.