ASP.NET Core SignalR – Simple chat

Update

The code has been updated to use latest SignalR – 6 which works with ASP.NET Core 6

Link to GitHub repository: https://github.com/Ibro/SignalRSimpleChat

  • Angular 5 version: here
  • React.js version: here

 

Introduction

In the last post, we briefly mentioned what SignalR is all about and talked about the history of the SignalR.

In this post, we will be building a simple chat. Yes, a brand new chat. I see you thinking: “Really creative and unique..”. Do forgive me for that! However, in next few posts, we will be building something entirely different, a game!

We will use Razor Pages and plain old vanilla JavaScript, without any frontend JS frameworks or plugins.

Let’s focus on showing how we can quickly put SignalR into action!

 

Setting up ASP.NET Core SignalR

Installing .NET SDK

Let’s first install latest dotnet 2 SDK, which you can find here.

 

I will create a new folder for my project and run the following inside of it:

dotnet new razor

The template "ASP.NET Core Web App" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.

Processing post-creation actions...
Running 'dotnet restore' on G:\SignalRSample\SignalRSample.csproj...

As you can see, with version 2.0 of CLI dotnet restore is triggered just after CLI finishes creating a project. And that is cool, 1 step less for us.

 

Updating and installing packages

Now, we want to add needed SignalR packages.

SignalR is part of both Microsoft’s Metapackages:  Microsoft.AspNetCore.All and Microsoft.AspNetCore.App. Hence, we don’t need to add any nuget packages to our project.

We do need to install the client-side package:

npm install @aspnet/signalr

I will copy the signalr.js from node_modules/@aspnet/signalr/dist/browser folder to wwwroot/lib/signalr folder.

 

Setting up SignalR middleware

Now we will add SignalR service and SignalR middleware to our project.

Inside of our Startup.cs:

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddSignalR();        
        }

And we need to use SignalR middleware inside of our Configure method:

  app.UseSignalR(routes =>
  {
     
  });

 

Adding chat

Let’s add a Chat Hub

namespace SignalRSimpleChat
{
    public class Chat : Hub
    {      
        public async Task Send(string message)
        {
            await Clients.All.SendAsync("Send", message);
        }
    }
}

The code above should be quite simple, it takes a message and broadcasts it to all clients by calling Send method on clients, sending in one parameter message along.

To make the Hub accessible from the client side, change the usage of SignalR middleware in Startup.cs:

 app.UseSignalR(routes =>
 {
   routes.MapHub<Chat>("/chat");
 });

Regarding server-side code, that is all! Really.

Let’s move to client-side.

Inside of our Index Page (.cshtml file), we will add the HTML needed for a chat:

<div id="chat">
    <form id="frm-send-message" action="#">
        <label for="message">Message:</label>
        <input type="text" id="message" />
        <input type="submit" id="send" value="Send" />
    </form>
    <div class="clear">
    </div>
    <ul id="messages"></ul>
</div>

We have an input for message and simple submit button. Also, we have ul element for messages.

For all this to work, we will have to reference and use SignalR client-side library.

Just a line after that we will reference the SignalR client file:

<script src="lib/signalr/signalr.js"></script>

 

Now we can add client-side code:

<script>
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/chat")
        .build();

    connection.start().catch(err => console.error(err.toString()));

    connection.on('Send', (message) => {
        appendLine(message);    
    });

    document.getElementById('frm-send-message').addEventListener('submit', event => {
        let message = document.getElementById('message').value;
        document.getElementById('message').value = '';

        connection.invoke('Send', message);   
        event.preventDefault();
    });

    function appendLine(line, color) {
        let li = document.createElement('li');
        li.innerText = line;
        document.getElementById('messages').appendChild(li);
    };

</script>

First 3 lines are about setting up the transport type (WebSockets in our case) and HttpConnection. We provide the URL of the Hub and transport type.

After that, we start the connection. We listen to the Send event, which will happen once server triggers its Send method. When we get the message from the server, we just add it to the list.

On form submit, we fetch the message from the input and simply invoke Send on server Hub sending in one parameter (message).

Do notice that we will see our own messages appended to the list of the message. Since we are broadcasting the message to everyone on the server, once we submit the form and it triggers Send on the server, immediately after our client-side Send Hub method will be triggered and the message will be appended.

That’s all there is to it.

 

Adding name identifier

Extending our chat by adding a name to the messages is easy.

First, let’s update ChatHub on server-side to receive and broadcast nickname as well:

public class Chat : Hub
{
    public async Task Send(string nick, string message)
    {
        await Clients.All.InvokeAsync("Send", nick, message);
    }
}

 

We will change our HTML a bit. We will add a div with input for a nickname. Once the user enters the nick and continues, we will hide that div and show our old chat div.

Here is the extended HTML:

<div id="entrance">
    <label for="nick">Enter your nickname:</label>
    <input type="text" id="nick" />
    <button onclick="continueToChat()">Continue</button>
</div>

<div id="chat" style="display: none">
    <h3 id="spn-nick"></h3>
    <form id="frm-send-message" action="#">
        <label for="message">Message:</label>
        <input type="text" id="message" />
        <input type="submit" id="send" value="Send" class="send" />
    </form>
    <div class="clear">
    </div>
    <ul id="messages"></ul>
</div>

 

And our JavaScript:

const connection = new signalR.HubConnectionBuilder()
        .withUrl("/chat")
        .build();

connection.start().catch(err => console.error(err.toString()));

connection.on('Send', (nick, message) => {
    appendLine(nick, message);
});

document.getElementById('frm-send-message').addEventListener('submit', event => {
    let message = $('#message').val();
    let nick = $('#spn-nick').text();

    $('#message').val('');

    connection.invoke('Send', nick, message);
    event.preventDefault();
});

function appendLine(nick, message, color) {
    let nameElement = document.createElement('strong');
    nameElement.innerText = `${nick}:`;

    let msgElement = document.createElement('em');
    msgElement.innerText = ` ${message}`;

    let li = document.createElement('li');
    li.appendChild(nameElement);
    li.appendChild(msgElement);

    $('#messages').append(li);
};

function continueToChat() {
    $('#spn-nick').text($('#nick').val());
    $('#entrance').hide();
    $('#chat').show();
}

 

 

Working sample

You can find the working example in this repository.

Stay tuned for more posts on SignalR!

Ibrahim Šuta

Software Consultant interested and specialising in ASP.NET Core, C#, JavaScript, Angular, React.js.