Développement d'une Application de Messagerie en Temps Réel avec SignalR et ASP.NET Core

SignalR est un framework de communication en temps réel éprouvé, open-source, conçu pour les plateformes .NET Core et .NET Framework. Il simplifie grandement l'intégration de fonctionnalités en temps réel dans les applications web, en abstraits les complexités sous-jacentes des protocoles de transport.

Introduction à SignalR

Au cœur de SignalR se trouve sa capacité à utiliser différentes technologies de transport : les WebSockets, les Server-Sent Events (SSE) et le Long Polling. Il choisit auotmatiquement la meilleure option disponible en fonction des capacités du client et du serveur, garantissant une communication fluide et réactive. Cette abstraction permet aux développeurs de se concentrer sur la logique métier plutôt que sur les détails techniques de la connexion.

SignalR est polyvalent, supportant une variété de clients, notamment les applications web (JavaScript), les applications de bureau (.NET) et les applications mobiles (grâce à des bibliothèques clientes spécifiques).

Concepts Fondamentaux

Mécanisme de Repli (Fallback)

La robustesse de SignalR repose sur son mécanisme de négociation automatique du transport. Il tente d'abord d'établir une connexion via WebSockets, qui offre la meilleure performance et les coûts les plus bas. Si WebSockets n'est pas supporté (par exemple, par un navigateur ancien ou un proxy), SignalR essaie les Server-Sent Events. En dernier recours, si aucune des options précédentes n'est disponible, il utilise le Long Polling. Ce processus assure que la communication en temps réel est maintenue même dans des environnements contraints.

RPC (Appel de Procédure à Distance)

SignalR utilise le paradigme RPC (Remote Procedure Call) pour permettre aux serveurs d'appeler des fonctions côté client et vice-versa. Cela signifie que le code côté client peut invoquer des méthodes sur le serveur, et le serveur peut appeler des méthodes JavaScript (ou autres langages clients) exécutées dans le navigateur ou l'application cliente, comme si ces méthodes étaient locales.

Hubs

Le Hub est un composant central de SignalR côté serveur. C'est la classe principale que les clients utilisent pour interagir avec le serveur. Il agit comme un point de communication bidirectionnel : il reçoit les appels de méthodes des clients et il est capable d'envoyer des messages ou d'appeler des méthodes sur des clients spécifiques, un groupe de clients, ou tous les clients connectés.

Scalabilité Horizontale

Pour les déploiements à grande échelle, où plusieurs instances de serveurs SignalR sont utilisées, la gestion des connexions clients est cruciale. SignalR prend en charge la scalabilité horizontale via des backplanes (comme Redis, Azure SignalR Service) qui permettent aux messages d'être distribués entre toutes les instances de serveurs. Les "Sticky Sessions" sont souvent employées pour s'assurer qu'un client reste connecté à la même instance de serveur tout au long de sa session, bien que les backplanes réduisent la dépendance à cette approche pour la plupart des scénarios.

Mise en Œuvre d'un Service de Chat en Temps Réel

Nous allons développer une application de chat basique utilisant SignalR avec ASP.NET Core. Le processus implique la configuration des services, la création de modèles de données, l'implémentation du Hub et la configuration du côté client.

1. Configuration des Services Serrveur

Tout d'abord, nous devons enregistrer les services SignalR dans le conteneur d'injection de dépendances de ASP.NET Core, ainsi qu'un service pour gérer l'historique des messages.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddSignalR();
        services.AddSingleton<ChatHistoryService>(); // Service pour stocker les messages
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseStaticFiles();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapHub<ChatHub>("/chat-hub"); // Mappe notre Hub à une URL spécifique
        });
    }
}

2. Modèle de Données pour les Messages

Nous définissons une classe simple pour représenter un message de chat.

public class MessageEntry
{
    public string Sender { get; set; }
    public string MessageContent { get; set; }
    public DateTime SentAt { get; set; }
    public string ClientId { get; set; } // Identifiant du client d'origine (ex: 'Web', 'Mobile')
}

3. Service de Gestion de l'Historique

Ce service (singleton) gérera la persistance en mémoire des messages de chat.

public class ChatHistoryService
{
    private readonly List<MessageEntry> _messageLog;

    public ChatHistoryService()
    {
        _messageLog = new List<MessageEntry>();
    }

    public void AddMessage(MessageEntry message)
    {
        _messageLog.Add(message);
    }

    public IEnumerable<MessageEntry> GetRecentMessages()
    {
        return _messageLog.AsReadOnly();
    }
}

4. Implémentation du Hub SignalR

Le ChatHub est la classe qui expose les méthodes appelables par les clients et qui gère la diffusion des messages. Il interagit avec le ChatHistoryService.

using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class ChatHub : Hub
{
    private readonly ChatHistoryService _historyService;

    public ChatHub(ChatHistoryService historyService)
    {
        _historyService = historyService;
    }

    // Méthode appelée par un client pour envoyer un nouveau message
    public async Task SendNewMessage(string senderName, string textContent, string clientAppId)
    {
        var newMessage = new MessageEntry
        {
            Sender = senderName,
            MessageContent = textContent,
            SentAt = DateTime.UtcNow,
            ClientId = clientAppId
        };

        _historyService.AddMessage(newMessage);

        // Diffuse le nouveau message à tous les clients connectés
        await Clients.All.SendAsync("ReceiveBroadcastMessage", newMessage);
    }

    // Méthode appelée lorsqu'un nouveau client se connecte
    public override async Task OnConnectedAsync()
    {
        // Envoie l'historique des messages au client qui vient de se connecter
        var currentMessages = _historyService.GetRecentMessages();
        await Clients.Caller.SendAsync("ReceiveChatHistory", currentMessages);

        await base.OnConnectedAsync();
    }
}

5. Côté Client (JavaScript)

Pour interagir avec le Hub, nous utilisons la bibliothèque cliente JavaScript de SignalR. Assurez-vous d'avoir installé le package @microsoft/signalr via npm ou d'avoir enclus la bibliothèque via un CDN.

L'interface utilisateur HTML pour le chat n'est pas détaillée ici, mais elle comprendrait des éléments pour afficher les messages et un champ de saisie pour envoyer de nouveaux messages.

let chatConnection;

function initiateChatConnection() {
    chatConnection = new signalR.HubConnectionBuilder()
        .withUrl("/chat-hub") // Doit correspondre à l'URL mappée dans Startup.cs
        .configureLogging(signalR.LogLevel.Information)
        .build();

    // Écoute l'événement 'ReceiveBroadcastMessage' du serveur
    chatConnection.on("ReceiveBroadcastMessage", (message) => {
        console.log("Nouveau message reçu:", message);
        displayMessageInUI(message.sender, message.sentAt, message.messageContent, message.clientId);
    });

    // Écoute l'événement 'ReceiveChatHistory' du serveur
    chatConnection.on("ReceiveChatHistory", (historyData) => {
        console.log("Historique de chat reçu:", historyData);
        for (const msg of historyData) {
            displayMessageInUI(msg.sender, msg.sentAt, msg.messageContent, msg.clientId);
        }
    });

    // Gère la fermeture de la connexion
    chatConnection.onclose(error => {
        console.warn("Connexion SignalR fermée.", error);
        // Tenter de se reconnecter ou informer l'utilisateur
    });

    // Démarre la connexion au Hub
    chatConnection.start()
        .then(() => console.log("Connexion SignalR établie."))
        .catch(err => console.error("Erreur de connexion SignalR: " + err.toString()));
}

// Fonction pour envoyer un message au serveur
function sendMessageToServer(currentUsername, messageText) {
    if (chatConnection && chatConnection.state === signalR.HubConnectionState.Connected) {
        const clientIdentifier = "WebClient"; // Peut être dynamique
        chatConnection.invoke("SendNewMessage", currentUsername, messageText, clientIdentifier)
            .catch(err => console.error("Erreur lors de l'envoi du message: " + err.toString()));
    } else {
        console.warn("La connexion au chat n'est pas établie.");
    }
}

// Fonction utilitaire pour ajouter un message à l'interface utilisateur (à implémenter)
function displayMessageInUI(sender, timestamp, content, clientId) {
    const chatWindow = document.getElementById('chatDisplay'); // Supposons un élément avec cet ID
    if (chatWindow) {
        const msgElement = document.createElement('div');
        msgElement.innerHTML = `<b>${sender}</b> <span style="font-size: 0.8em; color: gray;">(${new Date(timestamp).toLocaleTimeString()})</span>: ${content}`;
        chatWindow.appendChild(msgElement);
        chatWindow.scrollTop = chatWindow.scrollHeight; // Fait défiler vers le bas
    }
}

// Appeler cette fonction au chargement de la page
document.addEventListener('DOMContentLoaded', initiateChatConnection);

// Exemple d'envoi de message via un bouton
document.getElementById('sendButton').addEventListener('click', () => {
    const inputField = document.getElementById('messageInput');
    const usernameField = document.getElementById('usernameInput'); // Supposons un champ pour le nom d'utilisateur
    if (inputField && usernameField && inputField.value.trim()) {
        sendMessageToServer(usernameField.value.trim() || 'Anonyme', inputField.value.trim());
        inputField.value = ''; // Efface le champ après envoi
    }
});

Avec cette configuration, vous pouvez ouvrir plusieurs onglets de navigateur ou instances clientes et observer les messages s'échanger en temps réel, démontrant ainsi la capacité de SignalR à gérer la communication bidirectionnelle.

Étiquettes: ASP.NET Core SignalR temps réel WebSockets C#

Publié le 30 octobre à 12h49