RepletoryLib.WebSocket.Abstractions
WebSocket hub abstractions and interfaces for real-time connection management, group messaging, broadcast, and message handling.
Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.

Overview
RepletoryLib.WebSocket.Abstractions defines the core contracts, models, enums, and options for real-time WebSocket communication. It provides IWebSocketHub for sending messages to connections, users, and groups, IWebSocketConnectionManager for tracking connection lifecycle and group memberships, and IWebSocketMessageHandler for pluggable message routing based on message type.
This package contains no implementations -- it is the abstraction layer that concrete providers (in-memory, Redis-backed, etc.) implement. Reference this package in your application code to program against interfaces, then swap providers without changing business logic.
Key Features
IWebSocketHub -- Core hub contract: send to connection, send to user, broadcast to all, broadcast to group, manage group membership, and disconnect
IWebSocketConnectionManager -- Connection lifecycle management: add, remove, query by ID/user/group, group membership, and connection counting
IWebSocketMessageHandler -- Pluggable message handler matched by message type string for custom message processing
WebSocketMessage -- Message model with type, payload, sender info, timestamp, and optional metadata
WebSocketConnection -- Connection model tracking identity, state, group memberships, and activity timestamps
SendMessageRequest / BroadcastRequest / GroupBroadcastRequest -- Typed request models for targeted, broadcast, and group-scoped messaging
SendResult / BulkSendResult -- Result types with per-connection success/failure tracking and static factory methods
WebSocketOptions -- Shared configuration for heartbeat intervals, connection limits, message sizes, allowed origins, and endpoint path
ConnectionState enum -- Connection lifecycle states: Connecting, Connected, Disconnecting, Disconnected
WebSocketMessageType enum -- Message type classifications: Text, System, Ping, Pong, Connected, Disconnected, GroupJoined, GroupLeft, Error
Installation
dotnet add package RepletoryLib.WebSocket.Abstractions
Or add to your .csproj:
<PackageReference Include="RepletoryLib.WebSocket.Abstractions" 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.Common |
RepletoryLib |
Quick Start
1. Reference the abstractions in your service layer
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class ChatService
{
private readonly IWebSocketHub _hub;
public ChatService(IWebSocketHub hub) => _hub = hub;
public async Task SendDirectMessageAsync(string connectionId, string text)
{
var result = await _hub.SendToConnectionAsync(new SendMessageRequest
{
ConnectionId = connectionId,
Message = new WebSocketMessage
{
Type = "Chat",
Payload = text
}
});
if (!result.Success)
throw new InvalidOperationException($"Send failed: {result.Error}");
}
}
2. Register a concrete provider in Program.cs
// In-memory provider (from RepletoryLib.WebSocket.Core)
using RepletoryLib.WebSocket.Core;
builder.Services.AddRepletoryWebSocket(builder.Configuration);
Configuration
WebSocketOptions
| 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 considered timed out |
AllowedOrigins |
List<string> |
[] (all allowed) |
Allowed origins for WebSocket requests. Empty list permits all |
EndpointPath |
string |
"/ws" |
URL path the WebSocket endpoint listens on |
Section name: "WebSocket"
{
"WebSocket": {
"HeartbeatIntervalSeconds": 30,
"MaxConnections": 10000,
"MaxMessageSizeBytes": 65536,
"ConnectionTimeoutSeconds": 120,
"AllowedOrigins": ["https://example.com", "https://app.example.com"],
"EndpointPath": "/ws"
}
}
Usage Examples
Send a Message to a Specific Connection
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class NotificationService
{
private readonly IWebSocketHub _hub;
public NotificationService(IWebSocketHub hub) => _hub = hub;
public async Task NotifyConnectionAsync(string connectionId, string eventType, string payload)
{
var result = await _hub.SendToConnectionAsync(new SendMessageRequest
{
ConnectionId = connectionId,
Message = new WebSocketMessage
{
Type = eventType,
Payload = payload,
Metadata = new Dictionary<string, string>
{
["source"] = "notification-service"
}
}
});
if (!result.Success)
Console.WriteLine($"Failed to send to {connectionId}: {result.Error}");
}
}
Send to All Connections for a User
public async Task NotifyUserAsync(string userId, string message)
{
var result = await _hub.SendToUserAsync(userId, new WebSocketMessage
{
Type = "Notification",
Payload = message
});
if (!result.Success)
Console.WriteLine($"Failed to notify user {userId}: {result.Error}");
}
Broadcast to All Connections
public async Task BroadcastAnnouncementAsync(string announcement, string? excludeConnectionId = null)
{
var request = new BroadcastRequest
{
Message = new WebSocketMessage
{
Type = "Announcement",
Payload = announcement
},
ExcludeConnectionIds = excludeConnectionId is not null
? [excludeConnectionId]
: null
};
var result = await _hub.BroadcastAsync(request);
Console.WriteLine($"Broadcast: {result.SuccessCount} delivered, {result.FailureCount} failed");
}
Broadcast to a Group
public async Task SendToRoomAsync(string roomName, string senderConnectionId, string message)
{
var result = await _hub.BroadcastToGroupAsync(new GroupBroadcastRequest
{
GroupName = roomName,
Message = new WebSocketMessage
{
Type = "Chat",
Payload = message,
ConnectionId = senderConnectionId
},
ExcludeConnectionIds = [senderConnectionId] // Don't echo back to sender
});
}
Group Management
// Add a connection to a group
await _hub.AddToGroupAsync(connectionId, "room-general");
// Remove a connection from a group
await _hub.RemoveFromGroupAsync(connectionId, "room-general");
// Forcefully disconnect a connection
await _hub.DisconnectAsync(connectionId);
Query Connections with IWebSocketConnectionManager
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class AdminService
{
private readonly IWebSocketConnectionManager _connections;
public AdminService(IWebSocketConnectionManager connections) => _connections = connections;
public async Task<int> GetOnlineCountAsync()
{
return await _connections.GetConnectionCountAsync();
}
public async Task<IReadOnlyList<WebSocketConnection>> GetUserSessionsAsync(string userId)
{
return await _connections.GetConnectionsByUserAsync(userId);
}
public async Task<IReadOnlyList<string>> GetGroupsAsync(string connectionId)
{
return await _connections.GetGroupsForConnectionAsync(connectionId);
}
}
Implement a Custom Message Handler
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)
{
// Echo the message back 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);
}
}
}
// Register in DI
builder.Services.AddSingleton<IWebSocketMessageHandler, ChatMessageHandler>();
API Reference
Interfaces
IWebSocketHub
| Method |
Returns |
Description |
SendToConnectionAsync(request, ct) |
Task<SendResult> |
Send a message to a specific connection |
SendToUserAsync(userId, message, ct) |
Task<SendResult> |
Send a message to all connections for a user |
BroadcastAsync(request, ct) |
Task<BulkSendResult> |
Broadcast a message to all connections with optional exclusions |
BroadcastToGroupAsync(request, ct) |
Task<BulkSendResult> |
Broadcast a message to a group with optional exclusions |
AddToGroupAsync(connectionId, groupName, ct) |
Task |
Add a connection to a group |
RemoveFromGroupAsync(connectionId, groupName, ct) |
Task |
Remove a connection from a group |
DisconnectAsync(connectionId, ct) |
Task |
Forcefully disconnect a connection |
IWebSocketConnectionManager
| Method |
Returns |
Description |
AddConnectionAsync(connectionId, userId, ct) |
Task<WebSocketConnection> |
Register a new connection |
RemoveConnectionAsync(connectionId, ct) |
Task |
Remove a connection and clean up resources |
GetConnectionAsync(connectionId, ct) |
Task<WebSocketConnection?> |
Get a connection by its ID |
GetConnectionsByUserAsync(userId, ct) |
Task<IReadOnlyList<WebSocketConnection>> |
Get all connections for a user |
GetConnectionsByGroupAsync(groupName, ct) |
Task<IReadOnlyList<WebSocketConnection>> |
Get all connections in a group |
GetAllConnectionsAsync(ct) |
Task<IReadOnlyList<WebSocketConnection>> |
Get all active connections |
AddToGroupAsync(connectionId, groupName, ct) |
Task |
Add a connection to a group |
RemoveFromGroupAsync(connectionId, groupName, ct) |
Task |
Remove a connection from a group |
GetGroupsForConnectionAsync(connectionId, ct) |
Task<IReadOnlyList<string>> |
Get all groups a connection belongs to |
GetConnectionCountAsync(ct) |
Task<int> |
Get the total number of active connections |
IWebSocketMessageHandler
| Property/Method |
Returns |
Description |
MessageType |
string |
The message type this handler processes |
HandleAsync(message, connection, ct) |
Task |
Process an incoming message within the context of its connection |
Models
WebSocketMessage
| Property |
Type |
Required |
Description |
Type |
string |
Yes |
Message type identifier for routing to handlers |
Payload |
string |
Yes |
Serialized message payload content |
SenderId |
string? |
No |
User ID of the sender (null for system messages) |
ConnectionId |
string? |
No |
Connection ID of the sender (null for system messages) |
Timestamp |
DateTime |
No |
UTC creation timestamp (defaults to DateTime.UtcNow) |
Metadata |
Dictionary<string, string>? |
No |
Optional key-value metadata |
WebSocketConnection
| Property |
Type |
Default |
Description |
ConnectionId |
string |
(required) |
Unique connection identifier |
UserId |
string? |
null |
Authenticated user ID (null for anonymous) |
ConnectedAt |
DateTime |
DateTime.UtcNow |
UTC timestamp when connection was established |
LastActivityAt |
DateTime |
DateTime.UtcNow |
UTC timestamp of most recent activity |
State |
ConnectionState |
Connected |
Current connection state |
Groups |
List<string> |
[] |
Groups this connection belongs to |
Metadata |
Dictionary<string, string>? |
null |
Optional key-value metadata |
SendMessageRequest
| Property |
Type |
Required |
Description |
ConnectionId |
string |
Yes |
Target connection identifier |
Message |
WebSocketMessage |
Yes |
The message to send |
BroadcastRequest
| Property |
Type |
Required |
Description |
Message |
WebSocketMessage |
Yes |
The message to broadcast |
ExcludeConnectionIds |
List<string>? |
No |
Connection IDs to exclude from the broadcast |
GroupBroadcastRequest
| Property |
Type |
Required |
Description |
GroupName |
string |
Yes |
Target group name |
Message |
WebSocketMessage |
Yes |
The message to broadcast to the group |
ExcludeConnectionIds |
List<string>? |
No |
Connection IDs to exclude from the broadcast |
SendResult
| Property |
Type |
Description |
Success |
bool |
Whether the send operation succeeded |
Error |
string? |
Error message (null when successful) |
ConnectionId |
string? |
Connection ID the message was sent to |
| Static Method |
Returns |
Description |
Succeeded(connectionId?) |
SendResult |
Creates a successful result |
Failed(error, connectionId?) |
SendResult |
Creates a failed result with error message |
BulkSendResult
| Property |
Type |
Description |
Results |
List<SendResult> |
Individual results for each targeted connection |
SuccessCount |
int |
Number of successful sends (computed) |
FailureCount |
int |
Number of failed sends (computed) |
Enums
ConnectionState
| Value |
Description |
Connecting |
Connection is being established |
Connected |
Connection is active and ready for messaging |
Disconnecting |
Connection is being closed gracefully |
Disconnected |
Connection has been fully closed |
WebSocketMessageType
| Value |
Description |
Text |
Standard text message between clients or client-to-server |
System |
System-generated message for internal notifications |
Ping |
Keep-alive ping to verify connection is active |
Pong |
Response to a Ping confirming connection is alive |
Connected |
Client has established a WebSocket connection |
Disconnected |
Client has disconnected from the server |
GroupJoined |
Connection has joined a group |
GroupLeft |
Connection has left a group |
Error |
An error occurred during message processing |
Options
WebSocketOptions
| Property |
Type |
Default |
Description |
HeartbeatIntervalSeconds |
int |
30 |
Interval between heartbeat ping/pong messages |
MaxConnections |
int |
10000 |
Maximum concurrent connections |
MaxMessageSizeBytes |
int |
65536 |
Maximum message size in bytes |
ConnectionTimeoutSeconds |
int |
120 |
Inactivity timeout in seconds |
AllowedOrigins |
List<string> |
[] |
Allowed origins (empty allows all) |
EndpointPath |
string |
"/ws" |
WebSocket endpoint path |
Integration with Other RepletoryLib Packages
| Package |
Relationship |
RepletoryLib.Common |
Direct dependency -- shared base types |
RepletoryLib.WebSocket.Core |
In-memory implementation of IWebSocketHub, IWebSocketConnectionManager, middleware, and heartbeat service |
Testing
using NSubstitute;
using RepletoryLib.WebSocket.Abstractions.Interfaces;
using RepletoryLib.WebSocket.Abstractions.Models;
public class ChatServiceTests
{
[Fact]
public async Task SendDirectMessageAsync_sends_to_connection()
{
// Arrange
var hub = Substitute.For<IWebSocketHub>();
hub.SendToConnectionAsync(Arg.Any<SendMessageRequest>(), Arg.Any<CancellationToken>())
.Returns(SendResult.Succeeded("conn-123"));
var service = new ChatService(hub);
// Act
await service.SendDirectMessageAsync("conn-123", "Hello!");
// Assert
await hub.Received(1).SendToConnectionAsync(
Arg.Is<SendMessageRequest>(r =>
r.ConnectionId == "conn-123" &&
r.Message.Type == "Chat" &&
r.Message.Payload == "Hello!"),
Arg.Any<CancellationToken>());
}
[Fact]
public async Task SendDirectMessageAsync_throws_on_failure()
{
// Arrange
var hub = Substitute.For<IWebSocketHub>();
hub.SendToConnectionAsync(Arg.Any<SendMessageRequest>(), Arg.Any<CancellationToken>())
.Returns(SendResult.Failed("Connection not found"));
var service = new ChatService(hub);
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(
() => service.SendDirectMessageAsync("conn-999", "Hello!"));
}
}
Troubleshooting
| Issue |
Solution |
SendResult.Success is false |
Check the Error property for the failure reason; common causes are closed connections or missing connection IDs |
| No handler found for message type |
Register an IWebSocketMessageHandler implementation in DI whose MessageType matches the incoming message's Type |
BulkSendResult shows partial failures |
Inspect individual Results entries -- broadcast operations continue on per-connection failure |
| Connection not tracked |
Ensure the connection was added via IWebSocketConnectionManager.AddConnectionAsync before querying |
| User has no connections |
The user may have disconnected; check GetConnectionsByUserAsync returns an empty list rather than null |
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.