Welcome to a SignalR guide for implementing a chat application with features such as displaying user online/offline status, marking message status as seen, and maintaining chat conversation history in the database. 

How can I get online and offline users using signalR?

To implement online and offline status tracking along with message seen status using SignalR, we can follow these steps:
public class ChatHub : Hub
{
    // Dictionary to store online status of users
    private readonly Dictionary<string, bool> _onlineUsers = new Dictionary<string, bool>();

    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
    
    public async Task MarkMessageAsSeen(string messageId)
    {
        //Write logic for MarkMessageAsSeen
    }

    public override async Task OnConnectedAsync()
    {
        _onlineUsers[Context.ConnectionId] = true;
        await Clients.All.SendAsync("UserStatusChanged", GetUserStatuses());
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        _onlineUsers.Remove(Context.ConnectionId);
        await Clients.All.SendAsync("UserStatusChanged", GetUserStatuses());
        await base.OnDisconnectedAsync(exception);
    }

    private Dictionary<string, bool> GetUserStatuses()
    {
        return _onlineUsers.ToDictionary(entry => entry.Key, entry => entry.Value);
    }
}

In in above we have ChatHub class, we've set up a hub to manage real-time communication between users. We've cerated a private dictionary named `_onlineUsers` to keep track of the online status of users, mapping their unique identifiers to a boolean indicating their online status.

When a user sends a message using the `SendMessage` method, we send it to all clients by invoking the `ReceiveMessage` method for all clients.

For handling the marking of messages as seen, we've defined the `MarkMessageAsSeen` method. Whenever a user connects to the hub, we update their online status to true and inform all clients about the change by invoking the `UserStatusChanged` method and passing the current user statuses obtained from `GetUserStatuses`. We make sure to call the base `OnConnectedAsync` method to ensure proper hub connection handling.

Similarly, when a user disconnects, we remove their entry from the `_onlineUsers` dictionary, update user statuses, and inform all clients about the change. Again, we call the base `OnDisconnectedAsync` method to handle disconnection properly.

The `GetUserStatuses` method simply retrieves the current user statuses from the `_onlineUsers` dictionary and returns them as a dictionary mapping user identifiers to their online status.

ChatHub class manages user connections, disconnections, and message broadcasting, for a robust real-time chat application.

We have also provided a download link for the source code at the end of post. You can download the source code.

SignalR with ASP.NET – One-to-one and Group Chat,Tracking Online/Offline Users, Seen/Not Seen and Chat History with Database Record 

Let's get started! , Recently, we've been developing a social network application aimed at connecting solo travelers worldwide, facilitating chat and trip planning. We're considering incorporating a couple of new features into the application:

  1. Tracking online users.
  2. Implementing chat functionality, including one-to-one and group chat.
We've explored SignalR and found it suitable for our requirements. Since we're using ASP.NET Core, we're planning to use SignalR for real-time communication. One question we have is whether SignalR can also be used to track online users. Our idea is to have clients call a server-side function at regular intervals to indicate their online status, which would update a corresponding 'isOnline' flag in the database through a SignalR hub method. When a client disconnects, we would unset this flag. Would this approach work? 


If you're also working on a chat application and have similar questions or concerns, you've come to the right place. Let's discuss each point in detail.

In this post, we will cover the step-by-step process of setting up an ASP.NET Core MVC project, designing the database, creating a DbContext, setting up a SignalR Hub for chat functionality, configuring user authentication and authorization, and creating the user interface for the chat conversation, including showing a list of online users and enabling users to view chat conversation history. 

Step 1: Adding Project

  • Open Visual Studio.
  • Create a new ASP.NET Core Web Application project.
  • Select "ASP.NET Core Web Application" template.
  • Choose the desired project name and location.
  • Select "Web Application (Model-View-Controller)" template.
  • Click "Create" to create the project.

Step 2: Database Design & Creating DbContext

  • Create database schema for users and messages.
  • Create a DbContext class that inherits from DbContext and includes DbSet properties for User and Message History.
ChatDbContext.cs
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=OnlineChatDb;User ID=sa;Password=adk@1234;Encrypt=false;");
        }
    }
    public class User
    {
        public Guid UserId { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public bool IsOnline { get; set; }
        public DateTime? DisconnectedAt { 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 bool IsSeen { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime? SeetAt { get; set; }
    }
}

The database context ChatDbContext for our chat application, along with the User and UserChatHistory entities. The User entity represents user information, while the UserChatHistory entity represents the chat history between users. 

Step 3: Creating SignalR Hub for Chat

  • Create a SignalR Hub class that inherits from Hub.
  • Implement methods in the Hub to handle sending and receiving messages.
  • Implement methods to track online/offline status of users.

 [Authorize]
    public class RealTimeChatHub : Hub
    {
        public override async Task OnDisconnectedAsync(Exception exception)
        {
            using (var _chatDbContext = new ChatDbContext())
            {
                //Get logged UserId From the claim
                Guid userId = new Guid(Context.User.FindFirstValue(ClaimTypes.PrimarySid));
                //Set User status to offline 
                var user = _chatDbContext.Users.FirstOrDefault(a => a.UserId == userId);
                user.IsOnline = true;
                user.DisconnectedAt = DateTime.UtcNow;
                _chatDbContext.SaveChanges();
            }
            await Clients.All.SendAsync("UserStatusChanged", "StateChangedLoadLatestChatList");
            Debug.WriteLine("Client disconnected: " + Context.ConnectionId);
            await base.OnDisconnectedAsync(exception);
        }
        public override async Task OnConnectedAsync()
        {
            using (var _chatDbContext = new ChatDbContext())
            {
                //Get logged UserId From the claim
                Guid userId = new Guid(Context.User.FindFirstValue(ClaimTypes.PrimarySid));
                //Set User status to online
                var user = _chatDbContext.Users.FirstOrDefault(a => a.UserId == userId);
                user.IsOnline = true;
                _chatDbContext.SaveChanges();
            }
            await Clients.All.SendAsync("UserStatusChanged", "StateChangedLoadLatestChatList");
            await 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);
        }
        //Mark message as seen
        public async Task MarkMessageAsSeen(Guid senderUserId)
        {
            using (var _chatDbContext = new ChatDbContext())
            {
                //Set MessageAsSeen
                Guid userId = new Guid(Context.User.FindFirstValue(ClaimTypes.PrimarySid));
                var userChatHistories = _chatDbContext.UserChatHistory.Where(a => a.ReceiverUserId == userId && a.SenderUserId == senderUserId
                && a.SeetAt == null).ToList();
                if (userChatHistories?.Any() ?? false)
                {
                    foreach (var userChat in userChatHistories)
                    {
                        userChat.SeetAt = DateTime.UtcNow;
                        userChat.IsSeen = true;
                    }
                    _chatDbContext.SaveChanges();
                }
            }
        }
        //Send message to SendMessageToUserChatGroup
        public async Task SendMessageToUserChatGroup(string senderUserId, string senderName, string receiverUserId, string message)
        {
            //Insert message to database then send it to the Client
            var _chatDbContext = new ChatDbContext();
            UserChatHistory chatHistory = new UserChatHistory();
            chatHistory.Message = message;
            chatHistory.IsSeen = false;
            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",
                new ReceiveMessageDTO
                {
                    Message = message,
                    SenderName = senderName,
                    SenderUserId = senderUserId
                });
        }
    }
    public class ReceiveMessageDTO
    {
        public string Message { get; set; }
        public string SenderUserId { get; set; }
        public string SenderName { get; set; }
    }
In part , we've implemented a SignalR hub called RealTimeChatHub, which handles real-time communication between clients and the server. We manage user connections and disconnections, update their online status in the database, create chat groups for private conversations, mark messages as seen, and send messages between users. we've defined a ReceiveMessageDTO class to structure the data being sent when receiving messages. 
Register DbContext & SignalR Hub in Program.cs
using AspCoreMvcSingnalR.DatabaseEntity;
using AspCoreMvcSingnalR.SignalRHub;
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);

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

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

builder.Services.AddSignalR();
builder.Services.AddSingleton<RealTimeChatHub>();
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();

Step 4: Setup User Authentication and Authorization

  • Configure ASP.NET Core Identity for user authentication.
  • Implement login, registration, and logout functionality.
  • Ensure that SignalR hub authorization is configured correctly.

HomeController.cs
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.PrimarySid, 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);
            }
        }
    }
Index.cshtml
public class LoginViewModel
    {
        [Required]
        public string Email { get; set; }

        [Required]
        public string Password { get; set; }
    }
    
@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>
    
We have a HomeController responsible for handling user interactions related to logging in. We inject the logger and the ChatDbContext into the controller. The Index action method renders the login page. The Login action method is call when the user submits the login form. and then we verify the provided credentials against the database. If the credentials are correct, we create claims for the user, sign them in, and redirect them to the chat page. If the login fails, we return to the login page with an error message.

Step 5: Creating UI for Chatting Conversion

  • Design UI components for displaying online users and chat conversation history.
  • JavaScript logic to connect to the SignalR hub.
  • Functionality to display online users and initiate chat conversations.
  • Implement functionality to send and receive messages.
  • Functionality to display message.
UserChatController.cs
[Authorize]
    public class UserChatController : Controller
    {

        ChatDbContext _chatDbContext;
        public UserChatController(ChatDbContext chatDbContext)
        {
            _chatDbContext = chatDbContext;
        }
        public IActionResult Chat()
        {
            User loggedInUser = new User();
            //Get logged in user detail from Claim
            Guid userId = new Guid(User.FindFirstValue(ClaimTypes.PrimarySid));
            string name = User.FindFirstValue(ClaimTypes.Name);
            loggedInUser = new User { UserId = userId, FullName = name };
            return View(loggedInUser);
        }
        public ActionResult ChatList()
        {
            List<User> users = new List<User>();
            Guid userId = new Guid(User.FindFirstValue(ClaimTypes.PrimarySid));
            users = _chatDbContext.Users.Where(a => a.UserId != userId).ToList();
            return PartialView("_ChatList", users);
        }
        public ActionResult GetChatCobversion(Guid userIdToLoadChat)
        {
            ChatConversionModel chatConversion = new ChatConversionModel();
            Guid loginUserId = new Guid(User.FindFirstValue(ClaimTypes.PrimarySid));

            chatConversion.ChatUser = _chatDbContext.Users.FirstOrDefault(a => a.UserId == userIdToLoadChat);

            chatConversion.UserChatHistories = _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", chatConversion);
        }
    }
    public class ChatConversionModel
    {
        public User? ChatUser { get; set; }  
        public List<UserChatHistory>? UserChatHistories { get; set; }
    }
we have a UserChatController responsible for managing user chat functionality. The Chat action method renders the main chat page, where we retrieve details of the logged-in user from the claim. The ChatList action method returns the list of users available for chat, excluding the logged-in user. The GetChatCobversion action method retrieves the chat conversation with a specific user, including the user's information and chat history between the logged-in user and the selected user.
Chat.cshtml
@model AspCoreMvcSingnalR.DatabaseEntity.User
@{
    ViewData["Title"] = "Chat";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<style type="text/css">
  

    .chat-list {
        max-height: 100vh;
        overflow-y: auto;
    }
    .message{
        font-size:10px;
    }
    .user {
        padding: 10px;
        border-bottom: 1px solid #ddd;
        cursor: pointer;
        display: flex;
        align-items: center;
        font-size:13px;
    }

        .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.FullName</h4>
            <div class="chat-window" id="chat-window">
                
            </div>
        </div>
        <div class="col-4 chatbox">
            <h5 class="chatlisthead">Chat List</h5>
            <div class="chat-list" id="divChatList">
                
            </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 () {
        LoadUserChatList();
        $("#close-chat").click(function () {
            $("#chat-window").slideUp();
        });
    });
    var senderChatId = "@Model.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("UserStatusChanged", function (userStatuses) {
        // Update UI to reflect online/offline status
        LoadUserChatList();
    });
    connection.on("ReceiveMessage", async (messageObj) => {
        var messageBuilder = "<li class='left'><div class='text'><div class='user'>" + GetUserNameWithIcon(messageObj.senderName) + "</div><small>" + messageObj.message + "</small>" + "</li>"
        $("#chatlist").append(messageBuilder);
        
        //If ChatBox is open then MarkMessageAsSeen from senderId
        var reciverId = $("#chatlist").attr("data-userid");
        if (reciverId)
        {
            await connection.invoke("MarkMessageAsSeen", messageObj.senderUserId);
        }
        //Showing notifcation to user if get any message
        var notification = "You have received a message from user " + messageObj.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.FullName';
                await connection.invoke("SendMessageToUserChatGroup", 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 LoadUserChatCobversion(name) {
        Loader(true)
        $.ajax({
            url: '/UserChat/GetChatCobversion?userIdToLoadChat=' + name,
            type: 'GET',
            success: function (result) {
                $('#chat-window').html(result);
                Loader(false)
            },
            error: function (xhr, status, error) {
                console.log(xhr.responseText);
                Loader(false)
            }
        });
    }
    function LoadUserChatList() {
        Loader(true)
        $.ajax({
            url: '/UserChat/ChatList',
            type: 'GET',
            success: function (result) {
                $('#divChatList').html(result);
                Loader(false)
            },
            error: function (xhr, status, error) {
                console.log(xhr.responseText);
                Loader(false)
            }
        });
    }
</script>

_ChatList.cshtml
@model IEnumerable<AspCoreMvcSingnalR.DatabaseEntity.User>
@if (Model != null && Model.Any())
{
    foreach (var user in Model)
    {
        <div class="user" data-user-id="@user.UserId">
            <i class="fa fa-user-circle-o" aria-hidden="true"></i> @user.FullName
            @if(user.IsOnline)
            {
                <i class="fa fa-circle" style="color:green;font-size: 12px;" aria-hidden="true"></i>
            }
            else
            {
                <i class="fa fa-circle" style="color:red;font-size: 12px;" aria-hidden="true"></i>
            }
        </div>
    }
}
<script type="text/javascript">
    $(document).ready(function () {
        $(".user").click(function () {
            var userId = $(this).data("user-id");
            var userName = $(this).text();
            $("#chat-window").slideDown();
            LoadUserChatCobversion(userId);
        });
    });
</script>
_ChatConversion.cshtml
@model AspCoreMvcSingnalR.Controllers.ChatConversionModel
@{
    string loginUserId = ViewData["loginUserId"].ToString();
}
@if (Model != null && Model.ChatUser != null)
{

    <div class="chat-header">
        <span class="close" id="close-chat">×</span>
        <h4>
            Chat with <span>
                @Model.ChatUser.FullName
                @if (Model.ChatUser.IsOnline)
                {
                    <i class="fa fa-circle" style="color:green;font-size: 12px;" aria-hidden="true"></i>
                }
                else
                {
                    <i class="fa fa-circle" style="color:red;font-size: 12px;" aria-hidden="true"></i>
                }
            </span>
        </h4>
    </div>
    <div class="chat-body">
        <ul class="chat-ui" data-userid="@Model.ChatUser.UserId" id="chatlist">
            @if (Model.UserChatHistories?.Any() == true)
            {
                @foreach (var chat in Model.UserChatHistories)
                {
                    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>
                                <small class="message">
                                    @chat.Message
                                    <br>
                                    @if (chat.SeetAt != null)
                                    {
                                        <small style="font-size:8px">SeetAt: @chat.SeetAt.ToString()</small>
                                    }
                                    else
                                    {
                                        <small style="font-size:8px">Not seen</small>
                                    }
                                </small>
                            </div>
                        </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>
                                <small class="message">
                                    @chat.Message
                                    <br>
                                    @if (chat.SeetAt != null)
                                    {
                                        <small style="font-size:8px">SeetAt: @chat.SeetAt.ToString()</small>
                                    }
                                    else
                                    {
                                        <small style="font-size:8px">Not seen</small>
                                    }
                                </small>
                            </div>

                        </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>

}
  • SendMessage(): Sends a message to the selected user. 
  • GetUserNameWithIcon(userName): Formats the user name with an icon. 
  • LoadUserChatCobversion(name): Loads the chat conversation with a specific user. LoadUserChatList(): Loads the list of users available for chat. 
Partial View (_ChatList.cshtml) Displays the list of users available for chat. Online users are indicated with a green circle icon, while offline users are indicated with a red circle icon.


Final project structure