Server-to-Client Handlers
The server can call methods on the client and optionally await a response. Register handlers on the client to respond to these calls.
Typed handlers via interfaces
The cleanest approach: define a shared contract interface, implement it on the client, and register it.
1. Define the contract (shared library)
[SignalARRRContract]
public interface IChatClient
{
void ReceiveMessage(string user, string message);
Task<string> GetClientName();
}2. Implement on the client
public class ChatClientHandler : IChatClient
{
public void ReceiveMessage(string user, string message)
{
Console.WriteLine($"{user}: {message}");
}
public Task<string> GetClientName()
{
return Task.FromResult(Environment.MachineName);
}
}3. Register before connecting
var connection = HARRRConnection.Create(builder => { ... });
// Register with an instance
connection.RegisterInterface<IChatClient, ChatClientHandler>(new ChatClientHandler());
// Or let SignalARRR create the instance (parameterless constructor)
connection.RegisterInterface<IChatClient, ChatClientHandler>();
// Or with a factory (for dependency injection)
connection.RegisterInterface<IChatClient, ChatClientHandler>(sp => new ChatClientHandler(sp.GetRequiredService<ILogger>()));
await connection.StartAsync();4. Server calls the client
// In a ServerMethods class or anywhere with access to ClientContext
var client = clientContext.GetTypedMethods<IChatClient>();
client.ReceiveMessage("System", "Welcome!"); // fire-and-forget
var name = await client.GetClientName(); // awaits responseRegister before StartAsync
RegisterInterface must be called before StartAsync(). The server may invoke client methods immediately after connection.
Register ad-hoc handlers
Use OnServerRequest() to register handlers by method name:
// Handle a void method (fire-and-forget from server)
connection.OnServerRequest("ReceiveMessage", (string user, string message) =>
{
Console.WriteLine($"{user}: {message}");
return null;
});
// Handle a method with a return value (server awaits response)
connection.OnServerRequest<string>("GetClientName", name =>
{
return Environment.MachineName;
});Typed overloads
OnServerRequest supports up to 4 typed parameters:
// 1 parameter
connection.OnServerRequest<string>("Echo", input => input.ToUpper());
// 2 parameters
connection.OnServerRequest<string, int>("Repeat", (text, count) =>
string.Concat(Enumerable.Repeat(text, count)));
// 3 parameters
connection.OnServerRequest<string, string, bool>("Compare", (a, b, ignoreCase) =>
string.Equals(a, b, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));Extension method overloads
The HARRRConnectionExtensions class provides On<T>() overloads for up to 16 parameters:
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Console.WriteLine($"{user}: {message}");
});How it works
When the server calls a client method:
- Server sends
InvokeServerRequest(expects reply) orInvokeServerMessage(fire-and-forget) - Client receives the message and dispatches to the registered handler
- If
InvokeServerRequest, the handler's return value is sent back automatically via SignalR's native client results (no separate reply message needed)
The ChallengeAuthentication message is handled automatically — the client's token factory is called and the token is sent back without developer intervention.
Next steps
- Server Methods — how the server calls client methods
- Cancellation Propagation — server-initiated cancellation
- TypeScript Client — same pattern in TypeScript