Welcome developers to the series on SignalR. In this article, we discuss a SignalR chat application with a database in ASP.NET Core MVC. In this project, we will look into implementing SignalR in ASP.NET Core Web API with an example.

Recently, I had the opportunity to work on a project where I needed to implement a one-on-one chat system, primarily focusing on a private chat system. 

In my search on the internet, I found that almost all examples provided code for public chat systems and none shared code for private chat systems. 

More psot

That's why I decided to write an article on that topic. In this post, we will explore the creation of a chat application using the latest version of ASP.NET Core with Visual Studio 2022. So, let's begin understanding how we can create a one-to-one chat system in ASP.NET Core MVC with SignalR.

I have also provided the link to download the source code for that implementation, along with the client-side code, at the end of the post.

1.Create a new ASP.NET Core Web API project: Open Visual Studio and create a new ASP.NET Core Web API.



2.Create a Hub class: Add a new class named ChatingHub.cs to your project. This class will inherit from Hub.

Public Chat Code

However, if we use the simple SendMessage function as shown in the code below, it will work as a public chat where any user sending a message will have it displayed to all users connected to SignalR. 

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Correct Code for One to One Chat

But as mentioned in the title, we want a one-to-one chat system where one user can send a message to a specific receiver, and then the message appears only in the receiver's side. 
To achieve this, we need to create a group for each user in our system and then send a message to that group. The name of each group will be the uniqueChatId of the user. 
In case a user has more than one connection, each connection ID will be added to the user's group. 
This ensures that the message is delivered to all connections associated with the user, for example, if the user is logged in on multiple browsers. 
So Copy pasted the below code in your Hub class
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Diagnostics;

namespace SignalRDemo.Chat
{
    [AllowAnonymous]
    public class ChatingHub : Hub
    {
        public override Task OnDisconnectedAsync(Exception exception)
        {
            Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
            return base.OnDisconnectedAsync(exception);
        }
        public override Task OnConnectedAsync()
        {
            Debug.WriteLine("Client connected: " + Context.ConnectionId);
            return base.OnConnectedAsync();
        }
        //Create Group for each user to chat sepeartely
        public void SetUserChatGroup(string userChatId)
        {
            var id = Context.ConnectionId;
            Debug.WriteLine($"Client {id} added to group " + userChatId);
            Groups.AddToGroupAsync(Context.ConnectionId, userChatId);
        }
        //Send message to user Group
        public async Task SendMessageToGroup(string senderChatId,string senderName, string receiverChatId, string message)
        {
            await Clients.Group(receiverChatId).SendAsync("ReceiveMessage", senderChatId, senderName, receiverChatId, message);
        }
    }
}

Let me explain you each function and usage of that function:

SetUserChatGroup(string userChatId): 

The `Groups.AddToGroupAsync()` function adds the client associated with the current connection ID to a specified group. In this case, we take `userChatId` as a parameter for this function, where `userChatId` represents the name of the group. For each user in the system, we create a unique `userChatId`, which serves as a group for that specific user's chat session.This function is used to add a client to a specific chat group, enabling them to participate in conversations within that group. 

This code allows us to create separate chats for different users, ensuring that each user only receives messages intended for their group identified by `userChatId`. In other words, logged-in users will only receive messages sent to their respective groups, enhancing privacy and ensuring that users do not receive messages for other users.

SendMessageToGroup(string senderChatId,string senderName, string receiverChatId, string message):

  • senderChatId: UniqueChatId of the sender user.
  • senderName: Name of the sender user.
  • receiverChatId: DUniqueChatId of the receiver user.
  • message: Message content need to sent.
In that function we can write logic for inserting chat in database.

Clients.Group(receiverChatId) 
Here we specify that the message should be sent to all clients within the chat group identified by 'receiverChatId'. `SendAsync("ReceiveMessage", ...)` sends the message to all clients in the specified group. 
The message includes parameters such as `senderChatId`, `senderName`, `receiverChatId`, and `message`. "ReceiveMessage" is a client-side method responsible for displaying the received message to the user.
This function enables sending a message from one user's chat group (senderChatId) to another user's chat group (receiverChatId). 
It ensures that only users within the specified receiver group will receive the message, to allow private communication between users in a one-to-one chat system.

The `Microsoft.AspNetCore.SignalR` namespace is typically included by default with .NET projects when creating a web API project. However, if you encounter a missing namespace error, you may need to install the corresponding package based on your .NET Core project version.

You can install the missing package using NuGet Package Manager or the .NET CLI. Make sure to install the appropriate version of  the `Microsoft.AspNetCore.SignalR` package that matches your .NET Core project version to resolve the missing namespace error.

In the NuGet Package Manager Console, run the following command to install the SignalR package:

Install-Package Microsoft.AspNetCore.SignalR -Version
3.Configure SignalR: In the program.cs file, add SignalR services and endpoints.

using SignalRDemo.Chat;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddSignalR();
builder.Services.AddSingleton<ChatingHub>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseCors(builder => builder
    .AllowAnyHeader()
    .AllowAnyMethod()
    .SetIsOriginAllowed((host) => true)
    .AllowCredentials()
  );
app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();
app.MapHub<ChatingHub>("/ChatingHub");
app.Run();
If you have a lower .NET Core version, i.e., less than 7(6,5,3.2 etc), you can configure SignalR in the `Startup.cs` file using the following code:
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //Register the Swagger services      
            services.AddOpenApiDocument(c =>
            {
                c.Title = "AdequateTravel Travel API";
            });
            services.AddCors(o => o.AddPolicy("AllowOrigin", builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyMethod()
                       .AllowAnyHeader();
            }));
            
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddSignalR();
            services.AddDbContext<AdequateDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn")));
            services.AddSingleton<ChatingHub>();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {


            //app.UseMiddleware<UserAuthentication>();

            app.UseStaticFiles(); // For the wwwroot folder
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            
            app.UseCors(builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .SetIsOriginAllowed((host) => true)
                .AllowCredentials()
              );
            app.UseHttpsRedirection();
            // Make sure you call this before calling app.UseMvc()
            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatingHub>("/ChatingHub");
            });
            app.UseMvc();
            //Register the Swagger generator      
            app.UseOpenApi();
            app.UseSwaggerUi3();
        }
    }
4. Enable CORS For JavaScript 
Let me explain that functions:
app.UseCors(builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .SetIsOriginAllowed((host) => true)
                .AllowCredentials()
              );
AllowAnyHeader() function allows  any header to be sent from the client-side for our application.
AllowAnyMethod() function allows any HTTP Verb(GET, POST, etc.) from the client-side for our application.

SetIsOriginAllowed((host) => true) this function pecifies whether the specified origin is allowed to access the url. we have set to allow any origin (true), that means any JavaScript application running on any domain can access our SignalR Hub.
AllowCredentials() this function allows the use of credentials such as cookies, HTTP authentication, and client certificates.

By setting these CORS policies, we are allowing cross-origin requests from any domain, any HTTP method, and any headers, which is suitable for development purposes. 

However, for a real project scenario, my recommendation is to restrict access to only your domain instead of allowing access from all domains. 

This means limiting the allowed origins to only those that you trust, thereby preventing potential security risks associated with cross-origin requests for your application.

5. Client-side Implementation: 

Now we are going to implement the client-side code using e.g., JavaScript with SignalR client library.

In our testing scenario, we will simulate three users by creating an HTML page. We will open this HTML page in three different browsers, such as Chrome, Firefox, and Edge, considering each browser as a different user. We will then proceed to chat with each other using these different browser instances to test our one-to-one chat system.



Here's an example of how we can might implement the client-side code for consuming the SignalR:

Chrome Browser :

Here, I have set the `senderChatId` to "Chrome_User" and made some UI changes. For example, for the Chrome user, Firefox and Edge are available for chat.

As you can see in the image, I have created a chat list displaying the users who are available for chat. When a username is clicked, a chat window is opened for initiating a conversation.



<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />
    <link rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
    <style type="text/css">
        body {
            overflow: hidden;
        }

        .chat-list {
            max-height: 100vh;
            overflow-y: auto;
        }

        .user {
            padding: 10px;
            border-bottom: 1px solid #ddd;
            cursor: pointer;
            display: flex;
            align-items: center;
        }

            .user img {
                width: 40px;
                height: 40px;
                object-fit: cover;
                border-radius: 50px;
                margin-right: 5px;
            }

            .user:hover {
                background-color: #f0f0f0;
            }

        .chat-window {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 350px;
            height: 425px;
            background-color: #fff;
            border: 1px solid #ddd;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            display: none;
        }

        .chat-header {
            background-color: #f0f0f0;
            padding: 10px;
            border-bottom: 1px solid #ddd;
        }

        .chat-body {
            height: 300px;
            overflow-y: auto;
            padding: 10px;
        }

        .chat-footer {
            display: flex;
            padding: 15px 10px;
            border-top: 1px solid #ddd;
        }

        .close {
            float: right;
            cursor: pointer;
        }

        .chat-ui {
            list-style: none;
            padding: 0px;
            margin: 0;
        }

            .chat-ui li {
                display: flex;
                align-items: center;
                margin-bottom: 15px;
            }

                .chat-ui li img {
                    width: 40px;
                    height: 40px;
                    border-radius: 50px;
                    object-fit: cover;
                    object-position: center;
                }

                .chat-ui li .text {
                    display: flex;
                    flex-direction: column;
                }

                    .chat-ui li .text span {
                        font-size: 12px;
                    }

        li.right {
            justify-content: end;
            text-align: right;
        }

        .chatbox {
            margin-bottom: 5%;
            padding: 20px;
            border: 1px solid #e1e1e1;
            box-shadow: 0 15px 35px -15px #e1e1e1;
            border-top: 10px solid #68798f;
        }
        .chatlisthead {
            background: #7ea67e;
            padding: 2px;
        }
    </style>
</head>

<body>

    <div class="container-fluid">
        <div class="row">
            <div class="col-8 position-relative min-vh-100">
                <h4 style="text-align: center;">Login User:<i class="fa fa-chrome" aria-hidden="true"></i> Chrome User</h4>
                <div class="chat-window" id="chat-window">
                    <div class="chat-header">
                        <span class="close" id="close-chat">×</span>
                        <h4>Chat with <span id="chat-user">User</span></h4>
                    </div>
                    <div class="chat-body">
                        <ul class="chat-ui" data-chatuserid="" id="chatlist">
                            <li class="left">
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                                <div class="text">
                                    Hey, 's it going?
                                    <span>2 min</span>
                                </div>

                            </li>
                            <li class="right">

                                <div class="text">
                                    Not too bad, just chilling. You?
                                    <span>2 min</span>
                                </div>
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                            </li>
                        </ul>
                    </div>
                    <div class="chat-footer">
                        <input type="text" class="form-control" id="textmessage" placeholder="Type a message...">
                        <button class="btn btn-primary ml-1" onclick="SendMessage()">Send</button>
                    </div>
                </div>
            </div>
            <div class="col-4 chatbox">
                <h5 class="chatlisthead">Chat List</h5>
                <div class="chat-list">
                    <div class="user" data-user-id="FireFox_User">
                        <i class="fa fa-firefox" aria-hidden="true"></i> FireFox_User

                    </div>
                    <div class="user" data-user-id="Edge_User">
                        <i class="fa fa-edge" aria-hidden="true"></i> Edge_User
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/css/toastr.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/js/toastr.js"></script>
    <script>
        $(document).ready(function () {
            $(".user").click(function () {
                var userId = $(this).data("user-id");
                var userName = $(this).text();
                $("#chat-user").text(userName);
                $("#chat-window").slideDown();
                $("#chatlist").attr("data-chatuserid", userId);
                $("#chatlist").empty();
            });

            $("#close-chat").click(function () {
                $("#chat-window").slideUp();
            });
        });
        var senderChatId = "Chrome_User"; //Chrome user unique chatId
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("https://localhost:7039/ChatingHub")
            .configureLogging(signalR.LogLevel.Information)
            .build();

        async function start() {
            try {
                await connection.start();
                console.log("SignalR Connected.");
                //Creating user group with his unique chatId
                await connection.invoke("SetUserChatGroup", senderChatId);
            } catch (err) {
                console.log(err);
                setTimeout(start, 5000);
            }
        };

        connection.on("ReceiveMessage", async (senderId, senderName, reciverId, message) => {
            var messageBuilder = "<li class=''><div class=''><div class=''>" + GetUserName(senderId, senderName) +"</div><small>"+message+ "</small>" + "</li>"
            $("#chatlist").append(messageBuilder);

            //Showing notifcation to user if get any message
            var notification = "You have received a message from user " + senderName;
            toastr.success(notification);
        });

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

        // Start the connection.
        start();

        async function SendMessage() {
            try {
                var message = $("#textmessage").val();
                if (message) {

                    //Getting reciver unique chatId for sending message to reciver user chat Group so that others user can't recived it
                    var reciverId = $("#chatlist").attr("data-chatuserid");
                    var senderName = senderChatId;
                    await connection.invoke("SendMessageToGroup", senderChatId, senderName, reciverId, message);
                    var messageBuilder = "<li class='right'><div class='text'><div class='user'>" + GetUserName(reciverId, senderName) + "</div><small>" + message + "</small>" + "</li>"
                    $("#chatlist").append(messageBuilder);
                    $("#textmessage").val("");
                }
                else { 

                    toastr.error("Please input message!");
                }
            } catch (err) {
                console.error(err);
            }
        }
        //Function for getting username and icon when binding message to the chat list
        function GetUserName(userChatId, userName) {
            if (userChatId == "Edge_User") {
                return '<i class="fa fa-edge" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "Chrome_User") {
                return '<i class="fa fa-chrome" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "FireFox_User") {
                return '<i class="fa fa-firefox" aria-hidden="true"></i>' + userName;
            }
        }
    </script>
</body>

</html>

Edge Browser User 

Here, I have set the senderChatId to "Edge_User" and made some UI changes. For example, for the Edge user, Firefox and Chrome are available for chat.
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />
    <link rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
    <style type="text/css">
        body {
            overflow: hidden;
        }

        .chat-list {
            max-height: 100vh;
            overflow-y: auto;
        }

        .user {
            padding: 10px;
            border-bottom: 1px solid #ddd;
            cursor: pointer;
            display: flex;
            align-items: center;
        }

            .user img {
                width: 40px;
                height: 40px;
                object-fit: cover;
                border-radius: 50px;
                margin-right: 5px;
            }

            .user:hover {
                background-color: #f0f0f0;
            }

        .chat-window {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 350px;
            height: 425px;
            background-color: #fff;
            border: 1px solid #ddd;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            display: none;
        }

        .chat-header {
            background-color: #f0f0f0;
            padding: 10px;
            border-bottom: 1px solid #ddd;
        }

        .chat-body {
            height: 300px;
            overflow-y: auto;
            padding: 10px;
        }

        .chat-footer {
            display: flex;
            padding: 15px 10px;
            border-top: 1px solid #ddd;
        }

        .close {
            float: right;
            cursor: pointer;
        }

        .chat-ui {
            list-style: none;
            padding: 0px;
            margin: 0;
        }

            .chat-ui li {
                display: flex;
                align-items: center;
                margin-bottom: 15px;
            }

                .chat-ui li img {
                    width: 40px;
                    height: 40px;
                    border-radius: 50px;
                    object-fit: cover;
                    object-position: center;
                }

                .chat-ui li .text {
                    display: flex;
                    flex-direction: column;
                }

                    .chat-ui li .text span {
                        font-size: 12px;
                    }

        li.right {
            justify-content: end;
            text-align: right;
        }
        .chatbox {
            margin-bottom: 5%;
            padding: 20px;
            border: 1px solid #e1e1e1;
            box-shadow: 0 15px 35px -15px #e1e1e1;
            border-top: 10px solid #68798f;
        }
        .chatlisthead {
            background: #7ea67e;
            padding: 2px;
        }
    </style>
</head>

<body>
   
    <div class="container-fluid">
        <div class="row">
            <div class="col-8 position-relative min-vh-100">
                <h4 style="text-align: center;">Login User:<i class="fa fa-edge" aria-hidden="true"></i> Edge_User</h4>
                <div class="chat-window" id="chat-window">
                    <div class="chat-header">
                        <span class="close" id="close-chat">×</span>
                        <h4>Chat with <span id="chat-user">User</span></h4>
                    </div>
                    <div class="chat-body">
                        <ul class="chat-ui" data-chatuserid="" id="chatlist">
                            <li class="left">
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                                <div class="text">
                                    Hey, 's it going?
                                    <span>2 min</span>
                                </div>

                            </li>
                            <li class="right">

                                <div class="text">
                                    Not too bad, just chilling. You?
                                    <span>2 min</span>
                                </div>
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                            </li>
                        </ul>
                    </div>
                    <div class="chat-footer">
                        <input type="text" class="form-control" id="textmessage" placeholder="Type a message...">
                        <button class="btn btn-primary ml-1" onclick="SendMessage()">Send</button>
                    </div>
                </div>
            </div>
            <div class="col-4 chatbox">
                <h5 class="chatlisthead">Chat List</h5>
                <div class="chat-list">
                    <div class="user" data-user-id="FireFox_User">
                        <i class="fa fa-firefox" aria-hidden="true"></i> FireFox_User

                    </div>
                    <div class="user" data-user-id="Chrome_User">
                        <i class="fa fa-chrome" aria-hidden="true"></i> Chrome_User
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/css/toastr.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/js/toastr.js"></script>
    <script>
        $(document).ready(function () {
            $(".user").click(function () {
                var userId = $(this).data("user-id");
                var userName = $(this).text();
                $("#chat-user").text(userName);
                $("#chat-window").slideDown();
                $("#chatlist").attr("data-chatuserid", userId);
                $("#chatlist").empty();
            });

            $("#close-chat").click(function () {
                $("#chat-window").slideUp();
            });
        });
        var senderChatId = "Edge_User"; //Edge_User unique chatId
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("https://localhost:7039/ChatingHub")
            .configureLogging(signalR.LogLevel.Information)
            .build();

        async function start() {
            try {
                await connection.start();
                console.log("SignalR Connected.");
                await connection.invoke("SetUserChatGroup", senderChatId);
            } catch (err) {
                console.log(err);
                setTimeout(start, 5000);
            }
        };

        connection.on("ReceiveMessage", async (senderId, senderName, reciverId, message) => {
            var messageBuilder = "<li class=''><div class=''><div class=''>" + GetUserName(senderId, senderName) + "</div><small>" + message + "</small>" + "</li>"
            $("#chatlist").append(messageBuilder);
            var notification = "You have received a message from user " + senderName;
            toastr.success(notification);
        });

        connection.onclose(async () => {
            await start();
        });
        // Start the connection.
        start();
        async function SendMessage() {
            try {
                var message = $("#textmessage").val();
                if (message) {
                    var reciverId = $("#chatlist").attr("data-chatuserid");
                    var senderName = senderChatId;
                    await connection.invoke("SendMessageToGroup", senderChatId, senderName, reciverId, message);
                    var messageBuilder = "<li class=''><div class=''><div class=''>" + GetUserName(reciverId, senderName) + "</div><small>" + message + "</small>" + "</li>"
                    $("#chatlist").append(messageBuilder);
                    $("#textmessage").val("");
                }
                else {
                    toastr.error("Please input message!");
                }

            } catch (err) {
                console.error(err);
            }
        }
        function GetUserName(userChatId, userName) {
            if (userChatId == "Edge_User") {
                return '<i class="fa fa-edge" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "Chrome_User") {
                return '<i class="fa fa-chrome" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "FireFox_User") {
                return '<i class="fa fa-firefox" aria-hidden="true"></i>' + userName;
            }
        }
    </script>
</body>

</html>

Firefox Browser User 

Here, I have set the `senderChatId` to "FireFox_User" and made some UI changes. For example, for the Firefox user, Edge and Chrome are available for chat.
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200" />
    <link rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
    <style type="text/css">
        body {
            overflow: hidden;
        }

        .chat-list {
            max-height: 100vh;
            overflow-y: auto;
        }

        .user {
            padding: 10px;
            border-bottom: 1px solid #ddd;
            cursor: pointer;
            display: flex;
            align-items: center;
        }

            .user img {
                width: 40px;
                height: 40px;
                object-fit: cover;
                border-radius: 50px;
                margin-right: 5px;
            }

            .user:hover {
                background-color: #f0f0f0;
            }

        .chat-window {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 350px;
            height: 425px;
            background-color: #fff;
            border: 1px solid #ddd;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            display: none;
        }

        .chat-header {
            background-color: #f0f0f0;
            padding: 10px;
            border-bottom: 1px solid #ddd;
        }

        .chat-body {
            height: 300px;
            overflow-y: auto;
            padding: 10px;
        }

        .chat-footer {
            display: flex;
            padding: 15px 10px;
            border-top: 1px solid #ddd;
        }

        .close {
            float: right;
            cursor: pointer;
        }

        .chat-ui {
            list-style: none;
            padding: 0px;
            margin: 0;
        }

            .chat-ui li {
                display: flex;
                align-items: center;
                margin-bottom: 15px;
            }

                .chat-ui li img {
                    width: 40px;
                    height: 40px;
                    border-radius: 50px;
                    object-fit: cover;
                    object-position: center;
                }

                .chat-ui li .text {
                    display: flex;
                    flex-direction: column;
                }

                    .chat-ui li .text span {
                        font-size: 12px;
                    }

        li.right {
            justify-content: end;
            text-align: right;
        }
        .chatbox {
            margin-bottom: 5%;
            padding: 20px;
            border: 1px solid #e1e1e1;
            box-shadow: 0 15px 35px -15px #e1e1e1;
            border-top: 10px solid #68798f;
        }
        .chatlisthead {
            background: #7ea67e;
            padding: 2px;
        }
    </style>
</head>

<body>
  
    <div class="container-fluid">
        <div class="row">
            <div class="col-8 position-relative min-vh-100">
                <h4 style="text-align: center;">Login User:<i class="fa fa-firefox" aria-hidden="true"></i> FireFox_User</h4>
                <div class="chat-window" id="chat-window">
                    <div class="chat-header">
                        <span class="close" id="close-chat">×</span>
                        <h4>Chat with <span id="chat-user">User</span></h4>
                    </div>
                    <div class="chat-body">
                        <ul class="chat-ui" data-chatuserid="" id="chatlist">
                            <li class="left">
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                                <div class="text">
                                    Hey, 's it going?
                                    <span>2 min</span>
                                </div>

                            </li>
                            <li class="right">

                                <div class="text">
                                    Not too bad, just chilling. You?
                                    <span>2 min</span>
                                </div>
                                <span class="material-symbols-outlined">
                                    person
                                </span>
                            </li>
                        </ul>
                    </div>
                    <div class="chat-footer">
                        <input type="text" class="form-control" id="textmessage" placeholder="Type a message...">
                        <button class="btn btn-primary ml-1" onclick="SendMessage()">Send</button>
                    </div>
                </div>
            </div>
            <div class="col-4 chatbox">
                <h5 class="chatlisthead">Chat List</h5>
                <div class="chat-list">
                    <div class="user" data-user-id="Chrome_User">
                        <i class="fa fa-chrome" aria-hidden="true"></i> Chrome_User
                    </div>
                    <div class="user" data-user-id="Edge_User">
                        <i class="fa fa-edge" aria-hidden="true"></i> Edge_User
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/css/toastr.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.0.1/js/toastr.js"></script>
    <script>
        $(document).ready(function () {
            $(".user").click(function () {
                var userId = $(this).data("user-id");
                var userName = $(this).text();
                $("#chat-user").text(userName);
                $("#chat-window").slideDown();
                $("#chatlist").attr("data-chatuserid", userId);
                $("#chatlist").empty();
            });

            $("#close-chat").click(function () {
                $("#chat-window").slideUp();
            });
        });
        var senderChatId = "FireFox_User";  //FireFox_User unique chatId
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("https://localhost:7039/ChatingHub")
            .configureLogging(signalR.LogLevel.Information)
            .build();

        async function start() {
            try {
                await connection.start();
                console.log("SignalR Connected.");
                await connection.invoke("SetUserChatGroup", senderChatId);
            } catch (err) {
                console.log(err);
                setTimeout(start, 5000);
            }
        };

        connection.on("ReceiveMessage", async (senderId, senderName, reciverId, message) => {
            debugger;
            var messageBuilder = "<li class=''><div class=''><div class=''>" + GetUserName(senderId, senderName) + "</div><small>" + message + "</small>" + "</li>"
            $("#chatlist").append(messageBuilder);
            var notification = "You have received a message from user " + senderName;
            toastr.success(notification);
        });

        connection.onclose(async () => {
            await start();
        });
        // Start the connection.
        start();
        async function SendMessage() {
            try {
                var message = $("#textmessage").val();
                if (message) {
                    var reciverId = $("#chatlist").attr("data-chatuserid");
                    var senderName = senderChatId;
                    await connection.invoke("SendMessageToGroup", senderChatId, senderName, reciverId, message);
                    var messageBuilder = "<li class=''><div class=''><div class=''>" + GetUserName(reciverId, senderName) + "</div><small>" + message + "</small>" + "</li>"
                    $("#chatlist").append(messageBuilder);
                    $("#textmessage").val("");
                }
                else {
                    toastr.error("Please input message!");
                }

            } catch (err) {
                console.error(err);
            }
        }
        function GetUserName(userChatId, userName) {
            if (userChatId == "Edge_User") {
                return '<i class="fa fa-edge" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "Chrome_User") {
                return '<i class="fa fa-chrome" aria-hidden="true"></i>' + userName;
            }
            else if (userChatId == "FireFox_User") {
                return '<i class="fa fa-firefox" aria-hidden="true"></i>' + userName;
            }
        }
    </script>
</body>

</html>

Downlaod Source Code:

If you need the source code, you can find it on GitHub. 
Here is the link: https://github.com/ashokadk457/SignalROneToOneChat

Now, let's open all three pages in different browsers depending on the client-side code, and start chatting. 

For instance, Edge and Chrome want to chat. In this scenario, from Chrome's browser  user will click on edge user name in the chat list and it will open chat box, and Edge's browser user will click on in Chrome user in chat list. Now you can then send messages to from one client to other client,
and you can verify this message will show to Firefox browser user.

For instance, Firefox and Chrome want to chat. In this scenario, from Chrome's browser  user will click on Firefox user name in the chat list and it will open chat box, and Firefox's browser user will click on in Chrome user in chat list. 
Now you can send messages from one client  browser to other client browser,and you can verify this message will show to Edge's browser user.