A SignalR-based communication framework for .NET with built-in message handler patterns for request-response and fire-and-forget messaging between clients and servers.
- Fire-and-forget messaging - Send one-way messages from client to server or server to client(s)
- Request-response messaging - Send a request and await a typed response with configurable timeout
- Automatic handler discovery - Message handlers are discovered and registered via dependency injection
- Client connection tracking - Track connected clients with metadata (machine name, app type, version)
- Automatic reconnection - Configurable reconnect delays for client connections
- Extensible storage - Abstract repository pattern for client state with an in-memory default
dotnet add package Tharga.Communication
var builder = WebApplication.CreateBuilder(args);
builder.AddThargaCommunicationServer(options =>
{
options.RegisterClientStateService<MyClientStateService>();
options.RegisterClientRepository<MemoryClientRepository<ClientConnectionInfo>, ClientConnectionInfo>();
});
var app = builder.Build();
app.UseThargaCommunicationServer();
app.Run();Add the configuration section to appsettings.json:
{
"Tharga": {
"Communication": {
"ServerAddress": "https://localhost:5001"
}
}
}Register the client services:
var builder = Host.CreateApplicationBuilder(args);
builder.AddThargaCommunicationClient();public record MyNotification(string Text);
public class MyNotificationHandler : PostMessageHandlerBase<MyNotification>
{
public override Task Handle(MyNotification message)
{
Console.WriteLine(message.Text);
return Task.CompletedTask;
}
}Register the handler in DI:
builder.Services.AddTransient<PostMessageHandlerBase<MyNotification>, MyNotificationHandler>();public record PingRequest(string Message);
public record PingResponse(string Reply);
public class PingHandler : SendMessageHandlerBase<PingRequest, PingResponse>
{
public override Task<PingResponse> Handle(PingRequest message)
{
return Task.FromResult(new PingResponse($"Pong: {message.Message}"));
}
}From the client:
public class MyService(IClientCommunication client)
{
public async Task NotifyServer()
{
await client.PostAsync(new MyNotification("Hello from client"));
}
}From the server:
public class MyServerService(IServerCommunication server)
{
public async Task NotifyClient(string connectionId)
{
await server.PostAsync(connectionId, new MyNotification("Hello from server"));
}
public async Task NotifyAll()
{
await server.PostToAllAsync(new MyNotification("Broadcast message"));
}
public async Task<PingResponse> PingClient(string connectionId)
{
var response = await server.SendMessageAsync<PingRequest, PingResponse>(
connectionId, new PingRequest("Ping"));
return response.Value;
}
}public class MyClientStateService : ClientStateServiceBase<ClientConnectionInfo>
{
public MyClientStateService(IServiceProvider sp, IOptions<CommunicationOptions> options)
: base(sp, options) { }
protected override ClientConnectionInfo Build(IClientConnectionInfo info) =>
new()
{
Instance = info.Instance,
ConnectionId = info.ConnectionId,
Machine = info.Machine,
Type = info.Type,
Version = info.Version,
IsConnected = info.IsConnected,
ConnectTime = info.ConnectTime
};
protected override ClientConnectionInfo BuildDisconnect(ClientConnectionInfo info, DateTime disconnectTime) =>
info with { IsConnected = false, DisconnectTime = disconnectTime };
}| Property | Description | Default |
|---|---|---|
ServerAddress |
The server URL to connect to | (required) |
Pattern |
The hub endpoint pattern | "hub" |
ReconnectDelays |
Delays between reconnection attempts | [0s, 2s, 10s, 30s] |
The server requires registering a ClientStateServiceBase implementation and a ClientRepositoryBase implementation via the options callback. Use MemoryClientRepository<T> for an in-memory default.