Typed bidirectional RPC over ASP.NET Core SignalR.
Server and client call each other's methods through shared interfaces — with compile-time proxy generation, streaming, cancellation propagation, and ASP.NET Core authorization. Clients available for .NET, TypeScript/JavaScript, and Swift.
| Package | Purpose |
|---|---|
Cocoar.SignalARRR.Contracts |
[SignalARRRContract] attribute + source generator — reference from shared interface projects |
Cocoar.SignalARRR.Server |
Server-side: HARRR hub, ServerMethods, authorization, ClientManager |
Cocoar.SignalARRR.Client |
Client-side: HARRRConnection, typed proxies, event handlers |
Cocoar.SignalARRR.DynamicProxy |
Opt-in runtime proxy fallback via DispatchProxy |
Cocoar.SignalARRR.Client.FullFramework |
Client for .NET Framework 4.6.2+ — typed proxies, streaming, file transfer |
npm install @cocoar/signalarrr
.package(url: "https://github.com/cocoar-dev/Cocoar.SignalARRR.git", from: "4.0.0")Define shared interfaces, set up the server, and call methods with full type safety:
// Shared interface
[SignalARRRContract]
public interface IChatHub {
Task SendMessage(string user, string message);
Task<List<string>> GetHistory();
}
// Client usage — one line to get a typed proxy
var chat = connection.GetTypedMethods<IChatHub>();
await chat.SendMessage("Alice", "Hello!");// TypeScript client
const history = await connection.invoke<string[]>('ChatMethods.GetHistory');// Swift client — @HubProxy macro generates the proxy
@HubProxy protocol IChatHub { ... }
let chat = connection.getTypedMethods(IChatHubProxy.self)For full setup guides, streaming, authorization, and server-to-client calls, see the documentation.
- Typed bidirectional RPC — server and client call each other through shared interfaces
- Compile-time proxy generation — Roslyn source generator (zero reflection)
- Organized hub methods — split logic across
ServerMethods<T>classes with full DI - Streaming —
IAsyncEnumerable<T>,IObservable<T>,ChannelReader<T>in both directions - HTTP stream references — file download/upload through SignalR hub methods
- CancellationToken propagation — server can cancel client operations remotely
- Authorization — method-level, class-level, and hub-level
[Authorize] - Server-to-client calls from anywhere — inject
ClientManagerin controllers, background services, etc. - Four clients — .NET, .NET Framework, TypeScript/JavaScript, Swift
- Typed broadcasts —
WithHub<T>().WithGroup().SendAsync<T>()for groups and filtered clients - Redis-compatible multi-node backplane — opt-in scale-out with Redis, Valkey, or Garnet
SignalARRR stays pure in-memory by default. If you do not configure a backplane, behavior remains single-node and process-local exactly as before.
For multi-node scale-out, opt in with the built-in Redis-compatible backplane:
builder.Services.AddSignalARRR(b =>
b.AddServerMethodsFrom(typeof(ChatHub).Assembly));
builder.Services.AddSignalARRRRedisBackplane(options => options
.WithConnectionString("localhost:6379,abortConnect=false")
.WithChannelPrefix("my-app")
.WithNodeId($"{Environment.MachineName}-api-1"));This works with Redis, Valkey, and Garnet because SignalARRR talks to a Redis-compatible backend via StackExchange.Redis.
GetTypedMethods<T>(connectionId)send/invoke across nodesWithHub<T>().SendAsync(...)across all nodesWithGroup(...),WithUser(...), andWithAttribute(...)across nodesInvokeAllAsync(...)andInvokeOneAsync(...)across nodesAddToGroupAsync(...)/RemoveFromGroupAsync(...)for remote connections- Presence APIs on
ClientManager:GetConnectionsAsync<THub>()GetConnectionsByUserAsync<THub>(...)GetConnectionsInGroupAsync<THub>(...)GetConnectionsByAttributeAsync<THub>(...)GetOnlineUsersAsync<THub>()IsUserOnlineAsync<THub>(...)
- Transient transport: the backplane distributes live messages; it is not a durable queue or event store.
- Eventual convergence: connection, group, user, and attribute metadata propagate quickly, but not atomically across all nodes. Right after connect/disconnect/group changes there can be a short convergence window.
- Crash cleanup: dead nodes are removed by heartbeat + timeout sweep. Tune
WithHeartbeatInterval(...)andWithNodeTimeout(...)if you want faster stale-node cleanup. - Safe fallback: without
AddSignalARRRRedisBackplane(...), all APIs continue to use the old in-memory single-node path.
| Target | Version |
|---|---|
| .NET (server + client) | .NET 8 / .NET 9 / .NET 10 |
| .NET Framework (client) | 4.6.2+ (via Cocoar.SignalARRR.Client.FullFramework) |
| TypeScript / JavaScript | Node.js 22 / modern browsers |
| Swift (iOS / macOS) | Swift 5.10+, iOS 14+ / macOS 11+ |
# .NET
dotnet build src/Cocoar.SignalARRR.slnx
dotnet test src/Cocoar.SignalARRR.slnx
# TypeScript
cd src/Cocoar.SignalARRR.Typescript && npm install && npm run build
# Swift
swift build && swift testApache License 2.0 — see LICENSE for details.
See CONTRIBUTING.md for guidelines.