RepletoryLib.WebSocket.Core
1.0.0
dotnet add package RepletoryLib.WebSocket.Core --version 1.0.0
NuGet\Install-Package RepletoryLib.WebSocket.Core -Version 1.0.0
<PackageReference Include="RepletoryLib.WebSocket.Core" Version="1.0.0" />
<PackageVersion Include="RepletoryLib.WebSocket.Core" Version="1.0.0" />
<PackageReference Include="RepletoryLib.WebSocket.Core" />
paket add RepletoryLib.WebSocket.Core --version 1.0.0
#r "nuget: RepletoryLib.WebSocket.Core, 1.0.0"
#:package RepletoryLib.WebSocket.Core@1.0.0
#addin nuget:?package=RepletoryLib.WebSocket.Core&version=1.0.0
#tool nuget:?package=RepletoryLib.WebSocket.Core&version=1.0.0
RepletoryLib.WebSocket.Core
In-memory WebSocket hub implementation with connection tracking, group management, broadcast, heartbeat monitoring, and ASP.NET Core middleware for RepletoryLib.
Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.
Overview
RepletoryLib.WebSocket.Core provides the default in-memory implementation of the RepletoryLib.WebSocket.Abstractions contracts. It includes InMemoryWebSocketConnectionManager for tracking connections using concurrent dictionaries, WebSocketHub for sending messages and managing groups, WebSocketMiddleware for handling the full WebSocket connection lifecycle (acceptance, receive loop, message routing), and WebSocketHeartbeatService for background keep-alive monitoring and idle connection cleanup.
This package is suitable for single-server deployments. For horizontally scaled environments, consider implementing the abstractions with a distributed backplane (e.g., Redis).
Key Features
AddRepletoryWebSocket-- Single-call DI registration for all WebSocket services, options, and middlewareUseRepletoryWebSocket-- App builder extension to configure ASP.NET Core WebSocket middleware and map the endpointInMemoryWebSocketConnectionManager-- Thread-safe connection tracking with user-to-connection and group-to-connection mappings usingConcurrentDictionaryWebSocketHub-- DefaultIWebSocketHubimplementation that serializes messages to JSON and sends via rawSystem.Net.WebSockets.WebSocketWebSocketMiddleware-- Full lifecycle handler: WebSocket acceptance, origin validation, connection limits, receive loop with message deserialization, and handler routingWebSocketHeartbeatService-- BackgroundIHostedServicethat periodically pings active connections and closes timed-out onesWebSocketCoreOptions-- Core-specific configuration extendingWebSocketOptionswith buffer size, keep-alive interval, and heartbeat toggle- Automatic user association -- Extracts
ClaimTypes.NameIdentifierfrom the authenticated user on connection - Message size enforcement -- Rejects oversized messages based on
MaxMessageSizeBytes - Origin validation -- Blocks connections from non-whitelisted origins when
AllowedOriginsis configured
Installation
dotnet add package RepletoryLib.WebSocket.Core
Or add to your .csproj:
<PackageReference Include="RepletoryLib.WebSocket.Core" Version="1.0.0" />
Note: RepletoryLib packages are published to a local BaGet feed. See the main repository README for feed configuration.
Dependencies
| Package | Type |
|---|---|
RepletoryLib.WebSocket.Abstractions |
RepletoryLib |
Microsoft.AspNetCore.App |
Framework Reference |
Quick Start
Complete Program.cs Setup
using RepletoryLib.WebSocket.Core;
var builder = WebApplication.CreateBuilder(args);
// Register WebSocket services (connection manager, hub, heartbeat, middleware)
builder.Services.AddRepletoryWebSocket(builder.Configuration);
// Register your custom message handlers
builder.Services.AddSingleton<IWebSocketMessageHandler, ChatMessageHandler>();
builder.Services.AddSingleton<IWebSocketMessageHandler, PingMessageHandler>();
var app = builder.Build();
// Configure the WebSocket middleware and map the endpoint
app.UseRepletoryWebSocket();
app.Run();
appsettings.json
{
"WebSocket": {
"HeartbeatIntervalSeconds": 30,
"MaxConnections": 10000,
"MaxMessageSizeBytes": 65536,
"ConnectionTimeoutSeconds": 120,
"AllowedOrigins": [],
"EndpointPath": "/ws",
"Core": {
"ReceiveBufferSize": 4096,
"KeepAliveIntervalSeconds": 30,
"EnableHeartbeat": true
}
}
}
Configuration
WebSocketOptions (base)
Bound from the "WebSocket" configuration section.
| Property | Type | Default | Description |
|---|---|---|---|
HeartbeatIntervalSeconds |
int |
30 |
Interval in seconds between heartbeat ping/pong messages |
MaxConnections |
int |
10000 |
Maximum number of concurrent WebSocket connections |
MaxMessageSizeBytes |
int |
65536 (64 KB) |
Maximum allowed size for a single WebSocket message |
ConnectionTimeoutSeconds |
int |
120 |
Seconds of inactivity before a connection is closed |
AllowedOrigins |
List<string> |
[] (all allowed) |
Allowed origins for WebSocket requests. Empty list permits all |
EndpointPath |
string |
"/ws" |
URL path the WebSocket endpoint listens on |
WebSocketCoreOptions (extends WebSocketOptions)
Bound from the "WebSocket:Core" configuration section.
| Property | Type | Default | Description |
|---|---|---|---|
ReceiveBufferSize |
int |
4096 |
Size in bytes of the buffer used for receiving WebSocket messages |
KeepAliveIntervalSeconds |
int |
30 |
Interval in seconds for the ASP.NET Core WebSocket keep-alive mechanism |
EnableHeartbeat |
bool |
true |
Whether the background heartbeat service is enabled |
Usage Examples
DI Registration
The AddRepletoryWebSocket extension method registers all core services in a single call:
builder.Services.AddRepletoryWebSocket(builder.Configuration);
This registers:
WebSocketOptionsbound from"WebSocket"sectionWebSocketCoreOptionsbound from"WebSocket:Core"sectionInMemoryWebSocketConnectionManageras singleton (also exposed asIWebSocketConnectionManager)WebSocketHubas singleton implementingIWebSocketHubWebSocketHeartbeatServiceas a hosted service (whenEnableHeartbeatistrue)WebSocketMiddlewareas singleton
App Builder Configuration
The UseRepletoryWebSocket extension method configures the middleware pipeline:
app.UseRepletoryWebSocket();
This performs:
- Configures the ASP.NET Core WebSocket middleware with the
KeepAliveIntervalSecondsfromWebSocketCoreOptions - Maps the WebSocket endpoint at the path specified by
WebSocketOptions.EndpointPath(defaults to/ws)
Sending Messages from a Controller
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
private readonly IWebSocketHub _hub;
public NotificationsController(IWebSocketHub hub) => _hub = hub;
[HttpPost("broadcast")]
public async Task<IActionResult> Broadcast([FromBody] string message)
{
var result = await _hub.BroadcastAsync(new BroadcastRequest
{
Message = new WebSocketMessage
{
Type = "Announcement",
Payload = message
}
});
return Ok(new { result.SuccessCount, result.FailureCount });
}
[HttpPost("send/{connectionId}")]
public async Task<IActionResult> SendToConnection(string connectionId, [FromBody] string message)
{
var result = await _hub.SendToConnectionAsync(new SendMessageRequest
{
ConnectionId = connectionId,
Message = new WebSocketMessage
{
Type = "DirectMessage",
Payload = message
}
});
return result.Success ? Ok() : NotFound(result.Error);
}
[HttpPost("groups/{groupName}/broadcast")]
public async Task<IActionResult> BroadcastToGroup(string groupName, [FromBody] string message)
{
var result = await _hub.BroadcastToGroupAsync(new GroupBroadcastRequest
{
GroupName = groupName,
Message = new WebSocketMessage
{
Type = "GroupMessage",
Payload = message
}
});
return Ok(new { result.SuccessCount, result.FailureCount });
}
}
Implementing Custom Message Handlers
Register one or more IWebSocketMessageHandler implementations to process incoming messages. The middleware matches incoming messages to handlers by the MessageType property.
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class ChatMessageHandler : IWebSocketMessageHandler
{
public string MessageType => "Chat";
private readonly IWebSocketHub _hub;
public ChatMessageHandler(IWebSocketHub hub) => _hub = hub;
public async Task HandleAsync(WebSocketMessage message, WebSocketConnection connection, CancellationToken cancellationToken = default)
{
// Broadcast the chat message to all connections in the sender's groups
foreach (var group in connection.Groups)
{
await _hub.BroadcastToGroupAsync(new GroupBroadcastRequest
{
GroupName = group,
Message = message,
ExcludeConnectionIds = [connection.ConnectionId]
}, cancellationToken);
}
}
}
public class JoinGroupHandler : IWebSocketMessageHandler
{
public string MessageType => "JoinGroup";
private readonly IWebSocketHub _hub;
public JoinGroupHandler(IWebSocketHub hub) => _hub = hub;
public async Task HandleAsync(WebSocketMessage message, WebSocketConnection connection, CancellationToken cancellationToken = default)
{
var groupName = message.Payload;
await _hub.AddToGroupAsync(connection.ConnectionId, groupName, cancellationToken);
}
}
// Register in DI
builder.Services.AddSingleton<IWebSocketMessageHandler, ChatMessageHandler>();
builder.Services.AddSingleton<IWebSocketMessageHandler, JoinGroupHandler>();
Querying Connection State
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
[ApiController]
[Route("api/[controller]")]
public class ConnectionsController : ControllerBase
{
private readonly IWebSocketConnectionManager _connectionManager;
public ConnectionsController(IWebSocketConnectionManager connectionManager)
=> _connectionManager = connectionManager;
[HttpGet("count")]
public async Task<IActionResult> GetCount()
{
var count = await _connectionManager.GetConnectionCountAsync();
return Ok(new { count });
}
[HttpGet("user/{userId}")]
public async Task<IActionResult> GetByUser(string userId)
{
var connections = await _connectionManager.GetConnectionsByUserAsync(userId);
return Ok(connections.Select(c => new
{
c.ConnectionId,
c.ConnectedAt,
c.LastActivityAt,
c.Groups
}));
}
[HttpGet("group/{groupName}")]
public async Task<IActionResult> GetByGroup(string groupName)
{
var connections = await _connectionManager.GetConnectionsByGroupAsync(groupName);
return Ok(connections.Select(c => new { c.ConnectionId, c.UserId }));
}
}
Connecting from a JavaScript Client
const ws = new WebSocket('wss://your-server.com/ws');
ws.onopen = () => {
console.log('Connected');
// Join a group
ws.send(JSON.stringify({
Type: 'JoinGroup',
Payload: 'room-general'
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(`[${message.Type}] ${message.Payload}`);
};
// Send a chat message
ws.send(JSON.stringify({
Type: 'Chat',
Payload: 'Hello, everyone!'
}));
Architecture
Middleware Pipeline
When a request arrives at the configured EndpointPath:
- WebSocket validation -- Rejects non-WebSocket requests with
400 Bad Request - Connection limit check -- Returns
503 Service UnavailableifMaxConnectionsis reached - Origin validation -- Returns
403 Forbiddenif the origin is not inAllowedOrigins(when configured) - Connection registration -- Creates a
WebSocketConnectionand associates the authenticated user (viaClaimTypes.NameIdentifier) - Socket acceptance -- Accepts the WebSocket upgrade and registers the raw socket
- Connected notification -- Sends a
Connectedmessage back to the client with the assigned connection ID - Receive loop -- Reads messages, enforces size limits, deserializes JSON to
WebSocketMessage, and routes to matchingIWebSocketMessageHandler - Cleanup -- On disconnect, unregisters the socket, removes the connection, and closes the WebSocket gracefully
Heartbeat Service
When EnableHeartbeat is true, the WebSocketHeartbeatService runs as a background hosted service:
- Interval -- Ticks every
HeartbeatIntervalSeconds - Timeout detection -- Connections idle longer than
ConnectionTimeoutSecondsare closed and removed - Keep-alive -- Active connections receive a JSON ping message (
{"Type":"Ping","Payload":""}) and have theirLastActivityAtupdated
Service Registrations
| Service | Lifetime | Interface |
|---|---|---|
InMemoryWebSocketConnectionManager |
Singleton | IWebSocketConnectionManager |
WebSocketHub |
Singleton | IWebSocketHub |
WebSocketHeartbeatService |
Hosted Service | -- |
WebSocketMiddleware |
Singleton | -- |
Integration with Other RepletoryLib Packages
| Package | Relationship |
|---|---|
RepletoryLib.WebSocket.Abstractions |
Direct dependency -- interfaces, models, enums, and base options |
RepletoryLib.Common |
Transitive dependency via Abstractions |
Testing
using NSubstitute;
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class ChatMessageHandlerTests
{
[Fact]
public async Task HandleAsync_broadcasts_to_all_groups()
{
// Arrange
var hub = Substitute.For<IWebSocketHub>();
hub.BroadcastToGroupAsync(Arg.Any<GroupBroadcastRequest>(), Arg.Any<CancellationToken>())
.Returns(new BulkSendResult());
var handler = new ChatMessageHandler(hub);
var connection = new WebSocketConnection
{
ConnectionId = "conn-1",
UserId = "user-1",
Groups = ["room-general", "room-dev"]
};
var message = new WebSocketMessage
{
Type = "Chat",
Payload = "Hello!"
};
// Act
await handler.HandleAsync(message, connection);
// Assert
await hub.Received(2).BroadcastToGroupAsync(
Arg.Is<GroupBroadcastRequest>(r =>
r.Message == message &&
r.ExcludeConnectionIds!.Contains("conn-1")),
Arg.Any<CancellationToken>());
}
}
Troubleshooting
| Issue | Solution |
|---|---|
400 Bad Request on connect |
Ensure the client is making a WebSocket upgrade request, not a regular HTTP request |
503 Service Unavailable on connect |
MaxConnections limit reached; increase the limit or scale horizontally |
403 Forbidden on connect |
The client's origin is not in AllowedOrigins; add it to the list or clear the list to allow all origins |
| Messages not reaching handlers | Ensure IWebSocketMessageHandler implementations are registered in DI and the MessageType string exactly matches the incoming message's Type |
| Connections timing out unexpectedly | Increase ConnectionTimeoutSeconds or ensure clients are sending messages frequently enough to keep the connection active |
| Heartbeat not running | Verify EnableHeartbeat is true in the "WebSocket:Core" configuration section |
| Large messages silently dropped | Messages exceeding MaxMessageSizeBytes are logged as warnings and discarded; increase the limit if needed |
| Anonymous connections (no user ID) | The middleware extracts ClaimTypes.NameIdentifier from HttpContext.User; ensure authentication middleware runs before the WebSocket endpoint |
License
This project is licensed under the MIT License.
Copyright (c) 2024-2026 Repletory.
For complete documentation, infrastructure setup, and configuration reference, see the RepletoryLib main repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- RepletoryLib.WebSocket.Abstractions (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 78 | 3/2/2026 |