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.
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.
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.
public class ChatHub : Hub { public async Task SendMessage(string user, string message) { await Clients.All.SendAsync("ReceiveMessage", user, message); } }
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); } } }
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() );
<!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>