0Pricing
C# Academy · Lesson

Strongly Typed Hubs

Define typed hub interfaces to get compile-time safety and IntelliSense for client method calls.

Strongly Typed Hubs is a free C# Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Problem with String Method Names

Standard SignalR calls use SendAsync("MethodName", ...) with magic strings. A typo in the method name causes a silent failure — the client simply never receives the message. Strongly typed hubs solve this.

Defining the Client Interface

Create an interface that declares every method the server can invoke on the client. The method names and parameter types are your contract.

// INotificationClient.cs
public interface INotificationClient
{
    Task ReceiveMessage(string user, string message);
    Task OrderShipped(int orderId, string trackingNumber);
    Task UserJoined(string username);
    Task UserLeft(string username);
    Task SystemAlert(string message);
}

Creating a Strongly Typed Hub

Inherit from Hub<T> where T is your client interface. The Clients property is now typed — IntelliSense shows available methods, and typos are compile errors.

public class ChatHub : Hub<INotificationClient>
{
    public async Task SendMessage(string message)
    {
        var user = Context.User!.Identity!.Name ?? "Anonymous";

        // Compile-time safety — no magic strings!
        await Clients.All.ReceiveMessage(user, message);
        await Clients.Others.UserJoined(user); // IntelliSense works
    }
}

Targeting Specific Clients with Typed Hub

All standard targeting methods work on the typed hub: Clients.User, Clients.Group, Clients.Caller, Clients.Client — all returning the typed interface.

public async Task NotifyOrderShipped(int orderId, string customerId)
{
    await Clients.User(customerId)
                 .OrderShipped(orderId, "TRK-12345");

    await Clients.Group("admins")
                 .SystemAlert($"Order {orderId} shipped to {customerId}");

    await Clients.Caller.ReceiveMessage("System", "Notification sent");
}

Typed IHubContext

Inject IHubContext<THub, TClient> into services to get the same compile-time safety when pushing messages from outside the hub.

public class ShipmentService
{
    private readonly IHubContext<ChatHub, INotificationClient> _hub;

    public ShipmentService(
        IHubContext<ChatHub, INotificationClient> hub) => _hub = hub;

    public async Task ProcessShipmentAsync(int orderId, string customerId)
    {
        // Fully typed — no magic strings
        await _hub.Clients.User(customerId)
                  .OrderShipped(orderId, "TRK-99999");
    }
}

Client Interface Rules

Methods in the client interface must return Task (not Task<T>). SignalR fires-and-forgets client invocations — there is no return value from the client to the server.

// CORRECT
public interface IMyClient
{
    Task ReceiveMessage(string msg);         // OK
    Task UpdateProgress(int percent);        // OK
    Task<string> GetUserInput();            // NOT SUPPORTED
}
// Return types other than Task are not supported in hub client interfaces
// For request-response patterns, use client-callable hub methods instead

Combining Typed Hubs with Groups

Typed groups work identically to untyped — the API is the same, just with compile-time method names.

public class RoomHub : Hub<IRoomClient>
{
    public async Task JoinRoom(string room)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, room);
        await Clients.Group(room).UserJoined(Context.User!.Identity!.Name!);
    }

    public async Task BroadcastToRoom(string room, string message)
    {
        var user = Context.User!.Identity!.Name!;
        await Clients.Group(room).ReceiveMessage(user, message);
    }
}

Testing Strongly Typed Hubs

Since client method calls go through an interface, you can mock the client in unit tests to verify that the correct methods were called with the right arguments.

// NSubstitute test
var mockClients = Substitute.For<IHubCallerClients<INotificationClient>>();
var mockAllClients = Substitute.For<INotificationClient>();
mockClients.All.Returns(mockAllClients);

var hub = new ChatHub { Clients = mockClients };
await hub.SendMessage("Hello!");

await mockAllClients.Received(1).ReceiveMessage("System", "Hello!");

Multiple Hubs per Application

An application can have multiple hub endpoints for different real-time features, each with its own typed client interface.

// Three separate hubs
app.MapHub<ChatHub>("/hubs/chat");
app.MapHub<NotificationHub>("/hubs/notifications");
app.MapHub<DashboardHub>("/hubs/dashboard");

// Each with its own client interface:
// Hub<IChatClient>, Hub<INotificationClient>, Hub<IDashboardClient>

Sending Structured Data

Client interface methods can accept complex DTOs. SignalR serializes them to JSON automatically — ensure your types are serializable.

public interface IOrderClient
{
    Task OrderUpdated(OrderStatusDto status);
}

public record OrderStatusDto(
    int OrderId,
    string Status,
    decimal Total,
    DateTime UpdatedAt);

// Usage in hub:
await Clients.User(customerId).OrderUpdated(
    new OrderStatusDto(order.Id, order.Status.ToString(),
                       order.Total, DateTime.UtcNow));

Quick Check

What is the return type restriction for methods in a SignalR hub client interface?

Recap: Strongly Typed Hubs

Key takeaways:

  • Define an interface (IMyClient) listing all methods the server can call on clients
  • Inherit from Hub<IMyClient> — Clients property becomes fully typed
  • Inject IHubContext<THub, TClient> for typed pushes from outside the hub
  • Client interface methods must return Task, not Task<T>
  • Interface-based clients are mockable — strongly typed hubs are easier to test

Frequently asked questions

Is the “Strongly Typed Hubs” lesson free?

Yes — the full text of “Strongly Typed Hubs” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Strongly Typed Hubs”?

Define typed hub interfaces to get compile-time safety and IntelliSense for client method calls. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Strongly Typed Hubs” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. SignalR Hubs & Connections
  2. Groups, Users & Connection Management
  3. Strongly Typed Hubs
  4. Scaling with Redis Backplane
← Back to C# Academy