Chat Application in Asp .Net MVC Core Using C# With Database


In our previous article, we discussed how to implement SignalR for one-to-one chat in an ASP .NET Core Web API. In this post, we will implement the same concept but in an MVC project, and we will also use a database to save the conversation.

In this tutorial, we will implement how to build a one-to-one chat application in ASP.NET MVC using C#.

Let me tell you that SignalR is a web-based real-time bidirectional communication framework and is very useful when we want real-time communication.SignalR is a very important technology when it comes to developing a chat application, for sending notification popups, or if we want to broadcast any real time message.

In this post will Create a one-to-one chat application in ASP.NET MVC using C# with a database involves several steps. In this post will Create a one-to-one chat application in ASP.NET MVC using C# with a database for that we need follow several steps. 

Database Design

  • We will create a database to store user information, messages, and any other necessary data.

Creating ASP.NET MVC Setup:

  • Create a new ASP.NET Core MVC project in Visual Studio and then set up project structure, with controllers, views, and models.

Authentication and Authorization:

Authentication and Authorization is important for any application to secure the application, so in this step we are going to configure user authentication and authorization for we will use ASP.NET Identity so that users able to register, log in, and see users. for chat.

Creating Chat Interface:

It is obvious that we will need a UI interface to chat, so in this part we will create a chat interface where users can view and send messages, and for that, we are going to use HTML, CSS, and JavaScript to create UI. 

Database Integration:

To maintain user chat history, a database will also be required. In this step, we will configure our database in our ASP.NET MVC application for storing users, conversations, and messages.

Server-Side Logic:

sending and receiving messages we will have to write logic, so in this step, we will implement server-side logic to handle sending and receiving messages and for that we  will use SignalR for real-time communication between clients and the server.

Message Storage:

As we have discussed in the above post, we will store the messages in the database, so in this step, we will write logic to store messages in our database, with the appropriate users and conversations.

Testing and Debugging:

So this is our last and final step, In this step, we will test our application to ensure that messages are sent and received correctly at user end.

So let's start implementation, open your Visual Studio and create a .NET Core MVC project.Open Visual Studio and click on 'Create a new Project' and then select 'ASP.NET Web App with MVC' option

and click on the 'Next' button ,Give the project a name and click on 'Create project' that will create an MVC project for you with basic setup.

I have also provided a download link for the source code at the endpoint post.

So let's start implementation. Open your Visual Studio and add a .NET Core MVC project.
  • Open Visual Studio and click on 'Create a new Project'. Then select 'ASP.NET Web App with MVC' and click on the 'Next' button.
  • Next, give the project a name and click on 'Create project'. It will create an MVC project for you with basic setup.




More post releted to that topic:

Database Integration:

For database connectivity, we are going to use Entity Framework Code First approach. However, we can also use other ORMs like ADO.NET, etc.

Install Entity Framework Core Package:

Install the Entity Framework Core package via NuGet Package Manager or .NET CLI:
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

Define Model Classes & Create DbContext Class:

Create model classes & create a DbContext class that inherits from DbContext and define DbSet properties for each of model classes for our database

using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;

namespace AspCoreMvcSingnalR.DatabaseEntity
{
public class ChatDbContext : DbContext
    {
public DbSet<User> Users { get; set; }
public DbSet<UserChatHistory> UserChatHistory { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Data Source=DESKTOP-MFLCOI2;Initial Catalog=ChatDbV2;User ID=sa;Password=adk@1234;Encrypt=false;");
        }
protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
if (modelBuilder == null)
throw new ArgumentNullException("modelBuilder");
            
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
               
                entityType.SetTableName(entityType.DisplayName());

                
                entityType.GetForeignKeys()
                    .Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade)
                    .ToList()
                    .ForEach(fk => fk.DeleteBehavior = DeleteBehavior.Restrict);
            }

base.OnModelCreating(modelBuilder);
        }
    }
public class User
    {
public Guid UserId { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
    }
    [Table("UserChatHistory", Schema = "dbo")]
public class UserChatHistory
    {
public Guid Id { get; set; }
public virtual User SenderUser { get; set; }
public Guid SenderUserId { get; set; }
public virtual User ReceiverUser { get; set; }
public Guid ReceiverUserId { get; set; }
public string Message { get; set; }
public DateTime CreatedAt { get; set; }
    }
}
In above we have define a DbContext class named 'ChatDbContext', which inherits from Entity Framework Core's DbContext class. and this contains two DbSet properties representing database tables: Users and UserChatHistory. OnModelCreating method is overridden to configure entity mappings and relationships. It sets table names, ensures that cascading deletes are restricted, and configures the foreign key properties.


The UserChatHistory class represents a chat history entity with properties such as Id, 
SenderUser (a navigation property referencing the sender user), SenderUserId (the foreign key for the sender user), ReceiverUser (a navigation property referencing the receiver user), ReceiverUserId (the foreign key for the receiver user), Message, and CreatedAt.

Sql Script:
Below is the SQL create and insert script. You can copy and use it if you want.


CREATE TABLE User (
    UserId UNIQUEIDENTIFIER PRIMARY KEY,
    FullName NVARCHAR(MAX),
    Email NVARCHAR(MAX),
    Password NVARCHAR(MAX)
);

INSERT INTO [User] (UserId, FullName, Email, Password) VALUES
    ('e7f2b6e9-72ab-4a9e-a609-dfc0d20c8efc', 'Rahul Sharma', '[email protected]', '123456'),
    ('fae92718-941d-4a1f-8f7e-103be7e5cf36', 'Priya Patel', '[email protected]', '654321'),
    ('6d56dc2b-015d-4f12-968c-4d4db8e4d314', 'Amit Singh', '[email protected]', '987654');

Create SignalR Hub:

we have Created a folder in our project with name "SignalRHub", added RealTimeChatHub.cs class for SignalR hub. 



RealTimeChatHub.cs class should inherit from the Hub class provided by SignalR. 
Here Code for RealTimeChatHub.cs
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.SignalR;
using System.Diagnostics;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.Reflection.Metadata;
using System.Xml.Linq;
using System.Xml;
using AspCoreMvcSingnalR.DatabaseEntity;
using Microsoft.EntityFrameworkCore;

namespace AspCoreMvcSingnalR.SignalRHub
{
public class RealTimeChatHub:Hub
    {
public override Task OnDisconnectedAsync(Exception exception)
        {
            Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
        }
public override Task OnConnectedAsync()
        {
return base.OnConnectedAsync();
        }
//Create a group for each user to chat separately for private conversations.
public void CreateUserChatGroup(string userId)
        {
var id = Context.ConnectionId;
            Groups.AddToGroupAsync(Context.ConnectionId, userId);
//Adds the client associated with the current connection ID to a specified group.In this case, we are taking `userId` as a parameter for this function,
//Where `userId` represents the name of the group for loggedin user.
//For each user in our database, we create a unique group with ConnectionId
//This function is used to add a client to a specific chat group, enabling them to participate in conversations within that group. 

//We have written this logic, to  ensuring that each user only gets messages for their group
//identified by `userId`. that means logged-in users will only receive messages
//sent to their groups, enhancing privacy and ensuring that users do not receive messages for other users to make sure private chat.
        }
//Send message to user Group
public async Task SendMessageToUserGroup(string senderUserId, string senderName, string receiverUserId, string message)
        {
//Insert message to database then send it to the Client
var optionsBuilder = new DbContextOptionsBuilder<ChatDbContext>();
var _chatDbContext = new ChatDbContext();
            UserChatHistory chatHistory = new UserChatHistory();
            chatHistory.Message = message;
            chatHistory.CreatedAt = DateTime.UtcNow;
            chatHistory.SenderUserId = new Guid(senderUserId);
            chatHistory.ReceiverUserId = new Guid(receiverUserId);
await _chatDbContext.UserChatHistory.AddAsync(chatHistory);
await _chatDbContext.SaveChangesAsync();
await Clients.Group(receiverUserId).SendAsync("ReceiveMessage", senderUserId, senderName, message);
//"Send the message to all users in the specified group. We take the sender's user ID and the receiver's user ID, and then send the message to
//the group of the receiver's user ID to ensure that only users within the specified receiver group receive the message.
//This allows for private communication between users in a one-to-one chat system."
        }
    }
}

Register DbContext & RealTimeChatHub in Program.cs:

In the Program.cs file, register your DbContext in the ConfigureServices method:

using AspCoreMvcSingnalR.DatabaseEntity;
using AspCoreMvcSingnalR.SignalRHub;
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

//AddAuthentication for login & in case of unauhroize user clear auth cookis
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(x => x.LoginPath = "/Home/Index"); 
builder.Services.AddHttpContextAccessor();

//AddSignalR & Hub class

builder.Services.AddSignalR();
builder.Services.AddSingleton<RealTimeChatHub>();

//Add DbContext
builder.Services.AddDbContext<ChatDbContext>();
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapControllers();
app.MapHub<RealTimeChatHub>("/RealTimeChatHub");

app.Run();

Create Database & Tables:

Run Migrations:
Open Package Manager Console in ASP.NET Core project, and run the following command to create and apply migrations:
Add-Migration InitialCreate
Update-Database
First command will create migrations for us and second will create the database object for us.


Setup User Authentication and Authorization:

In this step, we are going to add a login page so that users can log in to the system and then see the list of users available for chat. For that, I have added an action method in the HomeController to handle login requests and also implemented the login page UI in the Index.cshtml view.

HomeController.cs

HomeController.cs

using AspCoreMvcSingnalR.DatabaseEntity;
using AspCoreMvcSingnalR.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Security.Claims;

namespace AspCoreMvcSingnalR.Controllers
{
public class HomeController : Controller
    {
private readonly ILogger<HomeController> _logger;

        ChatDbContext _chatDbContext;
public HomeController(ILogger<HomeController> logger, ChatDbContext chatDbContext)
        {
            _logger = logger;
            _chatDbContext = chatDbContext;
        }

public IActionResult Index()
        {
return View();
        }

//Here write your own code for login 
        [HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
        {
// Check if username and password are correct
var user = _chatDbContext.Users.FirstOrDefault(a => a.Email == model.Email && a.Password == model.Password);
if (user != null)
            {
// Redirect to chat page 
var claims = new List<Claim>() {
new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()),
new Claim(ClaimTypes.Name,user.FullName)
                                    };
//Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme    
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
//Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity    
var principal = new ClaimsPrincipal(identity);

//SignInAsync is a Extension method for Sign in a principal for the specified scheme.    
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
                {
                    IsPersistent = false
                });
return RedirectToAction("Chat", "UserChat");
            }
else
            {
// If login fails, return to the login page with an error message
                ModelState.AddModelError(string.Empty, "Invalid username or password");
return View("Index",model);
            }
        }

public IActionResult Privacy()
        {
return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
        {
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

LoginViewModel.cs

public class LoginViewModel
    {
        [Required]
public string Email { get; set; }

        [Required]
public string Password { get; set; }
    }
    
Index.cshtml

@model LoginViewModel
@{
    ViewData["Title"] = "Home Page";
}

<h1 class="display-4">Enter email and password To Chat</h1>
<form class="form-inline" asp-controller="Home" asp-action="Login" method="post">
    <div class="form-group">
        <label asp-for="Email"></label>
        <input class="form-control" asp-for="Email" />
    </div>
    <div class="form-group">
        <label asp-for="Password"></label>
        <input class="form-control" asp-for="Password" type="password" />
    </div>
    <button type="submit">Login</button>
</form>
As you can see in the controller logic, we have created an action method named 'Login' which handles 
POST requests from the login view. 


This is a simple login logic to demonstrate the functionality. After successfully logging in, we redirect the user to the 'Chat' action method inside the UserChatController. 

Now, let's create that controller so that we can redirect the user to the chat page, where they can see the list of users and the chat interface.

UserChatController.cs

using AspCoreMvcSingnalR.DatabaseEntity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;

namespace AspCoreMvcSingnalR.Controllers
{
    [Authorize]
public class UserChatController : Controller
    {

        ChatDbContext _chatDbContext;
public UserChatController(ChatDbContext chatDbContext)
        {
            _chatDbContext = chatDbContext;
        }
public IActionResult Chat()
        {
            UserChatViewModel userChatViewModel = new UserChatViewModel();

//Get logged in user detail from Claim
            Guid userId = new Guid(User.FindFirstValue(ClaimTypes.NameIdentifier));
string name = User.FindFirstValue(ClaimTypes.Name);

            userChatViewModel.LoggedInUser = new User { UserId = userId, FullName = name };

//Get usera for chat exclude logged In user from the list
            userChatViewModel.Users = _chatDbContext.Users.Where(a => a.UserId != userId).ToList();
return View(userChatViewModel);
        }
public ActionResult GetChatCobversion(Guid userIdToLoadChat)
        {
            Guid loginUserId = new Guid(User.FindFirstValue(ClaimTypes.NameIdentifier));
var chatHistories = _chatDbContext.UserChatHistory.Include("SenderUser")
                    .Include("ReceiverUser").Where(a => (a.ReceiverUserId == loginUserId && a.SenderUserId == userIdToLoadChat)
                   || (a.ReceiverUserId == userIdToLoadChat && a.SenderUserId == loginUserId)).OrderByDescending(a => a.CreatedAt).ToList();
            ViewData["loginUserId"] = loginUserId;
return PartialView("_ChatConversion", chatHistories);
        }
    }
public class UserChatViewModel
    {
public User LoggedInUser { get; set; }
public List<User> Users { get; set; }// Users avialable for Chat
    }
}
The [Authorize] attribute is decorated to the controller, ensuring that only authenticated users can access its actions that means to access that controller user need to loggedin.

The Chat action method is responsible for rendering the chat page view. with in the action method the logged-in user's details obtained from the claims and retrieves a list of users available for chat from the database, excluding the logged-in user.

GetChatConversion() Action Method: 
The GetChatConversion action method is responsible for fetching the chat conversation between the logged-in user and another user specified by userIdToLoadChat. It retrieves chat histories from the database based on the sender and receiver user IDs, orders them by creation date, and returns a partial view "_ChatConversion" with the chat histories._ChatConversion.cshtml partial having logic for showing the conversion.
Chat.cshtml
@model AspCoreMvcSingnalR.Controllers.UserChatViewModel
@{
    ViewData["Title"] = "Chat";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<style type="text/css">
  

    .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: 60px;
        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;
    }

    .overlay {
        position: fixed;
        background: #4646462b;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        z-index: 10000;
    }
    .min-vh-80{
        min-height:80vh;
    }
    .dot {
        background: tomato;
    }

        .dot, .dot:after {
            display: inline-block;
            width: 2em;
            height: 2em;
            border-radius: 50%;
            animation: a 1.5s calc(((var(--i) + var(--o, 0))/var(--n) - 1)*1.5s) infinite;
        }

            .dot:after {
                --o: 1;
                background: currentcolor;
                content: '';
            }

    @@keyframes a 
        0%, 50% {
            transform: scale(0);
        }
</style>

<link rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
<div class="container-fluid">
    <div class="row">
        <div class="col-8 position-relative min-vh-80">
            <h4 style="text-align: center;">Login User: @Model.LoggedInUser.FullName</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-userid="" id="chatlist">
                      
                    </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">
                @if(Model!=null && Model.Users.Any())
                {
foreach(var user in Model.Users)
                    {
                        <div class="user" data-user-id="@user.UserId">
                            <i class="fa fa-user-circle-o" aria-hidden="true"></i> @user.FullName
                        </div>
                    }
                }
            </div>
        </div>
    </div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.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>
<div class="overlay" id="divloader" style="display:none">
    <div class="a" style="--n: 5;position: absolute;top: 50%;left: 50%;">
        <div class="dot" style="--i: 0;"></div>
        <div class="dot" style="--i: 1;"></div>
        <div class="dot" style="--i: 2;"></div>
        <div class="dot" style="--i: 3;"></div>
        <div class="dot" style="--i: 4;"></div>
    </div>
</div>
<script type="text/javascript">
    function Loader(_value) {
if (_value) {
            document.getElementById("divloader").style.display = "block";
        }
else {
            document.getElementById("divloader").style.display = "none";
        }
        setTimeout(function () { document.getElementById("divloader").style.display = "none"; }, 30000);
    }
</script>
<script type="text/javascript">
    $(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-userid", userId);
            $("#chatlist").empty();
            LoadUserChatList(userId);
        });

        $("#close-chat").click(function () {
            $("#chat-window").slideUp();
        });
    });
var senderChatId = "@Model.LoggedInUser.UserId";
const connection = new signalR.HubConnectionBuilder()
        .withUrl("/RealTimeChatHub")
        .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("CreateUserChatGroup", senderChatId);
        } catch (err) {
            console.log(err);
            setTimeout(start, 5000);
        }
    };

    connection.on("ReceiveMessage", async (senderId, senderName,message) => {
var messageBuilder = "<li class='left'><div class='text'><div class='user'>" + GetUserNameWithIcon(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-userid");
var senderName = '@Model.LoggedInUser.FullName';
await connection.invoke("SendMessageToUserGroup", senderChatId, senderName, reciverId, message);
var messageBuilder = "<li class='right'><div class='text'><div class='user'>" + GetUserNameWithIcon(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 GetUserNameWithIcon(userName) {
return '<i class="fa fa-user-circle-o" aria-hidden="true"></i>' + userName;
    }
    function LoadUserChatList(name)
    {
        Loader(true)
        $.ajax({
            url: '/UserChat/GetChatCobversion?userIdToLoadChat=' + name,
            type: 'GET',
            success: function (result) {
                $('#chatlist').html(result);
                Loader(false)
            },
            error: function (xhr, status, error) {
                console.log(xhr.responseText);
                Loader(false)
            }
        });
    }
</script>



In Document Ready Function:

We attaches a click event handler to elements with the class "user" ,representing users in the user list.
When a user is clicked, it retrieves the user's ID and name, updates the chat window's title with the selected user's name, slides down the chat window, and loads the chat history for the selected user by caaling the partial view.

SignalR Connection:

It establishes a connection to the SignalR hub ("/RealTimeChatHub") using the HubConnectionBuilder.
The start() attempts to start the SignalR connection,if successful, it logs a message to the console and 
invokes the "CreateUserChatGroup" method on the hub, passing the sender's chat ID.

SignalR Event Handlers:

The "ReceiveMessage" event handler is invoked when a message is received from the SignalR hub and this function appends the received message to the chat list and displays a notification using toastr js library.

Send Message Function:

This function is called when the user sends a message,it retrieves the message text and the recipient's userid.It invokes the "SendMessageToUserGroup" method on the SignalR hub, passing the sender's chat ID, sender's name, recipient's chat ID, and the message.
_ChatConversion.cshtml
We have created the _ChatConversion.cshtml view to display conversations between two users. So, when a user clicks on any user, we will display their chat history.

@model IEnumerable<AspCoreMvcSingnalR.DatabaseEntity.UserChatHistory>
@{
string loginUserId = ViewData["loginUserId"].ToString();
}
@if (Model.Any())
{
foreach (var chat in Model)
    {
if (chat.SenderUserId.ToString() == loginUserId)
        {
//sender is loggged in user then show message to right side
            <li class="right">
                <div class="text">
                    <div class="user">
                        <i class="fa fa-user-circle-o" aria-hidden="true"></i> @chat.SenderUser.FullName
                    </div>
                </div>
                <small>@chat.Message</small>
            </li>
        }
else
        {
            <li class="left">
                <div class="text">
                    <div class="user">
                        <i class="fa fa-user-circle-o" aria-hidden="true"></i> @chat.SenderUser.FullName
                    </div>
                </div>
                <small>@chat.Message</small>
            </li>
        }
    }
}

Now, let's run the project. It will open the login page. Enter your username and password to log in to the system. 

You can download the source code from GitHub: https://github.com/ramkumar457/SignalRInAspNetMVC/"



After logging in, it will display the list of users. 

Now, copy the URL of our application and open it in two different browsers, let's say Chrome and Edge. Log in with different users in each browser. For example, log in with 'Rahul' in Chrome and with 'Amit Singh' in Edge. 




Then, open their chat and start chatting.