Chat con SignalR e C#
Questo esempio è una semplice chat realizzata in C# .NET 6 per mostrare il funzionamento e la configurazione di SignalR.
Sul progetto tasto destro Add / Client-Side Library... e nella form
e una classe che rappresenta il messaggio da scambiare ChatDTO
e si completa il tutto con un CSS
questo è il risultato
Il codice completo è disponibile su GitHub - Sgart.Net.SignalR.
Creazione Progetto
Per creare il progetto in Visual Studio selezionare il template ASP.NET Core Web App con framework .NET 6.0 (Long-term support e HTTPS abilitatoLibreria JavaScript
Il passo successivo è aggiungere la libreria JavaScript SignalR.Sul progetto tasto destro Add / Client-Side Library... e nella form
- selezionare il provider unpkg
- inserire la library @microsoft/signalr@latest
- selezionare Choose specific files
- selezionare in dist/browser solo signalr.js e signalr.min.js
- inserirecome target wwwroot/js/signalr
Hub e Message
Per permettere la comunicazione client/server va aggiunta una classe ChatHub che eredita da Microsoft.AspNetCore.SignalR.HubC#: Hubs/ChatHub.cs
using Microsoft.AspNetCore.SignalR;
using Sgart.Net.SignalR.DTO;
namespace Sgart.Net.SignalR.Hubs
{
/// <summary>
/// gestisce la comunicazione client/server
/// </summary>
public class ChatHub : Hub
{
/// <summary>
/// il nome del metodo è il valore da usare
/// lato JavaScript connection.invoke("SendMessage", data)
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public async Task SendMessage(ChatDTO data)
{
// applico un timestamp certo
data.Date = DateTime.UtcNow;
await Clients.All.SendAsync("ReceiveMessage", data);
}
}
}
C#: DTO/ChatDTO.cs
namespace Sgart.Net.SignalR.DTO
{
/// <summary>
/// rappresenta il messaggio da scambiare con SignalR
/// </summary>
public class ChatDTO
{
public string? ClientId { get; set; }
public DateTime Date { get; set; }
public string? User { get; set; }
public string? Message { get; set; }
}
}
Configurazione SignalR
La configurazione di SignalR avviene richiamando il metodo AddSignalR e MapHub per il mapping del canale di comunicazioneC#: Program.cs
using Sgart.Net.SignalR.Hubs;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
// aggiungo il servizio SignalR
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages();
// mapping del canali di comunicazione
app.MapHub<ChatHub>("/chatHub");
app.Run();
Interfaccia utente
Nella pagina di Index va inserito l'HTML che rappresenta la chatHTML: Pages/Index.cshtml
@page
<header class="contact">
<section>
User: <input type="text" id="user-input" value="User 1" />
</section>
</header>
<section class="messages" id="messages-list">
</section>
<form id="message-form" class="form-input">
<input type="text" id="message-input" autocomplete="off" />
<button type="submit" id="send-button">Send</button>
</form>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
[/page]
la pagina di '''Layout'' si semplifica l'[tag]HTML[/tag]
[code=cs,Pages/Shared/_Layouts.cshtml]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Sgart.Net.SignalR</title>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
@*<link rel="stylesheet" href="~/Sgart.Net.SignalR.styles.css" asp-append-version="true" />*@
</head>
<body>
@RenderBody()
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
CSS: wwwroot/css/site.css
:root {
--bg-color-black: #000000;
--bg-color-gray: #444444;
--msg-me-bg-color: #f0ad4e;
--msg-me-text-color: #000000;
--msg-others-bg-color: #600000;
--msg-others-text-color: #eeeeee;
--msg-date-color: #888888;
--msg-user-color: #cccccc;
}
html, body {
height: 100%;
font: 100%/1.5em "Arial", sans-serif;
padding: 0;
margin: 0;
}
html, body, * {
box-sizing: border-box;
}
body {
flex-direction: column;
}
body, header, .msg, form {
display: flex;
}
header, blockquote {
padding: .5em;
margin: 0;
}
header {
background-color: #374455;
color: white;
padding-right: 0;
line-height: 0.5em;
}
.messages, input {
background-color: black;
}
input {
padding-left: .325em;
border: none;
color: white;
flex: 1;
}
.messages {
padding: 1em;
margin: 0;
flex: 1;
overflow: auto;
}
.msg {
position: relative;
margin-bottom: 2em;
color: #fff;
display: flex;
flex-direction: column;
}
.msg-header {
display: flex;
text-align: right;
}
.msg-date {
color: var(--msg-date-color);
font-size: .9em;
}
.msg-user {
color: var(--msg-user-color);
font-size: .9em;
font-weight: bold;
}
.msg-body {
display: flex;
min-height: 3em;
}
.msg-my {
padding-left: 20%;
}
.msg-my .msg-header{
flex-direction: row-reverse;
}
.msg-my .msg-date {
margin-right: 2em;
}
.msg-my .msg-body {
background-color: var(--msg-me-bg-color);
color: var(--msg-me-text-color);
border-radius: .5em;
}
.msg-others {
padding-right: 20%;
}
.msg-others .msg-header {
flex-direction: row;
}
.msg-others .msg-date {
margin-left: 2em;
}
.msg-others .msg-body {
background-color: var(--msg-others-bg-color);
color: var(--msg-others-text-color);
border-radius: .5em;
}
.form-input {
background-color: #727b80;
padding: 1px;
min-height: 40px;
}
.form-input input {
height: 100%;
outline: none;
}
.form-input button {
flex-grow: 0;
text-transform: uppercase;
background-color: var(--bg-color-black);
color: var(--msg-me-bg-color);
font-weight: bold;
border: 0;
}
.form-input button:hover {
background-color: var(--bg-color-gray);
}
Client
Per completare la chat è necessario aggiungere del codice JavaScript per gestire l'invio (sendMessage) è la ricezione (ReceiveMessage) dei messaggiJavaScript: wwwroot/js/chat.js
"use strict";
const htmlEncode = msg => {
if (msg === undefined || msg === null)
return null;
var node = document.createTextNode(msg);
return document.createElement("a").appendChild(node).parentNode.innerHTML.replace(/'/g, "'").replace(/"/g, """);
};
try {
// Disable the send button until connection is established.
document.getElementById("send-button").disabled = true;
const clientUniqueId = Math.floor(Math.random() * 999999999) + "-" + Date.now(); // TODO: da migliorare
document.getElementById("user-input").value = "User " + Math.floor(Math.random() * 9999);
// salvo il riferimento alla text box del messaggi
const elmMessage = document.getElementById("message-input");
// imposto il focus
elmMessage.value = "";
elmMessage.focus();
//---------------------------------------------------------------------------------------
// setup chat
const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
connection.start()
.then(() => document.getElementById("send-button").disabled = false)
.catch(err => console.error(err.toString()));
// receive message
connection.on("ReceiveMessage", data => {
try {
const user = htmlEncode(data.user);
const date = htmlEncode(data.date);
const message = htmlEncode(data.message);
const template = `<div class="msg-header"><span class="msg-user">${user}</span><span class="msg-date">${date}</span></div>`
+ `<div class="msg-body"><blockquote>${message}</blockquote></div>`;
const wrapper = document.createElement("div");
wrapper.className = data.clientId === clientUniqueId ? "msg msg-my" : "msg msg-others";
wrapper.innerHTML = template;
const wrapperMessages = document.getElementById("messages-list");
wrapperMessages.appendChild(wrapper);
// scroll bottom to show last message
wrapperMessages.scrollTop = wrapperMessages.scrollHeight;
} catch (ex) {
console.error(ex, "ReceiveMessage");
}
});
// send message
const sendMessage = event => {
event.preventDefault();
try {
if (document.getElementById("send-button").disabled === true) {
return;
}
if (elmMessage.value.length === 0) {
return;
}
const data = {
clientId: clientUniqueId,
user: document.getElementById("user-input").value,
message: elmMessage.value
};
// SendMessage = nome del metodo nella classe Hub
connection.invoke("SendMessage", data)
.catch(err => console.error(err.toString()));
elmMessage.value = "";
elmMessage.focus();
} catch (ex) {
console.error(ex, "sendMessage");
}
};
// aggancio l'evento di submit al form
document.getElementById("message-form").addEventListener("submit", sendMessage);
} catch (ex) {
console.error(ex, "chat");
}
Il codice completo è disponibile su GitHub - Sgart.Net.SignalR.