TypedSignalR.Client 3.3.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package TypedSignalR.Client --version 3.3.0                
NuGet\Install-Package TypedSignalR.Client -Version 3.3.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TypedSignalR.Client" Version="3.3.0">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TypedSignalR.Client --version 3.3.0                
#r "nuget: TypedSignalR.Client, 3.3.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install TypedSignalR.Client as a Cake Addin
#addin nuget:?package=TypedSignalR.Client&version=3.3.0

// Install TypedSignalR.Client as a Cake Tool
#tool nuget:?package=TypedSignalR.Client&version=3.3.0                



C# Source Generator to create strongly typed SignalR clients.

Table of Contents


NuGet: TypedSignalR.Client

dotnet add package Microsoft.AspNetCore.SignalR.Client
dotnet add package TypedSignalR.Client

Why TypedSignalR.Client?

The ASP.NET Core SignalR C# client is not strongly typed. To call a Hub (server-side) method, we must specify the method defined in Hub using a string. We also have to determine the return type manually. Moreover, registering client methods called from a server also requires specifying the method name as a string, and we must set parameter types manually.

// ASP.NET Core SignalR Client

// Specify a hub method to invoke using string.
await connection.InvokeAsync("HubMethod1");

// Manually determine a return type.
// Parameters are cast to object type.
var guid = await connection.InvokeAsync<Guid>("HubMethod2", "message", 99);

// Registering a client method requires a string, and parameter types must be set manually.
var subscription = connection.On<string, DateTime>("ClientMethod", (message, dateTime) => {});

These are very painful and cause bugs easily. Moreover, if we change the code on the server-side, the modification on the client-side becomes very troublesome. The leading cause of the problems is that they are not strongly typed.

TypedSignalR.Client aims to generate strongly typed SignalR clients using interfaces in which the server and client methods are defined. Defining interfaces is helpful not only for the client-side but also for the server-side. See Usage section for details.

// TypedSignalR.Client

// First, create a hub proxy.
IHub hubProxy = connection.CreateHubProxy<IHub>();

// Invoke a hub method through hub proxy.
// We no longer need to specify the method using a string.
await hubProxy.HubMethod1();

// Both parameters and return types are strongly typed.
var guid = await hubProxy.HubMethod2("message", 99);

// Client method registration is also strongly typed, so it's safe and easy.
var subscription = connection.Register<IReceiver>(new Receiver());

// Defining interfaces are useful not only for the client-side but also for the server-side.
// See Usage in this README.md for details.
interface IHub
    Task HubMethod1();
    Task<Guid> HubMethod2(string message, int value);

interface IReceiver
    Task ClientMethod(string message, DateTime dateTime);

class Receiver : IReceiver
    // implementation


This Source Generator provides two extension methods and one interface.

static class HubConnectionExtensions
    THub CreateHubProxy<THub>(this HubConnection connection, CancellationToken cancellationToken = default){...}
    IDisposable Register<TReceiver>(this HubConnection connection, TReceiver receiver){...}

// An interface for observing SignalR events.
interface IHubConnectionObserver
    Task OnClosed(Exception? exception);
    Task OnReconnected(string? connectionId);
    Task OnReconnecting(Exception? exception);

Use it as follows.

HubConnection connection = ...;

IHub hub = connection.CreateHubProxy<IHub>();
IDisposable subscription = connection.Register<IReceiver>(new Receiver());


For example, we have the following interface defined.

public class UserDefinedType
    public Guid Id { get; set; }
    public DateTime Datetime { get; set; }

// The return type of methods on the client-side must be Task. 
public interface IClientContract
    // Of course, user defined type is OK. 
    Task ClientMethod1(string user, string message, UserDefinedType userDefine);
    Task ClientMethod2();

// The return type of methods on the hub-side must be Task or Task<T>. 
public interface IHubContract
    Task<string> HubMethod1(string user, string message);
    Task HubMethod2();

class Receiver1 : IClientContract
    // implementation

class Receiver2 : IClientContract, IHubConnectionObserver
    // implementation


It's very easy to use.

HubConnection connection = ...;

var hub = connection.CreateHubProxy<IHubContract>();
var subscription1 = connection.Register<IClientContract>(new Receiver1());

// When an instance of a class that implements IHubConnectionObserver is registered (Receiver2 in this case), 
// the method defined in IHubConnectionObserver is automatically registered regardless of the type argument. 
var subscription2 = connection.Register<IClientContract>(new Receiver2());

// Invoke hub methods
hub.HubMethod1("user", "message");

// Unregister the receiver

In ASP.NET Core SignalR, CancellationToken is passed for each invoke.

On the other hand, in TypedSignalR.Client, CancellationToken is passed only once when creating a hub proxy. The passed CancelationToken will be used for each invoke internally.

var cts = new CancellationTokenSource();

// The following two are equivalent.

// 1: ASP.NET Core SignalR Client
var ret =  await connection.InvokeAsync<string>("HubMethod1", "user", "message", cts.Token);
await connection.InvokeAsync("HubMethod2", cts.Token);

// 2: TypedSignalR.Client
var hubProxy = connection.CreateHubProxy<IHubContract>(cts.Token);
var ret = await hubProxy.HubMethod1("user", "message");
await hubProxy.HubMethod2();


Using the interface definitions, we can write as follows on the server-side (ASP.NET Core). TypedSignalR.Client is not necessary.

using Microsoft.AspNetCore.SignalR;

public class SomeHub : Hub<IClientContract>, IHubContract
    public async Task<string> HubMethod1(string user, string message)
        var instance = new UserDefinedType()
            Id = Guid.NewGuid(),
            DateTime = DateTime.Now,

        // broadcast
        await this.Clients.All.ClientMethod1(user, message, instance);
        return "OK!";

    public async Task HubMethod2()
        await this.Clients.Caller.ClientMethod2();


Sharing a Project

I recommend that these interfaces be shared between the client-side and server-side project, for example, by project references.

server.csproj --> shared.csproj <-- client.csproj

Client Code Format

It is easier to handle if we write client code in the following format.

class Client : IReceiver, IHubConnectionObserver, IDisposable
    private readonly IHub _hubProxy;
    private readonly IDisposable _subscription;
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    public Client(HubConnection connection)
        _hubProxy = connection.CreateHubProxy<IHub>(_cancellationTokenSource.Token);
        _subscription = connection.Register<IReceiver>(this);

    // implementation

Streaming Support

SignalR supports both server-to-client streaming and client-to-server streaming.

TypedSignalR.Client supports both server-to-client streaming and client-to-server streaming. If you use IAsyncEnumerable<T>, Task<IAsyncEnumerable<T>>, or Task<ChannelReader<T>> for the method return type, it is analyzed as server-to-client streaming. And if IAsyncEnumerable<T> or ChannelReader<T> is used in the method parameter, it is analyzed as client-to-server streaming.

When using server-to-client streaming, a single CancellationToken can be used as a method parameter (Note: CancellationToken cannot be used as a parameter except for server-to-client streaming).

Client Results Support

.NET 7 and later, you can use client results.

TypedSignalR.Client supports client results. If you use Task<T> for the method return type in the receiver interface, you can use client results.

Compile-Time Error Support

This library has some restrictions, including those that come from server-side implementations.

  • Type argument of the CreateHubProxy/Register method must be an interface.
  • Only method definitions are allowed in the interface used for CreateHubProxy/Register.
    • It is forbidden to define properties and events.
  • The return type of the method in the interface used for CreateHubProxy must be Task or Task<T>.
  • The return type of the method in the interface used for Register must be Task.

It is complicated for humans to comply with these restrictions properly. So, this library looks for parts that do not follow the restriction and report detailed errors at compile time. Therefore, no runtime error occurs.


Generated Source Code

TypedSignalR.Client checks the type argument of a methods CreateHubProxy and Register and generates source code. Generated source code can be seen in Visual Studio.


There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on TypedSignalR.Client:

Repository Stars
C# Source Generator to Create Strongly Typed SignalR Clients.
Version Downloads Last updated
3.5.2 12,181 4/3/2024
3.5.1 212 4/2/2024
3.5.0 321 3/31/2024
3.4.4 146 3/31/2024
3.4.3 10,912 12/5/2023
3.4.2 2,118 11/27/2023
3.4.1 495 11/25/2023
3.4.0 9,933 4/28/2023
3.3.0 1,111 2/7/2023
3.2.1 3,778 11/27/2022
3.1.1 7,332 8/14/2022
3.0.7 666 6/2/2022
3.0.6 511 5/8/2022
3.0.4 478 4/22/2022
3.0.3 436 4/15/2022
3.0.2 499 3/6/2022
3.0.1 429 2/21/2022
2.1.0 343 12/26/2021
2.0.1 2,068 6/16/2021
2.0.0 345 6/10/2021
1.1.0 348 5/13/2021
1.0.2 321 5/12/2021
1.0.1 289 5/11/2021
1.0.0 318 5/11/2021
0.0.1 514 5/11/2021