You should not implement raw WebSocket handlers yourself unless you have a very specific reason.
In real-world systems, SignalR (classic for .NET Framework / SignalR Core for .NET Core) is the industry-standard abstraction over WebSockets.
Since you are on ASP.NET MVC 5 / .NET Framework 4.8, this answer is SignalR (not ASP.NET Core) and battle-tested for production.
Why NOT raw WebSockets in production?
Raw WebSockets mean you must implement everything yourself:
- Connection lifecycle
- Reconnect logic
- Heartbeats
- Scaling across servers
- Fallback transports
- Authentication & authorization
- Group management
- Message routing
SignalR already solves all of this and uses WebSockets internally when available.
Architecture (Production Chat)
Browser
|
SignalR JS Client
|
SignalR Hub
|
ChatService
|
Database / Cache (SQL + Redis)
Step 1: Install SignalR (ASP.NET MVC 5)
Install-Package Microsoft.AspNet.SignalR
Step 2: Enable SignalR (OWIN Startup)
Create Startup.cs
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(YourApp.Startup))]
namespace YourApp
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
Step 3: Create Chat Hub (Server)
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
public class ChatHub : Hub
{
public override Task OnConnected()
{
string userId = Context.User.Identity.Name;
Groups.Add(Context.ConnectionId, userId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string userId = Context.User.Identity.Name;
Groups.Remove(Context.ConnectionId, userId);
return base.OnDisconnected(stopCalled);
}
// 1-to-1 chat
public Task SendPrivate(string toUserId, string message)
{
string fromUser = Context.User.Identity.Name;
return Clients.Group(toUserId).receiveMessage(fromUser, message);
}
// Group chat
public Task SendGroup(string groupName, string message)
{
string fromUser = Context.User.Identity.Name;
return Clients.Group(groupName).receiveGroupMessage(fromUser, message);
}
}
Step 4: Secure Hub (IMPORTANT)
SignalR automatically integrates with ASP.NET authentication.
[Authorize]
public class ChatHub : Hub
{
}
If you use Forms Authentication or ASP.NET Identity → works out of the box.
Step 5: JavaScript Client (Browser)
<script src="/Scripts/jquery.signalR-2.4.3.min.js"></script>
<script src="/signalr/hubs"></script>
<script>
var chat = $.connection.chatHub;
chat.client.receiveMessage = function (from, message) {
console.log(from + ": " + message);
};
$.connection.hub.start().done(function () {
console.log("Connected");
$('#send').click(function () {
chat.server.sendPrivate("user2", "Hello!");
});
});
</script>
Step 6: Message Persistence (Production MUST)
Never rely on in-memory messages.
public class ChatService
{
public void SaveMessage(string from, string to, string text)
{
// Save to SQL / NoSQL
}
}
Call this inside the Hub before sending.
Step 7: Scaling (CRITICAL for Production)
Problem
- SignalR hubs are in-memory per server.
Solution: Redis Backplane
Install-Package Microsoft.AspNet.SignalR.Redis
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver
.UseRedis("localhost", 6379, "", "ChatApp");
app.MapSignalR();
}
This allows:
- Multiple IIS servers
- Load balancer support
- Horizontal scaling
Step 8: Reliability Features
| Feature | Status |
|---|---|
| Reconnect | Built-in |
| Fallback (LongPolling) | Built-in |
| Heartbeat | Built-in |
| Transport negotiation | Built-in |
| Thread safety | Built-in |
Step 9: Message Delivery Guarantee
For enterprise-grade chat:
- Save message first
- Send notification second
- Use delivery status
public class ChatMessage
{
public long Id;
public string From;
public string To;
public string Text;
public byte Status; // Sent, Delivered, Read
}
Step 10: When to use RAW WebSocket?
Only if:
- You are building a game server
- You need binary streaming
- Ultra-low latency trading system
- Otherwise → SignalR is correct.