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, etc
In this post will Create a one-to-one chat application in ASP.NET MVC using C# with a database involves several steps.
Here's a step that we gonna to follow:
Database Design:
- We will create a database to store user information, messages, and any other necessary data.
Creating ASP.NET MVC Setup:
- We will create a new ASP.NET Core MVC project in Visual Studio.
- We will set up your project structure, including controllers, views, and models.
Setup User Authentication and Authorization:
We will implement user authentication and authorization using ASP.NET Identity so that users can register, log in, and see others for chat.
1.Creating Chat Interface:
- We will create the chat interface where users can view and send messages.
- For that, we are going to use HTML, CSS, and JavaScript to create a chat UI.
3.Database Integration:
- In this step, we will configure our database with our ASP.NET MVC application for users, conversations, and messages.
2.Server-Side Logic:
- In this step, we will implement server-side logic to handle sending and receiving messages.
- We will implement SignalR for real-time communication between clients and the server.
4.Message Storage:
- In this step, we will store messages in our database, associating them with the appropriate users and conversations.
5.Testing and Debugging:
- In this step, we will test our application thoroughly to ensure that messages are sent and received correctly.
I have also provided a download link for the source code at the endpoint. You can download the source code.
- 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:
- [Solved]-How To Connect MySQL In ASP.NET Core 2024
- ASP .Net Core CRUD operations without using Entity Framework Step By Step Guide
- Crud operation in Asp .Net Core using Razor Pages Step By Step
- Crud operation in Asp.Net Core Mvc using jquery datatable with entity framework core database first
- Make One To One Chat System In Asp.Net.Core Signalr Step By Step in Asp .Net Core API
Database Integration:
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools
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; }
}
}
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:
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:
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:
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:
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 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
}
}
@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>
@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>
}
}
}
You can download the source code from GitHub: https://github.com/ramkumar457/SignalRInAspNetMVC/"
Read Similar Articles
- [Fixed] Cannot convert from 'Microsoft.AspNetCore.Mvc.ActionResult' to 'System.Collections.Generic.IEnumerable<string>'
- [Solved]- Find Records From One Table Which Don’t Exist In Another SQL
- [Solved]- DataTables- Uncaught TypeError: Cannot read properties of undefined (reading 'length')
- [Solved] Import error: cannot import name 'open_filename' from 'pdfminer.utils
- Country state city json example file download