Welcome, developers, to this article on building a SignalR chat application with a database in ASP.NET Core MVC. In this post, we will walk through creating a one-on-one chat system using ASP.NET Core Web API and SignalR. Throughout this guide, we will use Visual Studio 2022 and focus on implementing private messaging in a realistic and efficient way.
Introduction to the Project
During a recent project, I needed to implement a private one-on-one chat system. While searching online, I found that most examples provided only code for public chat systems, leaving out examples for private chats. This gap inspired me to write this post to help others implement a one-to-one chat system using ASP.NET Core.
By the end of this post, you will understand how to set up a private chat where messages are delivered only to the intended recipient, ensuring privacy and reliability.
Let's get started!
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.
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.
Step 1: Create a New ASP.NET Core Web API Project
First, open Visual Studio 2022 and create a new ASP.NET Core Web API project. This will be the foundation for our SignalR chat application.
Step 2: Create a Hub Class
Public Chat Code
public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
However, in a one-to-one chat system, we need to ensure that messages are sent privately between users. A simple broadcast using Clients.All sends messages to all connected users, which is not ideal for private conversations.
Step 3: Implementing One-to-One Chat
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); } } }
- Multiple Connections: If a user logs in from multiple devices, each connection is associated with the user’s group. This ensures that messages are delivered to all devices where the user is logged in.
- Connection Management: The OnConnectedAsync and OnDisconnectedAsync methods help manage users joining and leaving the chat groups based on their connection status.
4.Setting Up SignalR in ASP.NET Core
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
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.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");
});
}
}
4.2. Enable CORS For JavaScript app.UseCors(builder => builder .AllowAnyHeader() .AllowAnyMethod() .SetIsOriginAllowed((host) => true) .AllowCredentials() );
5. Client-side Implementation:
Chrome Browser:
<!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
Firefox Browser User
Downlaod Source Code:
Read Similar Articles
- How To Cast List<dynamic> To List<String> In flutter
- Instead Of Delete Trigger In Sql Server With Example
- [Fixed]-"connection to node -1 (localhost/127.0.0.1:9092) could not be established. broker may not be available."
- [Solved]-"exception in thread ""main"" java.lang.nosuchmethoderror: 'org.springframework.core.io.support.springfactoriesloader org.springframework.core.io.support.springfactoriesloader.fordefaultresourcelocation(java.lang.classloader)'"
- Solved Error : Unable to track an instance of type because it does not have a primary key.
- How To Inject Dependency To Static Class In C#
- GroupBy : Entity Framework Using Various Operators Such as `Sum`, `Min`, and `Max`.
- How i can get latitude and longitude an address using Swift ios
- [Solved]-"platformexception (platformexception(null-error, host platform returned null value for non-null return value., null, null))"