Cirreum.RemoteConnections.SignalR
2.0.2
dotnet add package Cirreum.RemoteConnections.SignalR --version 2.0.2
NuGet\Install-Package Cirreum.RemoteConnections.SignalR -Version 2.0.2
<PackageReference Include="Cirreum.RemoteConnections.SignalR" Version="2.0.2" />
<PackageVersion Include="Cirreum.RemoteConnections.SignalR" Version="2.0.2" />
<PackageReference Include="Cirreum.RemoteConnections.SignalR" />
paket add Cirreum.RemoteConnections.SignalR --version 2.0.2
#r "nuget: Cirreum.RemoteConnections.SignalR, 2.0.2"
#:package Cirreum.RemoteConnections.SignalR@2.0.2
#addin nuget:?package=Cirreum.RemoteConnections.SignalR&version=2.0.2
#tool nuget:?package=Cirreum.RemoteConnections.SignalR&version=2.0.2
Cirreum.RemoteConnections.SignalR
SignalR transport for long-lived Cirreum client connections
Overview
Cirreum.RemoteConnections.SignalR is the SignalR implementation of Cirreum's IRemoteConnection abstraction — a typed, lifecycle-managed client connection backed by HubConnection.
The framework owns the concerns that otherwise drift between applications: DI lifetime, reconnect policy, credential acquisition and refresh across reconnects, observable connection state for UI binding, and deterministic disposal. Applications write a derived connection type exposing typed methods; the wire API stays native, reachable through a configure delegate.
The package is host-neutral. A Blazor WASM client connecting to its backend and a server-side service subscribing to another service use the same type.
Usage
Derive a connection type. The framework-supplied context is its first constructor parameter; anything else resolves from the container as usual:
public sealed class ChatConnection(SignalRRemoteConnectionContext context)
: SignalRRemoteConnection(context) {
public IDisposable OnMessage(Func<ChatMessage, Task> handler) =>
this.On("ReceiveMessage", handler);
// Fire-and-forget, one argument
public Task SendMessageAsync(ChatMessage message, CancellationToken ct = default) =>
this.SendAsync("SendMessage", message, ct);
// A client method the hub invokes with several arguments
public IDisposable OnToolComplete(Func<string, bool, Task> handler) =>
this.On("ReceiveToolComplete", handler);
// Several arguments
public Task SendToRoomAsync(string room, string text, CancellationToken ct = default) =>
this.SendAsync("SendToRoom", [room, text], ct);
// Request/response
public Task<string> StartConversationAsync(string context, CancellationToken ct = default) =>
this.InvokeAsync<string>("StartConversation", [context], ct);
}
Register it:
services.AddSingleton(sp => new ChatConnection(
SignalRRemoteConnectionContext.Create<ChatConnection>(sp, new RemoteConnectionOptions("MyApp") {
EndpointUri = new Uri("https://api.example.com/hubs/chat"),
Scopes = ["api://contoso/access_as_user"],
})));
// Optional: expose it for status surfaces that render every connection's state
services.AddSingleton<IRemoteConnection>(sp => sp.GetRequiredService<ChatConnection>());
Applications composing through a Cirreum application builder normally register through the matching Runtime Extensions package instead, which reduces the above to a single builder call and adds a per-session registration for connections that belong to one call or one bridge rather than to the application.
Registration does not connect. Connect when the caller is ready — typically after sign-in, not at startup:
await connection.ConnectAsync();
What the base owns
Lifetime —
ConnectAsyncis idempotent and coalesces concurrent callers;DisposeAsyncstops and releases the transport once.State —
StateandStateChangedreportConnecting/Connected/Reconnecting/Disconnecting/Disconnected, for binding spinners, toasts and offline banners.Identity —
ConnectionIdis assigned by the adapter and stable for the connection's life, including across reconnects. The transport's own identifier, which changes on every reconnect, isServerConnectionId.Reconnection — retries indefinitely with capped, jittered backoff. SignalR's own default stops after four attempts, which strands a connection a user expects to stay open. Override
OnReconnectedAsyncto restore server-side session state that does not survive a reconnect, such as group membership.Credentials — resolved on every connect and reconnect attempt, so a credential refreshes with no application code. Postures resolve in a fixed order, and the first one set wins:
# Posture Set by 1 CredentialProvidera callback on the connection's options 2 AuthorizationHeadercarrying a valuethe connection's options 3 AuthorizationHeaderSettings.Nonethe connection's options, to connect deliberately without one 4 the ambient IRemoteConnectionCredentialSourcethe host runtime, or the application The ambient source is told what it is supplying for — endpoint, the
Scopesthe options declare, and the connection type — and a source registered keyed to that type is preferred over the unkeyed one, so one connection can use a different mechanism than another.A resolved credential has three answers: a value to present,
Noneto connect without one, andnullmeaning none is available — which fails the connect rather than connecting anonymously.A bearer credential rides SignalR's own token path, so it travels as a header where the transport can carry one and as an
access_tokenquery parameter where it cannot, and it is re-resolved on every attempt.Any other scheme must be a static
AuthorizationHeader: the transport copies its configured headers when it builds the client for an attempt, before the credential callback runs, so a non-Bearer credential resolved per attempt would reach no request. Returning one from a callback or a source is rejected rather than silently dropped. A non-Bearer header also travels as a header only, which a browser cannot set on a WebSocket upgrade.Callbacks —
On<T>binds a single argument, andOn<T1,T2>throughOn<T1..T8>bind the rest: SignalR's protocol carries an argument array, so a hub declaring a client method with several parameters invokes it with several arguments.Invocation —
InvokeAsync<TResult>awaits a result;InvokeAsyncawaits a hub method that returns none, whichSendAsynccannot do since it completes once the message is sent.
Anything the transport offers beyond this surface — streaming, for instance — is reachable through
the protected HubConnection, and the native IHubConnectionBuilder is exposed through a configure
delegate that runs last, so an application can override anything the framework set.
Documentation
Contribution Guidelines
Be conservative with new abstractions
The API surface must remain stable and meaningful.Limit dependency expansion
Only add foundational, version-stable dependencies.Favor additive, non-breaking changes
Breaking changes ripple through the entire ecosystem.Include thorough unit tests
All primitives and patterns should be independently testable.Document architectural decisions
Context and reasoning should be clear for future maintainers.Follow .NET conventions
Use established patterns from Microsoft.Extensions.* libraries.
Versioning
Cirreum.RemoteConnections.SignalR follows Semantic Versioning:
- Major - Breaking API changes
- Minor - New features, backward compatible
- Patch - Bug fixes, backward compatible
License
This project is licensed under the MIT License - see the LICENSE file for details.
Cirreum Foundation Framework
Layered simplicity for modern .NET
| 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
- Cirreum.Domain (>= 5.0.1)
- Microsoft.AspNetCore.SignalR.Client (>= 10.0.11)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Cirreum.RemoteConnections.SignalR:
| Package | Downloads |
|---|---|
|
Cirreum.Runtime.RemoteConnections.SignalR
Runtime Extensions package for Cirreum SignalR remote connections — app-facing AddRemoteConnection<TConnection>() and AddRemoteConnectionFactory<TConnection>() extensions that register a typed, lifecycle-managed SignalR client connection from options. |
GitHub repositories
This package is not used by any popular GitHub repositories.