Tharga.Communication
0.2.0
dotnet add package Tharga.Communication --version 0.2.0
NuGet\Install-Package Tharga.Communication -Version 0.2.0
<PackageReference Include="Tharga.Communication" Version="0.2.0" />
<PackageVersion Include="Tharga.Communication" Version="0.2.0" />
<PackageReference Include="Tharga.Communication" />
paket add Tharga.Communication --version 0.2.0
#r "nuget: Tharga.Communication, 0.2.0"
#:package Tharga.Communication@0.2.0
#addin nuget:?package=Tharga.Communication&version=0.2.0
#tool nuget:?package=Tharga.Communication&version=0.2.0
Tharga Communication
A SignalR-based communication framework for .NET with built-in message handler patterns for request-response and fire-and-forget messaging.
Features
- Fire-and-forget and request-response messaging patterns
- Automatic message handler discovery via dependency injection
- Client connection tracking with metadata
- Automatic reconnection with configurable delays
- Extensible client state storage
- Pluggable API key validation with custom validators
- Subscription-based messaging with type and data-level granularity
Getting started
Server
builder.AddThargaCommunicationServer(options =>
{
options.RegisterClientStateService<MyClientStateService>();
options.RegisterClientRepository<MemoryClientRepository<ClientConnectionInfo>, ClientConnectionInfo>();
});
app.UseThargaCommunicationServer();
Client
Add to appsettings.json:
{
"Tharga": {
"Communication": {
"ServerAddress": "https://localhost:5001"
}
}
}
builder.AddThargaCommunicationClient();
API key validation
By default, the server accepts all connections. To require API keys, set ApiKeys on the server options:
builder.AddThargaCommunicationServer(options =>
{
options.ApiKeys = ["my-secret-key", "rotation-key"];
options.RegisterClientStateService<MyClientStateService>();
options.RegisterClientRepository<MemoryClientRepository<ClientConnectionInfo>, ClientConnectionInfo>();
});
Clients send the key via CommunicationOptions.ApiKey (or appsettings.json):
builder.AddThargaCommunicationClient(o => o.ApiKey = "my-secret-key");
Custom validators
For more advanced scenarios (per-key lookup in a database, per-IP allowlists, integration with Tharga.Platform's API key management, etc.), register an IApiKeyValidator:
public class MyApiKeyValidator : IApiKeyValidator
{
private readonly IApiKeyAdministrationService _keys;
public MyApiKeyValidator(IApiKeyAdministrationService keys) => _keys = keys;
public async Task<ApiKeyValidationResult> ValidateAsync(string apiKey, CancellationToken ct = default)
{
var key = await _keys.GetByValueAsync(apiKey, ct);
return key is null
? new() { IsValid = false }
: new() { IsValid = true, KeyId = key.Id.ToString(), KeyName = key.Name };
}
}
builder.AddThargaCommunicationServer(options =>
{
options.RegisterApiKeyValidator<MyApiKeyValidator>();
// …
});
The validator decides everything — including whether to accept empty keys (return IsValid = true, KeyId = null to allow anonymous), or how to identify the matched key. KeyId and KeyName flow through to IClientConnectionInfo, where consumers can use them for admin UIs and audit logs.
Need HTTP context? Inject
IHttpContextAccessorinto your validator if you need access to the request (IP allowlists, custom headers, etc.). The framework intentionally does not passHttpContextto keepIApiKeyValidatornarrow and testable.
Client identity
The client sends four self-reported identity headers during connection: Instance (Guid generated per process run), Machine, Type (assembly name), and Version. Two of those — Machine and Type — can be overridden via options:
builder.AddThargaCommunicationClient(o =>
{
o.ServerAddress = "https://localhost:5001";
o.ClientType = "monitor-agent"; // override; defaults to entry assembly name
o.ClientMachine = "us-east-prod-1"; // override; defaults to Environment.MachineName
});
Useful when one assembly hosts multiple roles, or when containerized hosts have meaningless hash-based hostnames.
Message handlers
// Fire-and-forget
public class MyHandler : PostMessageHandlerBase<MyMessage>
{
public override Task Handle(MyMessage message) => Task.CompletedTask;
}
// Request-response
public class PingHandler : SendMessageHandlerBase<PingRequest, PingResponse>
{
public override Task<PingResponse> Handle(PingRequest message) =>
Task.FromResult(new PingResponse("Pong"));
}
Subscription messaging
Subscriptions allow the server to signal clients whether anyone is consuming a particular message type, so clients can skip sending data when no dashboard or consumer is active.
Server side (consumer/dashboard)
// Type-based: subscribe to all messages of a type
await using var sub = await serverCommunication.SubscribeAsync<CollectionDto>();
// Data-based: subscribe to a specific entity
await using var sub = await serverCommunication.SubscribeAsync<FarmDetailsDto>(farmId.ToString());
// Monitor active subscriptions
IReadOnlyDictionary<string, int> active = serverCommunication.GetSubscriptions();
In Blazor, tie the subscription to the page lifecycle:
@implements IAsyncDisposable
@inject IServerCommunication ServerCommunication
@code {
private IAsyncDisposable? _subscription;
protected override async Task OnInitializedAsync()
{
_subscription = await ServerCommunication.SubscribeAsync<FarmDetailsDto>(FarmId.ToString());
}
public async ValueTask DisposeAsync()
{
if (_subscription != null) await _subscription.DisposeAsync();
}
}
Client side (agent/producer)
// Check before sending
if (clientCommunication.HasSubscribers<FarmDetailsDto>(farmId.ToString()))
await clientCommunication.PostAsync(farmDetails);
// Or use the convenience method (no-ops when no subscribers)
await clientCommunication.PostIfSubscribedAsync(farmDetails, farmId.ToString());
// React to subscription changes
clientCommunication.SubscriptionChanged += (sender, e) =>
{
Console.WriteLine($"{e.Topic}:{e.Key} → {(e.HasSubscribers ? "active" : "inactive")}");
};
Matching rules
- Type-based (
SubscribeAsync<T>()without key): wildcard —HasSubscribers<T>("anyKey")returnstrue. - Data-based (
SubscribeAsync<T>("1")with key): specific — onlyHasSubscribers<T>("1")returnstrue.
For full documentation and examples, see the GitHub repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- Microsoft.AspNetCore.SignalR.Client (>= 10.0.8)
- Microsoft.AspNetCore.SignalR.Client.Core (>= 10.0.8)
- Tharga.Runtime (>= 0.1.12)
-
net9.0
- Microsoft.AspNetCore.SignalR.Client (>= 10.0.8)
- Microsoft.AspNetCore.SignalR.Client.Core (>= 10.0.8)
- Tharga.Runtime (>= 0.1.12)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Tharga.Communication:
| Package | Downloads |
|---|---|
|
Tharga.MongoDB.Monitor.Client
Forwards MongoDB monitoring data to a central server via Tharga.Communication. |
|
|
Tharga.MongoDB.Monitor.Server
Receives MongoDB monitoring data from remote agents via Tharga.Communication and aggregates it into the local IDatabaseMonitor. |
|
|
Tharga.Communication.Mcp
Exposes Tharga.Communication runtime data (connected clients, active subscriptions, registered handlers) via MCP (Model Context Protocol). Plugs into Tharga.Mcp. |
GitHub repositories
This package is not used by any popular GitHub repositories.