Detailed SignalRGen Tutorial
This comprehensive tutorial walks through creating a complete SignalR solution with SignalRGen
, including project setup and configuration.
TIP
If you want a more concise version, head over to getting started
Project Setup
Create Solution Structure
First, let's create a solution with three projects:
- A server project to host our SignalR hub
- A client project to connect to the hub
- A shared interface project that defines our SignalR contract
# Create solution
mkdir MySignalRGenExample
cd MySignalRGenExample
dotnet new sln -n MySignalRGenExample
# Create server project
dotnet new web -n MyServer
dotnet sln add MyServer/MyServer.csproj
# Create client project
dotnet new web -n MyClient
dotnet sln add MyClient/MyClient.csproj
# Create shared interface project
dotnet new classlib -n MySharedInterface
dotnet sln add MySharedInterface/MySharedInterface.csproj
Install Required Packages
Install SignalRGen
in the shared interface project:
cd MySharedInterface
dotnet add package SignalRGen
dotnet add package Microsoft.AspNetCore.SignalR.Client
Then modify the package reference in your MySharedInterface.csproj
file:
<PackageReference Include="SignalRGen" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Add Project References
Make the shared interface available to both client and server:
# From the solution root
dotnet add MyServer/MyServer.csproj reference MySharedInterface/MySharedInterface.csproj
dotnet add MyClient/MyClient.csproj reference MySharedInterface/MySharedInterface.csproj
Define the SignalR Interface
Create the interface in your shared project:
// MySharedInterface/IPingPongHub.cs
using SignalRGen.Generator;
namespace MySharedInterface;
[HubClient(HubUri = "ping-pong")]
public interface IPingPongHub
{
// Client calls this method on the server
[ClientToServerMethod]
Task Ping(string message);
// Server calls this method on the client
Task Pong(string answer);
}
Server Implementation
Create the Hub
Implement the hub in your server project:
// MyServer/PongHub.cs
using Microsoft.AspNetCore.SignalR;
using MySharedInterface;
namespace MyServer;
public class PongHub : Hub<IPingPongHub>, IPingPongHub
{
private readonly ILogger<PongHub> _logger;
public PongHub(ILogger<PongHub> logger)
{
_logger = logger;
}
public Task Ping(string message)
{
_logger.LogInformation("Received Ping: {Message}", message);
return Clients.All.Pong("Hey, here is the server talking!");
}
public Task Pong(string answer)
{
throw new InvalidOperationException("This is a Server-to-Client method, hence it is not implemented!");
}
}
Configure Server Endpoint
Register the hub in your server's Program.cs
:
// MyServer/Program.cs
using MyServer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
var app = builder.Build();
// Map the SignalR hub to the path matching our HubUri
app.MapHub<PongHub>($"/{PingPongHub.HubUri}");
app.Run();
Client Implementation
Register the Hub
Configure the hub in your client's Program.cs
:
// MyClient/Program.cs
using MyClient;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Register the SignalR hub generated by SignalRGen
builder.Services.AddSignalRHubs(c => c.HubBaseUri = new Uri("http://localhost:5160"))
.WithPingPongHub();
// Register our background worker
builder.Services.AddHostedService<Worker>();
var app = builder.Build();
// Add an endpoint to trigger a ping
app.MapGet("/ping", async ([FromServices] PingPongHub hub) =>
{
await hub.InvokePingAsync("Hello from the client!");
return "Ping sent!";
});
app.Run();
Create a Background Service
Implement a worker service to maintain the SignalR connection:
// MyClient/Worker.cs
using MySharedInterface;
namespace MyClient;
public class Worker : IHostedService
{
private readonly ILogger<Worker> _logger;
private readonly PingPongHub _hub;
public Worker(ILogger<Worker> logger, PingPongHub hub)
{
_logger = logger;
_hub = hub;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting SignalR connection...");
// Subscribe to the Pong event
_hub.OnPong += OnPongReceived;
// Start the connection
return _hub.StartAsync(cancellationToken: cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping SignalR connection...");
// Unsubscribe from the event
_hub.OnPong -= OnPongReceived;
// Stop the connection
return _hub.StopAsync(cancellationToken: cancellationToken);
}
private Task OnPongReceived(string answer)
{
_logger.LogInformation("Received Pong: {Answer}", answer);
return Task.CompletedTask;
}
}
Testing the Implementation
- Start both projects (in separate terminals):
# Terminal 1 - Start the server
cd MyServer
dotnet run
# Terminal 2 - Start the client
cd MyClient
dotnet run
Navigate to
http://localhost:5000/ping
in your browser (adjust the port if necessary).The client will send a ping message to the server, which will respond with a pong.
Check the client console logs to see the received pong message.
Understanding What Happened
- The
IPingPongHub
interface defined our SignalR contract SignalRGen
generated a strongly typed client (PingPongHub
)- The server implemented the hub with methods matching our interface
- The client connected to the hub and subscribed to events
- When the
/ping
endpoint was triggered, the client sent a message to the server - The server responded with a pong message, which the client received as an event
Important Notes
- Naming Convention: Note that the generated client class has the same name as the interface but without the "I" prefix.
- Event Handling: Server-to-client methods are exposed as events on the generated client with an "On" prefix.
- Method Invocation: Client-to-server methods are exposed as methods on the generated client with an "Invoke" prefix and "Async" suffix.