### Per-Hub Configuration Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/configuration/config-per-hub.md Register hub-specific configurations after setting up global configuration. This example demonstrates setting HTTP connection options, hub connection builder options, and the service lifetime for a specific hub. ```csharp builder.Services.AddSignalRHubs(c => c.HubBaseUri = new Uri("http://localhost:5160")) // This is the bit that allows you to configure the hub client. .WithPingPongHub(c => { // With this you have access to everything that `HttpConnectionOptions` allows to define. c.HttpConnectionOptionsConfiguration = options => { options.Headers.Add("Authorization", "Bearer "); }; // With this you have access to everything that `HubConnectionBuilder` allows to define. c.HubConnectionBuilderConfiguration = connectionBuilder => { connectionBuilder.WithStatefulReconnect(); }; // With this you can define the lifetime of the hub client based on the `ServiceLifetime` enum. c.HubClientLifetime = ServiceLifetime.Scoped; }); ``` -------------------------------- ### Complete Chat Hub Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md A comprehensive example demonstrating a bidirectional chat hub contract, including server-to-client and client-to-server interfaces and supporting data types. ```APIDOC ## Complete Example Here's a comprehensive chat hub example: ```csharp using SignalRGen.Abstractions; using SignalRGen.Abstractions.Attributes; namespace ChatApp.Contracts; [HubClient(HubUri = "chat")] public interface IChatHubContract : IBidirectionalHub { } // Server-to-client communication public interface IChatHubServerToClient { Task UserJoined(string username); Task UserLeft(string username); Task MessageReceived(ChatMessage message); Task UserTyping(string username); Task UserStoppedTyping(string username); Task RoomUserCountUpdated(int count); } // Client-to-server communication public interface IChatHubClientToServer { Task SendMessage(string message); Task StartTyping(); Task StopTyping(); Task> GetActiveUsers(); Task> GetRecentMessages(int count); } // Supporting data types public record ChatMessage( string Content, string Sender, DateTime Timestamp, string? ReplyToMessageId = null ); ``` ``` -------------------------------- ### Running Server and Client Projects Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/detailed-tutorial.md Instructions to start both the server and client projects in separate terminals to test the SignalR implementation. ```shell # Terminal 1 - Start the server cd MyServer dotnet run # Terminal 2 - Start the client cd MyClient dotnet run ``` -------------------------------- ### Multiple Hub Interfaces Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Illustrates how to define and register multiple distinct hub interfaces for different functionalities within an application. ```APIDOC ## Multiple Hub Interfaces You can define multiple hub interfaces in your application: ```csharp [HubClient(HubUri = "chat")] public interface IChatHubContract : IBidirectionalHub { } [HubClient(HubUri = "notifications")] public interface INotificationHubContract : IServerToClientHub { } [HubClient(HubUri = "telemetry")] public interface ITelemetryHubContract : IClientToServerHub { } ``` Registration: ```csharp services.AddSignalRHubs(options => { options.HubBaseUri = new Uri("https://api.example.com"); }) .WithChatHubContractClient() .WithNotificationHubContractClient() .WithTelemetryHubContractClient(); ``` ``` -------------------------------- ### Add SignalRGen.Testing Package Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Install the SignalRGen.Testing package into your test project using the .NET CLI. ```shell dotnet add package SignalRGen.Testing ``` -------------------------------- ### Install SignalRGen Package Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/getting-started.md Add the SignalRGen package to your shared interface project using the .NET CLI. ```shell dotnet add package SignalRGen ``` -------------------------------- ### Complete Chat Hub Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md A comprehensive example of a SignalR hub contract, including bidirectional communication interfaces, supporting data types, and necessary using directives. ```csharp using SignalRGen.Abstractions; using SignalRGen.Abstractions.Attributes; namespace ChatApp.Contracts; [HubClient(HubUri = "chat")] public interface IChatHubContract : IBidirectionalHub { } // Server-to-client communication public interface IChatHubServerToClient { Task UserJoined(string username); Task UserLeft(string username); Task MessageReceived(ChatMessage message); Task UserTyping(string username); Task UserStoppedTyping(string username); Task RoomUserCountUpdated(int count); } // Client-to-server communication public interface IChatHubClientToServer { Task SendMessage(string message); Task StartTyping(); Task StopTyping(); Task> GetActiveUsers(); Task> GetRecentMessages(int count); } // Supporting data types public record ChatMessage( string Content, string Sender, DateTime Timestamp, string? ReplyToMessageId = null ); ``` -------------------------------- ### Traditional SignalR Client Implementation Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/what-is-signalrgen.md This example demonstrates the traditional approach to implementing a SignalR client, highlighting the boilerplate code, string-based method names, and manual connection management required. It shows the complexity that SignalRGen aims to eliminate. ```csharp // Traditional approach - lots of boilerplate and string-based APIs public class ChatClient : IAsyncDisposable { private readonly HubConnection _connection; private bool _isConnected; public ChatClient(string baseUrl) { // Manually create and configure connection _connection = new HubConnectionBuilder() .WithUrl($"{baseUrl}/chathub") .WithAutomaticReconnect() .Build(); // String-based method registration - prone to typos! _connection.On("ReceiveMessage", HandleMessage); // Manual connection state management _connection.Closed += async (error) => { _isConnected = false; await ReconnectWithRetryAsync(); }; } // Event to expose the received messages public event Action MessageReceived; private Task HandleMessage(string user, string message) { MessageReceived?.Invoke(user, message); return Task.CompletedTask; } // Complex connection management public async Task ConnectAsync() { if (_isConnected) return; await _connection.StartAsync(); _isConnected = true; } // Custom reconnection logic private async Task ReconnectWithRetryAsync() { // Implement complex retry logic // ... } // String-based method invocation - no compile-time safety! public async Task SendMessageAsync(string message) { if (!_isConnected) await ConnectAsync(); await _connection.InvokeAsync("SendMessage", message); } public async ValueTask DisposeAsync() { await _connection.DisposeAsync(); } } // Usage requires manual instantiation and management var client = new ChatClient("https://example.com"); await client.ConnectAsync(); client.MessageReceived += (user, message) => Console.WriteLine($"{user}: {message}"); await client.SendMessageAsync("Hello world"); ``` -------------------------------- ### HubClient Attribute Naming Examples Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Illustrates how to use the `HubUri` and optional `HubName` properties of the `[HubClient]` attribute to control the generated client class name and hub path. ```csharp // Basic usage - generates "ExampleHubContractClient" [HubClient(HubUri = "example")] public interface IExampleHubContract { } // Custom name - generates "CustomChatClient" [HubClient(HubUri = "chat", HubName = "CustomChatClient")] public interface IChatHubContract { } ``` -------------------------------- ### Basic Chat Service Usage Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Demonstrates initializing, sending messages, and cleaning up a generated chat hub client. Subscribe to server-to-client events before starting the connection. ```csharp public class ChatService { private readonly ChatHubContractClient _hubClient; private readonly ILogger _logger; public ChatService(ChatHubContractClient hubClient, ILogger logger) { _hubClient = hubClient; _logger = logger; } public async Task Initialize() { // Subscribe to server-to-client events _hubClient.OnUserJoined += user => { _logger.LogInformation("User '{Username} joined", user); return Task.CompletedTask; }; // Start the connection await _hubClient.StartAsync(); } public async Task SendMessage(string message) { // Call the method to send data to the server await _hubClient.InvokeSendMessageAsync(_message); } public async Task Cleanup() { // Unsubscribe from events _hubClient.OnUserJoined = null; // Stop the connection await _hubClient.StopAsync(); } } ``` -------------------------------- ### Interface Naming Conventions Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Provides examples of good and bad naming conventions for SignalR hub interface names. Prefixes like 'I' and specific suffixes are recommended. ```csharp // ✅ Good public interface IChatHubContract { } public interface INotificationHubContract { } public interface IGameHubContract { } // ❌ Avoid public interface ChatHub { } // Missing 'I' prefix public interface IHub { } // Too generic public interface IChatClient { } // Redundant 'Client' ``` -------------------------------- ### HubClient Attribute Usage Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Demonstrates the required `[HubClient]` attribute for all SignalR interfaces, specifying the `HubUri`. This example also shows the structure for a bidirectional hub. ```csharp using SignalRGen.Abstractions; using SignalRGen.Abstractions.Attributes; namespace MyApp.Contracts; [HubClient(HubUri = "example-hub")] public interface IExampleHubContract : IBidirectionalHub { // This interface CANNOT have methods defined } public interface IExampleHubServerToClient { // Server-to-client methods } public interface IExampleHubClientToServer { // Client-to-server methods } ``` -------------------------------- ### Client-to-Server Hub Contract Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Defines a hub for one-way client-to-server communication. The contract interface inherits from `IClientToServerHub` and specifies the client interface. ```csharp [HubClient(HubUri = "telemetry")] public interface ITelemetryHubContract : IClientToServerHub { } public interface ITelemetryHubClientToServer { Task LogEvent(string eventName, Dictionary properties); Task ReportError(string error, string stackTrace); } ``` -------------------------------- ### Blazor Chat Component Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/README.md A Blazor component that uses SignalRGen to implement a basic chat functionality. It demonstrates joining a chat, receiving messages, and sending messages to other users. ```csharp @page "/Chat" @using SignalRGen.Example.Contracts @* We inject the generated client. *@ @* Notice that we use the actual type, not the interface we defined. *@ @inject ChatHubContractClient ChatHubClient;

Chat

This is a really crude example of a chat that uses SignalR generated by SignalRGen to communicate.

It is kept simple to better showcase the generated SignalR code. This is not how a real chat app would look like.

To get the example working it is best to duplicate this tab a couple of times and open them in different browser tabs. After that enter a username in each tab and join the chat room. You should be able to see the sent message on all tabs.

@if (!_joined) {
} else {

Chatting as: @_confirmedUsername

@foreach (var message in _messages) {
@message
}
} @code { private string _username = string.Empty; private string _confirmedUsername = string.Empty; private bool _joined = false; private string _message = string.Empty; private List _messages = new(); private async Task Join() { if (!string.IsNullOrWhiteSpace(_username)) { _confirmedUsername = _username; _username = string.Empty; _joined = true; // We subscribe to the `OnUserJoined` event to get notified whenever a new user joined. ChatHubClient.OnUserJoined += async user => { _messages.Add($"~~ User '{user}' joined ~~"); await InvokeAsync(StateHasChanged); }; // We subscribe to the `OnUserLeft` event to get notified whenever a user left. ChatHubClient.OnUserLeft += async user => { _messages.Add($"~~ User '{user}' left ~~"); await InvokeAsync(StateHasChanged); }; // We subscribe to the `OnMessageReceived` event to get notified whenever a new message // was sent by other users. ChatHubClient.OnMessageReceived += async (user, message) => { _messages.Add($"{user}: {message}"); await InvokeAsync(StateHasChanged); }; // We use the headers in this stripped-down example as a means to send the username along. // In a real chat app this would be dealt with differently, but it is a nice showcase // on how to pass along runtime-based headers. var headers = new Dictionary() { { "username", _confirmedUsername } }; // We start the Hub, which will internally build up the connection to the server. await ChatHubClient.StartAsync(headers: headers); } } private async Task SendMessage() { // We are invoking the `InvokeSendMessageAsync` method to send the message to the server, // which will propagate it to the other users. await ChatHubClient.InvokeSendMessageAsync(_message); _messages.Add($"{_confirmedUsername}: {_message}"); await InvokeAsync(StateHasChanged); _message = string.Empty; } } ``` -------------------------------- ### Bidirectional Hub Contract Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Defines a hub supporting both server-to-client and client-to-server communication. The main contract interface inherits from `IBidirectionalHub` and specifies the server and client interfaces. ```csharp [HubClient(HubUri = "chat")] public interface IChatHubContract : IBidirectionalHub { // Contract interface - no methods allowed here } public interface IChatHubServerToClient { Task MessageReceived(string user, string message); Task UserJoined(string user); } public interface IChatHubClientToServer { Task SendMessage(string message); Task> GetActiveUsers(); } ``` -------------------------------- ### Server-to-Client Hub Contract Example Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Defines a hub for one-way server-to-client communication. The contract interface inherits from `IServerToClientHub` and specifies the server interface. ```csharp [HubClient(HubUri = "notifications")] public interface INotificationHubContract : IServerToClientHub { } public interface INotificationHubServerToClient { Task NotificationReceived(string title, string message); Task SystemAlert(string level, string details); } ``` -------------------------------- ### Invoke Server Methods from Client Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/getting-started.md Call server-side methods from the client using the generated client's invoke methods. This example shows invoking a 'Ping' method. ```csharp app.MapGet("/ping", async ([FromServices] PingPongHubContractClient hub) => { await hub.InvokePingAsync("Hello from the client!"); }); ``` -------------------------------- ### Generated Hub Client Connection Management Methods Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Provides essential methods for managing the SignalR connection lifecycle, including starting, stopping, and disposing of the connection. ```csharp // Start the connection Task StartAsync( Dictionary? queryStrings = null, Dictionary? headers = null, CancellationToken cancellationToken = default); // Stop the connection Task StopAsync(CancellationToken cancellationToken = default); // Disposes the connection ValueTask DisposeAsync(); ``` -------------------------------- ### HubClientBase C# Code Source: https://github.com/michaelhochriegl/signalrgen/wiki/Generated Files Base class for generated SignalR Hub clients. Provides methods for starting, stopping, and managing the HubConnection, along with event handlers for connection status changes. It requires subclasses to implement `RegisterHubMethods`. ```csharp //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ using Microsoft.AspNetCore.SignalR.Client; #nullable enable namespace SignalRGen.Generator; /// /// Base class for generated HubClients. /// /// /// This base is partial to allow for extensions for all clients. /// Be aware that any extended behavior might result in the need to rewrite the DisposeAsync. /// public abstract partial class HubClientBase : IAsyncDisposable { protected bool _isRegistered; protected HubConnection? _hubConnection; protected readonly IHubConnectionBuilder HubConnectionBuilder; /// /// Gets invoked each time the Closed event from the underlying occurs. /// public Func? Closed; /// /// Gets invoked each time the Reconnecting event from the underlying occurs. /// public Func? Reconnecting; /// /// Gets invoked each time the reconnected event from the underlying occurs. /// public Func? Reconnected; protected HubClientBase(IHubConnectionBuilder hubConnectionBuilder) { HubConnectionBuilder = hubConnectionBuilder; } /// /// Asynchronously starts the underlying and registers the event callbacks for the SignalR client methods. /// /// A to cancel the start of the . public virtual Task StartAsync(CancellationToken cancellationToken = default) { _hubConnection ??= HubConnectionBuilder.Build(); if (!_isRegistered) { _hubConnection.Closed += Closed; _hubConnection.Reconnected += OnReconnected; _hubConnection.Reconnecting += Reconnecting; RegisterHubMethods(); _isRegistered = true; } return _hubConnection.State == HubConnectionState.Disconnected ? _hubConnection.StartAsync(cancellationToken) : Task.CompletedTask; } /// /// Asynchronously stops the underlying . /// /// A to cancel the stop of the . public virtual Task StopAsync(CancellationToken cancellationToken = default) { return _hubConnection is null ? Task.CompletedTask : _hubConnection.StopAsync(cancellationToken); } protected abstract void RegisterHubMethods(); private Task OnReconnected(string? connectionId) { return Reconnected is not null ? Reconnected.Invoke() : Task.CompletedTask; } /// /// Releases resources for the underlying . /// /// A that completes when resources have been released. public ValueTask DisposeAsync() { if (_hubConnection is null) { return ValueTask.CompletedTask; } _hubConnection.Closed -= Closed; _hubConnection.Reconnected -= OnReconnected; _hubConnection.Reconnecting -= Reconnecting; return _hubConnection.DisposeAsync(); } } ``` -------------------------------- ### Create Solution Structure Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/detailed-tutorial.md Sets up a new .NET solution with separate projects for the server, client, and shared interface, and adds them to the solution file. ```shell mkdir MySignalRGenExample cd MySignalRGenExample dotnet new sln -n MySignalRGenExample dotnet new web -n MyServer dotnet sln add MyServer/MyServer.csproj dotnet new web -n MyClient dotnet sln add MyClient/MyClient.csproj dotnet new classlib -n MySharedInterface dotnet sln add MySharedInterface/MySharedInterface.csproj ``` -------------------------------- ### Multiple Hub Interfaces and Registration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Demonstrates how to define multiple distinct hub interfaces for different functionalities and how to register them with SignalRGen in the service collection. ```csharp [HubClient(HubUri = "chat")] public interface IChatHubContract : IBidirectionalHub { } [HubClient(HubUri = "notifications")] public interface INotificationHubContract : IServerToClientHub { } [HubClient(HubUri = "telemetry")] public interface ITelemetryHubContract : IClientToServerHub { } // Registration: services.AddSignalRHubs(options => { options.HubBaseUri = new Uri("https://api.example.com"); }) .WithChatHubContractClient() .WithNotificationHubContractClient() .WithTelemetryHubContractClient(); ``` -------------------------------- ### Add Project References Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/detailed-tutorial.md Establishes project references to make the shared interface accessible to both the server and client projects. ```shell # From the solution root dotnet add MyServer/MyServer.csproj reference MySharedInterface/MySharedInterface.csproj dotnet add MyClient/MyClient.csproj reference MySharedInterface/MySharedInterface.csproj ``` -------------------------------- ### Multi-level Inheritance for Server Events Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/advanced/hub-contract-inheritance.md Demonstrates multi-level inheritance for server-to-client events, combining base presence events, extended presence events, and news feed events into a single interface. ```csharp public interface IBasePresenceEvents { Task UserJoined(string user); } public interface IExtendedPresenceEvents : IBasePresenceEvents { Task UserLeft(string user); } public interface INewsFeedEvents { Task NewsPosted(string title, string content); } // SignalRGen will include all three events below public interface IPortalServerToClient : IExtendedPresenceEvents, INewsFeedEvents { Task SystemMessage(string message); } ``` -------------------------------- ### Map SignalR Hub in Program.cs Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/server-side-usage/server-hub-implementation.md Configure the server to listen on the correct URI and route to the Hub by mapping it in `Program.cs` using the `HubUri` from the client contract. ```csharp // To wire up our ChatHub, we have to map it here. // We use the `HubUri` from the generated client. // This ensures that both client and server will use the same route. app.MapHub($"{ChatHubContractClient.HubUri}"); ``` -------------------------------- ### Method Naming Conventions Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Illustrates recommended naming conventions for SignalR methods, emphasizing PascalCase and avoiding prefixes like 'On' or 'Invoke'. ```csharp // ✅ Good - Server-to-Client Task MessageReceived(string message); Task UserJoined(string username); Task GameStarted(int gameId); // ✅ Good - Client-to-Server Task SendMessage(string message); Task JoinGame(int gameId); Task GetGameState(int gameId); // ❌ Avoid Task OnMessageReceived(string message); // Don't use 'On' prefix Task InvokeSendMessage(string message); // Don't use 'Invoke' prefix Task message_received(string message); // Use PascalCase ``` -------------------------------- ### Server-to-Client Method Definitions Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Shows how to define methods within the server-to-client interface (`TServer`). These methods must return `Task` and can accept various parameters. ```csharp public interface IExampleHubServerToClient { // Simple notification Task UserJoined(string username); // Complex data Task MessageReceived(ChatMessage message); // Multiple parameters Task GameStateUpdated(int playerId, Vector3 position, float health); } ``` -------------------------------- ### Register Global Configuration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/configuration/config-global.md Register global configuration through dependency injection by setting the HubBaseUri. This URI is used for all Hub connections. ```csharp builder.Services .AddSignalRHubs(c => c.HubBaseUri = new Uri("http://localhost:5160")); ``` -------------------------------- ### Registering Generated Hub Client with DI Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Demonstrates how to register the generated SignalR hub client in the dependency injection container using `AddSignalRHubs`. ```csharp // In Program.cs or Startup.cs services.AddSignalRHubs(options => { options.HubBaseUri = new Uri("https://your-api.example.com/hubs"); }) .WithChatHubContractClient(); // Method name follows the pattern With[ClientName] ``` -------------------------------- ### Reset Recorded Calls and Events Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Use `ClearRecorded()` on a fake client instance to reset all recorded calls and events between tests. This is crucial when reusing fake instances or DI scopes to ensure that tests do not interfere with each other and that event observation starts fresh. ```csharp fakeClient.ClearRecorded(); ``` -------------------------------- ### Replace Production Client with Fake in DI Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md In your test project's DI configuration, register the fake implementation for the production hub client type. This allows components to inject the fake while still referencing the production type. ```csharp Services.AddSingleton(); ``` -------------------------------- ### Chat Project Configuration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/advanced/multi-project-support.md Configure the chat project (`MyApp.Chat.csproj`) with a custom `SignalRModuleName` set to 'Chat'. Include the necessary SignalRGen package reference. ```csharp net9.0 Chat all runtime; build; native; contentfiles; analyzers; buildtransitive ``` -------------------------------- ### Register SignalRGen Services Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/what-is-signalrgen.md Register SignalRHubs in your DI container and configure the hub base URI. Use the `WithChatHub()` extension method for the chat hub. ```csharp // 2. Register in your DI container services.AddSignalRHubs(c => c.HubBaseUri = new Uri("https://example.com")) .WithChatHub(); ``` -------------------------------- ### Supported Collection Types Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Illustrates the use of various collection types like Lists, Arrays, Dictionaries, and HashSets as method parameters. ```csharp Task ReceiveList(List items); Task ReceiveArray(string[] items); Task ReceiveDictionary(Dictionary scores); Task ReceiveSet(HashSet uniqueItems); ``` -------------------------------- ### Simulate Server-to-Client Callbacks Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Use the SimulateOn...Async helper methods provided by the fake client to trigger server-to-client callbacks. This records the event payload and invokes any subscribed delegates. ```csharp await fakeClient.SimulateOnUserJoinedAsync("NewUser"); ``` -------------------------------- ### Declare Multiple Fake Hub Clients Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md You can specify multiple hub clients for fake generation by adding multiple assembly attributes. ```csharp using SignalRGen.Testing.Abstractions.Attributes; [assembly: GenerateFakeForHubClient(typeof(ExampleHubClient))] [assembly: GenerateFakeForHubClient(typeof(ChatHubContractClient))] ``` -------------------------------- ### Versioning Contracts with Inheritance Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/advanced/hub-contract-inheritance.md Implement contract versioning by inheriting from previous versions and adding new methods. This allows for incremental updates to client-to-server commands. ```csharp public interface IChatCommandsV1 { Task SendMessage(string message); } public interface IChatCommandsV2 : IChatCommandsV1 { Task EditMessage(string messageId, string newContent); Task DeleteMessage(string messageId); } public interface IChatHubClientToServer : IChatCommandsV2 { Task> GetActiveUsers(); } ``` -------------------------------- ### Supported Nullable Types Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Shows how to use nullable types for parameters, allowing for optional values or objects. ```csharp Task ReceiveOptionalValue(int? maybeValue); Task ReceiveOptionalUser(User? user); ``` -------------------------------- ### Notifications Project Configuration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/advanced/multi-project-support.md Configure the notifications project (`MyApp.Notifications.csproj`) with a custom `SignalRModuleName` set to 'Notifications'. Include the necessary SignalRGen package reference. ```csharp net9.0 Notifications all runtime; build; native; contentfiles; analyzers; buildtransitive ``` -------------------------------- ### Configure Client SignalR Hub Registration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/detailed-tutorial.md Configures the client-side to use SignalRGen, setting the hub base URI and registering the generated client contract. ```csharp // 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")) .WithPingPongHubContractClient(); // Register our background worker builder.Services.AddHostedService(); var app = builder.Build(); // Add an endpoint to trigger a ping app.MapGet("/ping", async ([FromServices] PingPongHubContractClient hub) => { await hub.InvokePingAsync("Hello from the client!"); return "Ping sent!"; }); app.Run(); ``` -------------------------------- ### Supported Parameter and Return Types Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Details the primitive, nullable, collection, and complex types supported for parameters and return values in hub methods. ```APIDOC ## Parameter and Return Types ### Supported Types **Primitive Types:** ```csharp Task ReceiveCount(int count); Task ReceiveMessage(string message); Task ReceiveFlag(bool isActive); Task ReceiveAmount(decimal amount); Task ReceiveTimestamp(DateTime timestamp); ``` **Nullable Types:** ```csharp Task ReceiveOptionalValue(int? maybeValue); Task ReceiveOptionalUser(User? user); ``` **Collections:** ```csharp Task ReceiveList(List items); Task ReceiveArray(string[] items); Task ReceiveDictionary(Dictionary scores); Task ReceiveSet(HashSet uniqueItems); ``` **Complex Types:** ```csharp // Custom classes public class ChatMessage { public string Content { get; set; } public string Sender { get; set; } public DateTime Timestamp { get; set; } } // Records (preferred for immutable data) public record UserProfile(string Name, string Email, DateTime LastSeen); // Usage Task MessageReceived(ChatMessage message); Task UserUpdated(UserProfile profile); ``` ### Requirements for Complex Types Complex types must be JSON serializable: 1. **Public parameterless constructor** (for classes) 2. **Record types** (automatically serializable) 3. **All properties must be serializable** 4. **Use `JsonConstructor` attribute** if needed :::warning ⚠️ Unsupported Types - Methods with `ref` or `out` parameters - Generic method definitions - Types with circular references - Non-serializable types (like `Stream`, `Thread`) ::: ``` -------------------------------- ### Server-Side Hub Implementation Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/getting-started.md Implement the SignalR Hub on the server by inheriting from the appropriate Hub class and the client-to-server interface. ```csharp public class PongHub : Hub, IPingPongClientToServer { private readonly ILogger _logger; public PongHub(ILogger logger) { _logger = logger; } public Task Ping(string message) { _logger.LogInformation("Received Ping: {Message}", message); return Clients.All.Pong("Hey, here is the server talking!"); } } ``` -------------------------------- ### Register Hub Client in Client Application Source: https://github.com/michaelhochriegl/signalrgen/blob/main/README.md Configure the SignalRGen client services in your client application. Set the base URI for SignalR hubs and include the generated client contract. ```csharp builder.Services .AddSignalRHubs(c => c.HubBaseUri = new Uri("http://localhost:5155")) .WithChatHubContractClient(); ``` -------------------------------- ### Generated Server-to-Client Event Handler Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Illustrates the event-based pattern generated for server-to-client methods, allowing subscription to incoming server messages. ```csharp // For the interface method: // Task UserJoined(string user); // SignalRGen generates: public delegate global::System.Threading.Tasks.Task UserJoinedDelegate(string user); public UserJoinedDelegate? OnUserJoined = default; ``` -------------------------------- ### Advanced SignalRGen Configuration Source: https://github.com/michaelhochriegl/signalrgen/blob/main/README.md Configures SignalRGen services with custom settings, including hub base URI, service lifetime, HTTP connection options, and hub connection builder options. ```csharp builder.Services .AddExampleHubs(c => c.HubBaseUri = new Uri("http://localhost:5155")) .WithChatHubContractClient(config => { config.HubClientLifetime = ServiceLifetime.Scoped; config.HttpConnectionOptionsConfiguration = options => { options.Headers.Add("custom-header", "foo"); }; config.HubConnectionBuilderConfiguration = connectionBuilder => { connectionBuilder.WithKeepAliveInterval(TimeSpan.FromMinutes(1)); }; }); ``` -------------------------------- ### Working with Complex Types Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Illustrates defining and using complex types for server-to-client and client-to-server communication with SignalR. The generated client handles serialization automatically. ```csharp [HubClient(HubUri = "complex-example")] public interface IComplexTypeHubContract : IBidirectionalHub { } public interface IComplexTypeHubServerToClient { // Server-to-client with complex type Task ReceiveComplexData(MyCustomType data); } public interface IComplexTypeHubClientToServer { // Client-to-server with complex type Task SendComplexData(MyCustomType data); } public record MyCustomType(string Hey, int Dude); ``` -------------------------------- ### Configure SignalR Hubs in Client Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/getting-started.md Configure the SignalR Hubs client in the client application's Program.cs, specifying the Hub base URI and including the generated client contract. ```csharp builder.Services.AddSignalRHubs(c => c.HubBaseUri = new Uri("http://localhost:5160")) .WithPingPongHubContractClient(); ``` -------------------------------- ### Register Hub on Server Source: https://github.com/michaelhochriegl/signalrgen/blob/main/README.md Map your SignalR hub in the server's application configuration. Ensure the hub URI matches the one defined in your hub interface. ```csharp var app = builder.Build(); app.MapHub($"{ChatHubContractClient.HubUri}"); ``` -------------------------------- ### Implement SignalR Hub Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/detailed-tutorial.md Implements the SignalR hub on the server-side, handling incoming client messages and sending responses. ```csharp // MyServer/PongHub.cs using Microsoft.AspNetCore.SignalR; using MySharedInterface; namespace MyServer; public class PongHub : Hub, IPingPongClientToServer { private readonly ILogger _logger; public PongHub(ILogger logger) { _logger = logger; } public Task Ping(string message) { _logger.LogInformation("Received Ping: {Message}", message); return Clients.All.Pong("Hey, here is the server talking!"); } } ``` -------------------------------- ### Compose Server-to-Client Events Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/advanced/hub-contract-inheritance.md Combine presence and moderation events into a single server-to-client hub contract using interface inheritance. ```csharp public interface ICommonPresenceEvents { Task UserJoined(string user); Task UserLeft(string user); } public interface IModerationEvents { Task UserMuted(string user); Task UserUnmuted(string user); } public interface IChatHubServerToClient : ICommonPresenceEvents, IModerationEvents { Task MessageReceived(ChatMessage message); } ``` -------------------------------- ### Non-Flaky Test with WaitForOn...Async Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Employ WaitForOn...Async methods to ensure that a server-to-client event has been observed by the fake client. This is crucial for avoiding timing issues in tests, especially when UI updates follow the event. ```csharp await fakeClient.SimulateOnUserJoinedAsync("NewUser"); // Guarantees the fake observed the event publication (helps avoid timing flakes) await fakeClient.WaitForOnUserJoinedAsync(); // Now assert on the UI/state await cut.WaitForStateAsync(() => cut.FindAll(".msg").Any(m => m.TextContent.Contains("User 'NewUser' joined"))); ``` -------------------------------- ### Enable Fake Generation with Assembly Attribute Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Opt-in to fake generation by applying the GenerateFakeForHubClient attribute to the hub client types you want to mock in your test assembly. ```csharp using SignalRGen.Testing.Abstractions.Attributes; [assembly: GenerateFakeForHubClient(typeof(ChatHubContractClient))] ``` -------------------------------- ### Configure Custom Handler for Client-to-Server Calls Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Optionally, configure a custom handler for client-to-server method invocations on the fake client. This allows for custom behavior like validating arguments or simulating server responses. ```csharp fakeClient.SendMessageHandler = async (message, ct) => { // your custom behavior here await Task.CompletedTask; }; ``` -------------------------------- ### Supported Primitive Types Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Demonstrates the use of primitive data types for method parameters in SignalR contracts. ```csharp Task ReceiveCount(int count); Task ReceiveMessage(string message); Task ReceiveFlag(bool isActive); Task ReceiveAmount(decimal amount); Task ReceiveTimestamp(DateTime timestamp); ``` -------------------------------- ### Register Hub in Program.cs Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/getting-started.md Register your custom Hub with the SignalR endpoint in the server's Program.cs file. ```csharp app.MapHub($"/{PingPongHubContractClient.HubUri}"); ``` -------------------------------- ### Client-to-Server Methods Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Methods that clients invoke on the server. These are defined in the `IClientToServer` interface and must return `Task` or `Task`. ```APIDOC ## Client-to-Server Methods Methods the client calls on the server. Defined in the `TClient` interface. ```csharp public interface IExampleHubClientToServer { // Fire-and-forget Task SendMessage(string message); // Request-response Task> GetActiveUsers(); Task GetUserProfile(string userId); // Complex operations Task SubmitMove(int gameId, GameMove move); } ``` **Requirements:** - Must return `Task` (no result) or `Task` (with result) - Can have any number and type of parameters - Exposed as methods in the generated client ``` -------------------------------- ### Naming Conventions Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Provides guidelines for naming SignalR hub interfaces and methods to ensure consistency and clarity. ```APIDOC ## Naming Conventions ### Interface Naming ```csharp // ✅ Good public interface IChatHubContract { } public interface INotificationHubContract { } public interface IGameHubContract { } // ❌ Avoid public interface ChatHub { } // Missing 'I' prefix public interface IHub { } // Too generic public interface IChatClient { } // Redundant 'Client' ``` ### Method Naming ```csharp // ✅ Good - Server-to-Client Task MessageReceived(string message); Task UserJoined(string username); Task GameStarted(int gameId); // ✅ Good - Client-to-Server Task SendMessage(string message); Task JoinGame(int gameId); Task GetGameState(int gameId); // ❌ Avoid Task OnMessageReceived(string message); // Don't use 'On' prefix Task InvokeSendMessage(string message); // Don't use 'Invoke' prefix Task message_received(string message); // Use PascalCase ``` ``` -------------------------------- ### Client-to-Server Methods Interface Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/hub-contract-definition.md Defines methods that the client calls on the server. These methods must return Task or Task and can accept any number and type of parameters. ```csharp public interface IExampleHubClientToServer { // Fire-and-forget Task SendMessage(string message); // Request-response Task> GetActiveUsers(); Task GetUserProfile(string userId); // Complex operations Task SubmitMove(int gameId, GameMove move); } ``` -------------------------------- ### Assert Client-to-Server Invocations Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/testing/generated-fakes.md Verify that client-to-server hub method calls were made by checking the recorded arguments in the generated *Calls collections, such as SendMessageCalls. ```csharp cut.Find("#message-input").Input("My secret message"); await cut.Find("#send-button").ClickAsync(); Assert.Contains("My secret message", fakeClient.SendMessageCalls); ``` -------------------------------- ### Registering Custom Hubs in Main Application Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/advanced/multi-project-support.md Register custom chat and notification hubs in the main application (`MyApp.Web`) using their respective generated extension methods (`AddChatHubs` and `AddNotificationsHubs`). ```csharp // Program.cs in MyApp.Web var builder = WebApplication.CreateBuilder(args); // Register chat hub clients builder.Services .AddChatHubs(c => c.HubBaseUri = new Uri("http://localhost:5155")) .WithChatHubClient(); // Register notification hub clients builder.Services .AddNotificationsHubs(c => c.HubBaseUri = new Uri("http://localhost:5155")) .WithNotificationHubClient(); var app = builder.Build(); ``` -------------------------------- ### Compose Client-to-Server Commands Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/hub-contracts/advanced/hub-contract-inheritance.md Aggregate message sending and moderation commands, along with a method to retrieve active users, into a client-to-server hub contract. ```csharp public interface IMessageCommands { Task SendMessage(string message); } public interface IModerationCommands { Task MuteUser(string user); Task UnmuteUser(string user); } public interface IChatHubClientToServer : IMessageCommands, IModerationCommands { Task> GetActiveUsers(); } ``` -------------------------------- ### SignalROptions Class Definition Source: https://github.com/michaelhochriegl/signalrgen/wiki/Generated Files Defines basic configuration settings applicable to all SignalR Hubs, primarily the base URI for hubs. ```csharp //------------------------------------------------------------------------------ // // This code was generated by a tool. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ #nullable enable namespace SignalRGen.Generator.Client.Configuration; public class SignalROptions { public Uri HubBaseUri { get; set; } = default !; } ``` -------------------------------- ### Use Generated SignalR Client Source: https://github.com/michaelhochriegl/signalrgen/blob/main/README.md Inject and use the generated SignalR client in your services. The client handles connection management automatically and provides strongly-typed methods for invoking server functions and handling events. ```csharp public class ChatService { private readonly ChatHubContractClient _hubClient; public ExampleComponent(ChatHubContractClient hubClient) { _hubClient = hubClient; _hubClient.MessageReceived += HandleMessageReceived; } public async Task Initialize() { // Connection handling is done automatically await _hubClient.StartAsync(); // Strongly-typed method invocation await _hubClient.InvokeSendMessageAsync("Hello from SignalRGen!"); } private async Task HandleMessageReceived(string user, string message) { Console.WriteLine($"New message from {user}: {message}"); return Task.CompletedTask; } } ``` -------------------------------- ### Use Generated SignalR Client Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/guide/what-is-signalrgen.md Inject the generated `ChatHub` client into your services. Subscribe to events and invoke methods using the strongly-typed API. ```csharp // 3. Use the generated client with full type safety public class ChatService { private readonly ChatHub _hub; // Just inject and use! public ChatService(ChatHub hub) { _hub = hub; // Strongly-typed event subscription _hub.OnMessageReceived += (user, message) => { Console.WriteLine($"{user}: {message}"); return Task.CompletedTask; }; } public Task ConnectAsync() => _hub.StartAsync(); // Connection management handled automatically public async Task SendMessageAsync(string message) { // Strongly-typed method invocation await _hub.InvokeSendMessageAsync(message); } } ``` -------------------------------- ### Handling Connection Events Source: https://github.com/michaelhochriegl/signalrgen/blob/main/docs/client-side-usage/generated-hub-clients.md Shows how to subscribe to SignalR connection state change events like reconnecting, reconnected, and closed. These events are crucial for managing connection issues. ```csharp // Subscribe to connection events _hubClient.Reconnecting += error => { logger.LogWarning("Reconnecting due to: {Error}", error?.Message); return Task.CompletedTask; }; _hubClient.Reconnected += connectionId => { logger.LogInformation("Reconnected with ID: {ConnectionId}", connectionId); return Task.CompletedTask; }; _hubClient.Closed += error => { logger.LogWarning("Connection closed due to: {Error}", error?.Message); return Task.CompletedTask; }; ```