Building Real-Time Chat Application with Clean Architecture in .NET Core using SignalR
Introduction
Real-time applications have become increasingly popular in today’s fast-paced world, requiring the ability to update data and communicate with clients in real-time. In this article, we will explore how to build scalable and maintainable real-time applications using the principles of Clean Architecture, focusing on a .NET Core SignalR WebSocket API.
Understanding Clean Architecture
Clean Architecture is a software design pattern that promotes separation of concerns and maintainability by dividing the application into distinct layers. These layers include the domain, application, and infrastructure layers, each with its own responsibilities. The core principle of Clean Architecture is the dependency rule, which states that the inner layers should have no knowledge of the outer layers.
Setting up the Project
To get started, let’s create a new .NET Core project and set up the necessary dependencies. We’ll be using the following libraries:
Microsoft.AspNetCore.SignalR
: A real-time messaging library for .NET Core.Microsoft.EntityFrameworkCore
: An object-relational mapping framework for interacting with databases.
First, open Visual Studio and create a new ASP.NET Core Web Application project. Select the API template and choose .NET Core as the target framework. Next, install the required NuGet packages:
dotnet add package Microsoft.AspNetCore.SignalR
dotnet add package Microsoft.EntityFrameworkCore
Defining the Domain Model
In Clean Architecture, the domain layer represents the core business logic of the application. In our real-time application, let’s consider a chat application where users can send and receive messages. We will define the following domain model:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Message
{
public int Id { get; set; }
public string Content { get; set; }
public DateTime Timestamp { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Implementing the Application and Infrastructure Layers
The application layer contains the use cases and business logic of the application, while the infrastructure layer handles external concerns such as data access and communication.
Let’s create a ChatService
class in the application layer, responsible for managing the chat functionality:
public class ChatService
{
private readonly IHubContext<ChatHub> _hubContext;
private readonly ApplicationDbContext _dbContext;
public ChatService(IHubContext<ChatHub> hubContext, ApplicationDbContext dbContext)
{
_hubContext = hubContext;
_dbContext = dbContext;
}
public async Task SendMessage(string userName, string messageContent)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Name == userName);
if (user == null)
{
user = new User { Name = userName };
_dbContext.Users.Add(user);
await _dbContext.SaveChangesAsync();
}
var message = new Message
{
Content = messageContent,
Timestamp = DateTime.Now,
UserId = user.Id
};
_dbContext.Messages.Add(message);
await _dbContext.SaveChangesAsync();
await _hubContext.Clients.All.SendAsync("ReceiveMessage", message);
}
}
In the infrastructure layer, let’s create a SignalR hub named ChatHub
to handle real-time communication with clients:
public class ChatHub : Hub
{
public async Task SendMessage(string userName, string messageContent)
{
var chatService = Context.GetHttpContext().RequestServices.GetService<ChatService>();
await chatService.SendMessage(userName, messageContent);
}
}
Configuring SignalR and Database Connection
To enable SignalR and configure the database connection, open the Startup.cs
file and add the following code:
public void ConfigureServices(IServiceCollection services)
{
// Add SignalR
services.AddSignalR();
// Configure database connection
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Enable SignalR
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chatHub");
});
}
Make sure to update the DefaultConnection
connection string in the appsettings.json
file to point to your database.
Building the Client Application
To consume the real-time functionality in the client application, let’s create a JavaScript file and include the following code:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
connection.on("ReceiveMessage", function (message) {
// Handle received message
});
connection.start()
.then(function () {
// Connection established
})
.catch(function (error) {
console.error(error);
});
Conclusion
In this article, we explored how to build real-time applications using the principles of Clean Architecture and .NET Core SignalR. By following the separation of concerns and maintaining a clean and scalable architecture, we can develop robust and maintainable real-time applications.