Fuzn.FluentWebSocket
0.3.0
dotnet add package Fuzn.FluentWebSocket --version 0.3.0
NuGet\Install-Package Fuzn.FluentWebSocket -Version 0.3.0
<PackageReference Include="Fuzn.FluentWebSocket" Version="0.3.0" />
<PackageVersion Include="Fuzn.FluentWebSocket" Version="0.3.0" />
<PackageReference Include="Fuzn.FluentWebSocket" />
paket add Fuzn.FluentWebSocket --version 0.3.0
#r "nuget: Fuzn.FluentWebSocket, 0.3.0"
#:package Fuzn.FluentWebSocket@0.3.0
#addin nuget:?package=Fuzn.FluentWebSocket&version=0.3.0
#tool nuget:?package=Fuzn.FluentWebSocket&version=0.3.0
Fuzn.FluentWebSocket
A lightweight fluent API for building WebSocket connections with ClientWebSocket. Provides a clean, chainable interface for configuring connections, sending and receiving messages, and streaming.
Installation
To get started, add the Fuzn.FluentWebSocket package to your project using the following command:
dotnet add package Fuzn.FluentWebSocket
Quick Start
The following example demonstrates how to connect to a WebSocket endpoint, send a message, and receive a response:
using Fuzn.FluentWebSocket;
await using var ws = await new ClientWebSocket()
.Url("wss://echo.example.com/ws")
.Connect();
await ws.SendText("hello");
var msg = await ws.Receive();
Console.WriteLine(msg.Text); // "hello"
Alternative syntax using Request().WithUrl():
await using var ws = await new ClientWebSocket()
.Request()
.WithUrl("wss://echo.example.com/ws")
.Connect();
Note: URLs must use
ws://orwss://scheme.
Connection Configuration
Headers
.WithHeader("X-Custom", "value")
.WithHeaders(new Dictionary<string, string> { ["X-One"] = "1", ["X-Two"] = "2" })
.WithUserAgent("MyApp/1.0")
Authentication
// Bearer token
.WithAuthBearer("jwt-token")
// Basic auth
.WithAuthBasic("username", "password")
// API key (defaults to X-API-Key header)
.WithAuthApiKey("my-key")
.WithAuthApiKey("my-key", "X-Custom-Key")
Query Parameters
Parameters are URL-encoded and appended to the URL on connect:
.WithQueryParam("token", "abc123")
.WithQueryParam("channel", "trades")
Sub-Protocols
Negotiate WebSocket sub-protocols during the handshake:
.WithSubProtocol("graphql-ws")
Cookies
.WithCookie("session", "abc123")
Timeouts
// Connection timeout — cancels if connect takes too long
.WithTimeout(TimeSpan.FromSeconds(5))
// Keep-alive interval and timeout
.WithKeepAliveInterval(TimeSpan.FromSeconds(30))
.WithKeepAliveTimeout(TimeSpan.FromSeconds(10))
Compression
Enable per-message deflate compression:
// Default options
.WithCompression()
// Custom options
.WithCompression(new WebSocketDeflateOptions { ClientMaxWindowBits = 12 })
Message Size Limits
// Limit incoming messages to 1 MB (throws InvalidOperationException if exceeded)
.WithMaxMessageSize(1024 * 1024)
// Buffer size for receiving (default: 4096)
.WithReceiveBufferSize(8192)
TLS / Certificates
// Client certificate for mutual TLS
.WithClientCertificate(certificate)
// Custom server certificate validation
.WithServerCertificateValidation((sender, cert, chain, errors) => true)
Other Options
.WithCredentials(credentials)
.WithProxy(proxy)
.WithCancellationToken(cancellationToken)
Auto-Reconnect
Automatically reconnect on disconnection with exponential backoff:
// Default: 1s initial delay, 30s max delay, unlimited retries
.WithAutoReconnect()
// Custom settings
.WithAutoReconnect(
initialDelay: TimeSpan.FromMilliseconds(500),
maxDelay: TimeSpan.FromSeconds(10),
maxRetries: 5)
Logging
.WithLogger(logger)
Sending Messages
Text Messages
await ws.SendText("hello");
Binary Messages
await ws.SendBinary(bytes); // byte[]
await ws.SendBinary(readOnlyMemory); // ReadOnlyMemory<byte> (zero-copy)
await ws.SendBinary(stream); // Stream (chunked)
Typed Messages (JSON)
Objects are automatically serialized to JSON and sent as text:
await ws.Send(new { Action = "subscribe", Channel = "trades" });
Request-Response
Send a message and immediately receive the next response:
// Text
var response = await ws.SendTextAndReceive("ping");
// Typed
var response = await ws.SendAndReceive<MyRequest, MyResponse>(request);
Receiving Messages
Single Message
// Untyped — access raw Text or Bytes
var msg = await ws.Receive();
Console.WriteLine(msg.Text);
// Typed — automatic deserialization with lazy Data property
var typed = await ws.Receive<TradeEvent>();
Console.WriteLine(typed.Data?.Price);
With Timeout
Throws OperationCanceledException if no message arrives within the timeout:
var msg = await ws.Receive(TimeSpan.FromSeconds(5));
var typed = await ws.Receive<TradeEvent>(TimeSpan.FromSeconds(5));
Streaming with Listen
Use IAsyncEnumerable for continuous message streaming:
// Untyped
await foreach (var msg in ws.Listen(cancellationToken))
{
Console.WriteLine(msg.Text);
}
// Typed
await foreach (var msg in ws.Listen<TradeEvent>(cancellationToken))
{
Console.WriteLine(msg.Data?.Price);
}
Callback with OnMessage
Convenience method over Listen — return true to continue, false to stop:
await ws.OnMessage<ChatMessage>(async msg =>
{
Console.WriteLine(msg.Data?.Text);
return msg.Data?.Text != "bye";
});
Working with Messages
FluentWebSocketMessage / FluentWebSocketMessage<T>
Messages provide access to type, content, and close information:
var msg = await ws.Receive();
// Check message type
if (msg.IsText)
Console.WriteLine(msg.Text);
else if (msg.IsBinary)
Console.WriteLine(msg.Bytes?.Length);
else if (msg.IsClose)
Console.WriteLine($"Closed: {msg.CloseStatus} - {msg.CloseStatusDescription}");
// Deserialize to a specific type
var data = msg.ContentAs<MyEvent>();
// Try deserialize without throwing
if (msg.TryContentAs<MyEvent>(out var evt))
{
// Use evt
}
Connection Events
Subscribe to connection lifecycle events:
ws.Connected += () =>
Console.WriteLine("Connected");
ws.Disconnected += (status, description) =>
Console.WriteLine($"Disconnected: {status} - {description}");
ws.Reconnecting += (attempt, delay) =>
Console.WriteLine($"Reconnecting attempt {attempt} after {delay}");
ws.Error += ex =>
Console.WriteLine($"Error: {ex.Message}");
The Connected event fires on initial connection and after each successful reconnect.
Connection Properties
bool connected = ws.IsConnected; // Whether the connection is open
WebSocketState state = ws.State; // Current connection state
string? subProtocol = ws.SubProtocol; // Negotiated sub-protocol
Closing Connections
Graceful Close
var result = await ws.Close();
Console.WriteLine(result.Status); // e.g., NormalClosure
Console.WriteLine(result.Description); // e.g., "Goodbye"
Automatic Disposal
FluentWebSocketConnection implements IAsyncDisposable. Using await using ensures a graceful close (with a 5-second timeout) followed by disposal:
await using var ws = await new ClientWebSocket()
.Url("wss://example.com/ws")
.Connect();
// Connection is gracefully closed and disposed at end of scope
Custom Serialization
By default, FluentWebSocket uses System.Text.Json with JsonSerializerDefaults.Web (camelCase, case-insensitive) via the built-in SystemTextJsonSerializerProvider.
Per-Connection
Override serialization on a per-connection basis:
.WithSerializer(new MyCustomSerializer())
Global Default
Configure the default serializer for all connections:
FluentWebSocketDefaults.Serializer = new MyCustomSerializer();
Custom Serializer
Implement ISerializerProvider for custom serialization:
public class MyCustomSerializer : ISerializerProvider
{
public string Serialize<T>(T obj) => /* your serialization logic */;
public T? Deserialize<T>(string content) => /* your deserialization logic */;
}
Global Defaults
Configure defaults that apply to all connections unless overridden per-request:
FluentWebSocketDefaults.Serializer = new MyCustomSerializer();
FluentWebSocketDefaults.ReceiveBufferSize = 8192;
FluentWebSocketDefaults.MaxMessageSize = 1024 * 1024;
FluentWebSocketDefaults.Logger = logger;
Debugging
Use ToString() on requests, messages, and close results for debug output:
var request = new ClientWebSocket()
.Url("wss://example.com/ws")
.WithAuthBearer("token")
.WithAutoReconnect();
Console.WriteLine(request.ToString());
// === FluentWebSocket Request ===
// URL: wss://example.com/ws
// Headers:
// Authorization: ***
// Auto-Reconnect: Enabled (delay: 00:00:01, max: 00:00:30, retries: unlimited)
The request configuration is also accessible via the Data property for inspection:
var url = request.Data.Url;
var headers = request.Data.Headers;
Full Example
using Fuzn.FluentWebSocket;
await using var ws = await new ClientWebSocket()
.Url("wss://api.example.com/ws")
.WithAuthBearer("my-token")
.WithSubProtocol("graphql-ws")
.WithKeepAliveInterval(TimeSpan.FromSeconds(30))
.WithAutoReconnect()
.WithCompression()
.Connect();
ws.Disconnected += (status, desc) =>
Console.WriteLine($"Disconnected: {status}");
// Subscribe
await ws.Send(new { Type = "subscribe", Channel = "updates" });
// Listen for messages
await foreach (var msg in ws.Listen<UpdateEvent>())
{
Console.WriteLine($"Update: {msg.Data?.Description}");
}
License
MIT License - see LICENSE for details.
| 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
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Fuzn.FluentWebSocket:
| Package | Downloads |
|---|---|
|
Fuzn.TestFuzn.Plugins.WebSocket
WebSocket plugin for TestFuzn |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 132 | 4/6/2026 |
| 0.2.0-beta | 94 | 4/6/2026 |