Add a SignalR hub to ASP.NET Core & connect using JavaScript

Published: Monday 22 July 2024

SignalR is a powerful software library that allows you to send and receive messages from a client and server in real-time.

We are going to add SignalR to an ASP.NET Core app and use the client to connect to the SignalR hub. We'll also look at how to send messages and how we can receive them.

Create a SignalR hub

The first steps involve creating a SignalR hub.

C# coding challenges

C# coding challenges

Our online code editor allows you to compile the answer.

In the ASP.NET Core app, we create a new class called ChatHub. To make this a SignalR hub, we need to inherit the Hub class which is imported from the Microsoft.AspNetCore.SignalR namespace.

The Hub class has a virtual method called OnConnectedAsync. We are going to override this and send a messages to all connected clients when a new client has connected.

The method name will be called ReceiveMessage and we'll pass in a parameter with the message. The client will listen out for this method to be invoked.

In-addition, we are going to set up a SendMessage method. This will be invoked from the client and it will send a message back to that particular client saying that the message has been received.

// ChatHub.cs
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        await Clients.All.SendAsync("ReceiveMessage", $"Received the message: Another connection has been added.");
    }

    public async Task SendMessage(string message)
    {
        await Clients.Client(Context.ConnectionId).SendAsync("ReceiveMessage", $"Received the message: {message}");
    }
}

Sending messages to clients

If we want to send a message to all clients, we can call Clients.All and then the SendAsync method to invoke a method with parameters.

However, we may only want to send a message to a specific client. Each client connected to a SignalR hub has a connection ID assigned to them. We can get the connection ID from the hub context and pass it in as a parameter for Clients.Client.

So to send a message to the connected client, we would call Clients.Client(Context.ConnectionId), followed by the SendAsync method and then the method and parameters that we wish to invoke.

Add SignalR to services and map the hub

SignalR needs to be added to the services in the ASP.NET Core app. As well as that, the SignalR hub needs to be mapped with an endpoint.

This is done in Program.cs and we call the AddSignalR extension method that is part of the IServiceCollection instance.

To map the SignalR hub, we call the MapHub extension method from the WebApplication instance. We pass in the SignalR hub as the generic type. In this instance, that would be ChatHub. Then it needs to be mapped to a pattern or endpoint. This will be specified as /chathub and will be used to connect to the SignalR hub from the client.

// Program.cs
using RoundTheCode.SignalR.Hubs;

var builder = WebApplication.CreateBuilder(args);

...
builder.Services.AddSignalR(); // Adds SignalR to the IServiceCollection instance

var app = builder.Build();

...

app.MapHub<ChatHub>("/chathub"); // Maps the ChatHub with a endpoint of /chathub.

...

app.Run();

Front-end set up

We have an HTML page that will be responsible for sending and receiving messages back from the server.

The page has an input box that allows you to add your message. This has an ID of Message. As well as that, there is a Send Message button to send the message to the server which has an ID of SendMessage.

Finally, there is a <ul> tag that will store all the messages and has an ID of Messages.

The file also contains a reference to the SignalR JavaScript library as well as a custom SignalR JavaScript file (called signalr.js) which we will use to code the SignalR functionality.

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" 
        href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" 
    >
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-12">
                <input id="Message" value="" />
                <button class="btn btn-primary" id="SendMessage" 
                        type="button">Send message</button>
            </div>
        </div>
        <div class="row">
            <div class="col-12">
                <ul id="Messages">

                </ul>
            </div>
        </div>
    </div>
    <script 
        src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js">
    </script>
    <script src="/js/signalr.js"></script>
</body>
</html>
Send a message using SignalR

Send a message using SignalR

Connecting to SignalR hub

As stated, the index.html page has a reference to the signalr.js JavaScript file. We'll use this JavaScript file to connect to the SignalR hub.

We need to create a new signalR.HubConnectionBuilder and specify a URL. This will be set as /chathub so it's the same URL that we mapped the SignalR hub in the ASP.NET Core app.

We can also configure logging before we build the connection.

To start the connection, we can call the start method that is in the connection instance.

We may wish to try and re-established the SignalR connection if it's closed just say because we lose the connection. To do that, we can set the onclose method in the connection instance, and restart it from there.

// signalr.js
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

var Start = async () => {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(Start, 5000);
    }
};

// Start the connection.
Start();

connection.onclose(async () => {
    await Start();
});

A message stating SignalR Connected should be logged to the console application. If it doesn't, the client has been unable to connect to SignalR meaning that something is not set up correctly.

Sending a message to the SignalR hub

With the connection established, we can now send a message to the SignalR hub.

To do that, we get the connection instance and call the invoke method. The invoke method allows you to pass in the method name as a parameter, followed by any other parameters for that method.

In the ChatHub class, we created the SendMessage method:

// ChatHub.cs
public class ChatHub : Hub
{
	...

    public async Task SendMessage(string message)
    {
        await Clients.Client(Context.ConnectionId).SendAsync("ReceiveMessage", $"Received the message: {message}");
    }
}

We are now going to invoke this method by calling connection.invoke, adding SendMessage as the method name and the message we inputted into the input box as the message.

This will be added to the method and to call it, we get the SendMessage ID element in JavaScript and add a click listener. We get the Message ID element which is the input box and check it has a value. If it does, we send the message.

// signalr.js
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

var Start = async () => {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(Start, 5000);
    }
};

// Start the connection.
Start();

connection.onclose(async () => {
    await Start();
});

// Sends the message to SignalR
var SendMessage = async (message) => {
    await connection.invoke("SendMessage", message);
}

// Sends the message when the SendMessage ID element has been clicked
document.getElementById('SendMessage').addEventListener('click', async () => {
    var message = document.getElementById('Message');

    if (message && message.value) {
        await SendMessage(message.value);
        message.value = '';
    }
});

Receiving a message back from the server

We also want to be able to receive a message back from the server.

In the ChatHub class, we called SendAsync("ReceiveMessage", message) which invokes the ReceiveMessage method to the clients specified. We need to set up the client so it can listen out for calls from the ReceiveMessage method.

This is done by calling the on method in the connection instance, specifying the method name and any parameters we are expecting.

We'll do a look up for our messages element where we store each message as a bullet point. If it exists, we create a new li element and add the message as the inner text. Then we append the li element to the Messages ID element which adds it as a bullet point.

// signalr.js
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

var Start = async () => {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(Start, 5000);
    }
};

// Start the connection.
Start();

connection.onclose(async () => {
    await Start();
});

var SendMessage = async (message) => {
    await connection.invoke("SendMessage", message);
}

document.getElementById('SendMessage').addEventListener('click', async () => {
    var message = document.getElementById('Message');

    if (message && message.value) {
        await SendMessage(message.value);
        message.value = '';
    }
});

connection.on("ReceiveMessage", (message) => {
    var messages = document.getElementById('Messages');

    if (messages) {
        var bulletPoint = document.createElement('li');
        bulletPoint.innerText = message;

        messages.appendChild(bulletPoint);
    }
});

Watch the video

Watch the video where we show you how to create a SignalR hub, add it to ASP.NET Core and connect to it from the client so we can send and receive messages.

As well as that, we'll give you a demo of SignalR connected successfully and show you how it can send and receive messages.