C# Client Library
The Virtufin WebSocketManager Client library (Virtufin.WebSocketManager.Client) provides a C# interface for managing external WebSocket connections.
Installation
From Gitea NuGet Registry
Add the Virtufin package source with a personal access token (scope: read:packages):
dotnet nuget add source https://nuget.haenerconsulting.com/api/packages/virtufin/nuget/index.json \
--name Virtufin --username <your-gitea-username> --password <your-gitea-token>
dotnet add package Virtufin.WebSocketManager.Client
Or configure via NuGet.Config:
<configuration>
<packageSources>
<add key="Virtufin" value="https://nuget.haenerconsulting.com/api/packages/virtufin/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<Virtufin>
<add key="Username" value="<your-gitea-username>" />
<add key="ClearTextPassword" value="<your-gitea-token>" />
</Virtufin>
</packageSourceCredentials>
</configuration>
From Local Source
<ProjectReference Include="path/to/Virtufin.WebSocketManager.Client/Virtufin.WebSocketManager.Client.csproj" />
Overview
The client library provides the WebSocketManagerClient class for connecting to external WebSocket servers through the WebSocketManager service.
Quick Start
using Virtufin.WebSocketManager.Client;
using Virtufin.WebSocketManager.Protos;
using var client = new WebSocketManagerClient("localhost", 5002);
// Connect to a WebSocket server
var connectResponse = await client.ConnectAsync(
new ConnectRequest { Url = "wss://example.com/socket", AutoReconnect = true });
Console.WriteLine($"Connected: {connectResponse.Id} - {connectResponse.Status}");
// List all connections
var listResponse = await client.ListAsync(new ListRequest());
foreach (var conn in listResponse.Connections)
Console.WriteLine($"{conn.Id}: {conn.Url} [{conn.Status}]");
// Send a message and wait for response
var sendResponse = await client.SendAsync(new SendRequest {
Id = connectResponse.Id,
Message = "{\"action\": \"ping\"}",
TimeoutMs = 5000
});
Console.WriteLine($"Response: {sendResponse.Response}");
// Disconnect
await client.DisconnectAsync(new DisconnectRequest { Id = connectResponse.Id });
WebSocketManagerClient
The main client class for WebSocket connection management.
Constructor
public WebSocketManagerClient(string host = "localhost", int port = 5002)
Parameters:
- host - The gRPC host address (default: "localhost")
- port - The gRPC port (default: 5002)
Properties
// Direct access to the underlying gRPC client
public WebSocketManager.WebSocketManagerClient GrpcClient { get; }
Methods
Connection Management
// Connect to a WebSocket server
Task<ConnectResponse> ConnectAsync(ConnectRequest request)
// Disconnect from a WebSocket server
Task<DisconnectResponse> DisconnectAsync(DisconnectRequest request)
// List all managed connections
Task<ListResponse> ListAsync(ListRequest request)
Example:
var response = await client.ConnectAsync(new ConnectRequest {
Url = "wss://example.com/socket",
AutoReconnect = true
});
Console.WriteLine($"Connected: {response.Id}");
var connections = await client.ListAsync(new ListRequest());
foreach (var c in connections.Connections)
Console.WriteLine($"{c.Id}: {c.Url} [{c.Status}]");
await client.DisconnectAsync(new DisconnectRequest { Id = response.Id });
Pub/Sub Integration
// Start publishing WebSocket messages to a Dapr pub/sub topic
Task<StartPublishResponse> StartPublishAsync(StartPublishRequest request)
// Stop publishing to the topic
Task<StopPublishResponse> StopPublishAsync(StopPublishRequest request)
Example:
await client.StartPublishAsync(new StartPublishRequest {
Id = connectionId,
Topic = "websocket-events"
});
await client.StopPublishAsync(new StopPublishRequest { Id = connectionId });
Messaging
// Send a message and wait for a correlated response
Task<SendResponse> SendAsync(SendRequest request)
// Send a message without waiting for a response
Task<SendRawResponse> SendRawAsync(SendRawRequest request)
Example:
// Send with correlation (response expected)
var response = await client.SendAsync(new SendRequest {
Id = connectionId,
Message = "{\"type\": \"request\", \"id\": \"123\"}",
TimeoutMs = 5000
});
Console.WriteLine($"Got response: {response.Response}");
// Fire-and-forget
await client.SendRawAsync(new SendRawRequest {
Id = connectionId,
Message = "{\"type\": \"notification\"}"
});
Data Models
ConnectRequest
message ConnectRequest {
string Url = 1;
bool AutoReconnect = 2;
}
ConnectResponse
message ConnectResponse {
string Id = 1;
string Status = 2;
}
WebSocketConnection
message WebSocketConnection {
string Id = 1;
string Url = 2;
string Status = 3;
string Topic = 4;
string InstanceId = 5;
}
SendRequest
message SendRequest {
string Id = 1;
string Message = 2;
int32 TimeoutMs = 3;
}
SendResponse
message SendResponse {
string Response = 1;
}
Error Handling
Connection Not Found
try {
await client.DisconnectAsync(new DisconnectRequest { Id = "nonexistent" });
}
catch (Grpc.Core.RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
Console.WriteLine("Connection not found");
}
Send Timeout
try {
var response = await client.SendAsync(new SendRequest {
Id = connectionId,
Message = "{\"action\": \"slow\"}",
TimeoutMs = 100 // very short timeout
});
}
catch (Grpc.Core.RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Send timed out");
}
Connection Errors
try {
using var client = new WebSocketManagerClient("unreachable-host", 5001);
var response = await client.ConnectAsync(new ConnectRequest { Url = "wss://invalid" });
}
catch (Grpc.Core.RpcException ex)
{
Console.WriteLine($"Connection failed: {ex.Status}");
}
Complete Example
using Grpc.Core;
using Virtufin.WebSocketManager.Client;
using Virtufin.WebSocketManager.Protos;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("WebSocketManager Client Demo");
Console.WriteLine("==============================\n");
using var client = new WebSocketManagerClient("localhost", 5001);
// Connect
Console.WriteLine("Connecting to WebSocket server...");
var connectResponse = await client.ConnectAsync(new ConnectRequest {
Url = "wss://echo.websocket.org",
AutoReconnect = false
});
Console.WriteLine($"Connected: {connectResponse.Id} - {connectResponse.Status}");
// List connections
Console.WriteLine("\nAll connections:");
var listResponse = await client.ListAsync(new ListRequest());
foreach (var c in listResponse.Connections) {
Console.WriteLine($" {c.Id}: {c.Url} [{c.Status}]");
if (!string.IsNullOrEmpty(c.Topic))
Console.WriteLine($" Publishing to: {c.Topic}");
}
// Send a message
Console.WriteLine("\nSending message...");
var sendResponse = await client.SendAsync(new SendRequest {
Id = connectResponse.Id,
Message = "Hello, WebSocket server!",
TimeoutMs = 5000
});
Console.WriteLine($"Response: {sendResponse.Response}");
// Start pub/sub
Console.WriteLine("\nStarting pub/sub...");
await client.StartPublishAsync(new StartPublishRequest {
Id = connectResponse.Id,
Topic = "ws-events"
});
Console.WriteLine("Publishing started");
// Stop pub/sub
Console.WriteLine("\nStopping pub/sub...");
await client.StopPublishAsync(new StopPublishRequest { Id = connectResponse.Id });
Console.WriteLine("Publishing stopped");
// Disconnect
Console.WriteLine("\nDisconnecting...");
await client.DisconnectAsync(new DisconnectRequest { Id = connectResponse.Id });
Console.WriteLine("Disconnected");
}
}
Protobuf Dependencies
The client library requires:
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Grpc.Tools" />
The proto file (websocketmanager.proto) is compiled into:
- Virtufin.WebSocketManager.Protos.WebSocketManager
- Virtufin.WebSocketManager.Protos.WebSocketManagerClient
- Request/Response message classes