Fuzn.FluentWebSocket 0.3.0

dotnet add package Fuzn.FluentWebSocket --version 0.3.0
                    
NuGet\Install-Package Fuzn.FluentWebSocket -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fuzn.FluentWebSocket" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fuzn.FluentWebSocket" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Fuzn.FluentWebSocket" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Fuzn.FluentWebSocket --version 0.3.0
                    
#r "nuget: Fuzn.FluentWebSocket, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Fuzn.FluentWebSocket@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Fuzn.FluentWebSocket&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Fuzn.FluentWebSocket&version=0.3.0
                    
Install as a Cake Tool

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:// or wss:// 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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