Back to blog
Backend Systemsbeginner

SignalR Hubs — Your First Real-Time App

Build real-time features with ASP.NET Core SignalR: hubs, client connections, broadcasting messages, groups, and a working chat example.

Asma HafeezApril 17, 20264 min read
dotnetsignalrreal-timewebsocketsaspnet-core
Share:𝕏

SignalR Hubs

SignalR is ASP.NET Core's real-time communication library. It uses WebSockets when available and falls back to long polling — you write one API, SignalR handles the transport.


How SignalR Works

Client (JavaScript)          Server (ASP.NET Core)
      |                              |
      |-- connection.start() ------> |
      |<-- connected -------------- |
      |                              |
      |-- Invoke("SendMessage") ---> Hub method runs
      |<-- ReceiveMessage ---------- Server pushes to clients
      |                              |

The Hub is the server-side class. Clients connect to it, invoke methods on it, and receive messages from it.


Server Setup

Bash
# Already included in ASP.NET Core  no extra package needed
dotnet new web -n ChatApp
C#
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();

var app = builder.Build();
app.UseStaticFiles();
app.MapHub<ChatHub>("/chathub");
app.Run();

The Hub

C#
// Hubs/ChatHub.cs
using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    // Client calls this method to send a message
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to ALL connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message, DateTime.UtcNow);
    }

    // Called when a client connects
    public override async Task OnConnectedAsync()
    {
        var connectionId = Context.ConnectionId;
        Console.WriteLine($"Client connected: {connectionId}");
        await base.OnConnectedAsync();
    }

    // Called when a client disconnects
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        Console.WriteLine($"Client disconnected: {Context.ConnectionId}");
        await base.OnDisconnectedAsync(exception);
    }
}

Targeting Specific Clients

C#
public class ChatHub : Hub
{
    // Send to the caller only
    public async Task Echo(string message)
    {
        await Clients.Caller.SendAsync("ReceiveMessage", message);
    }

    // Send to everyone except the caller
    public async Task Broadcast(string message)
    {
        await Clients.Others.SendAsync("ReceiveMessage", message);
    }

    // Send to a specific connection
    public async Task Whisper(string toConnectionId, string message)
    {
        await Clients.Client(toConnectionId).SendAsync("ReceiveMessage", message);
    }
}

Groups — Rooms and Channels

C#
public class ChatHub : Hub
{
    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
        await Clients.Group(roomName).SendAsync("UserJoined", Context.ConnectionId, roomName);
    }

    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
    }

    public async Task SendToRoom(string roomName, string message)
    {
        await Clients.Group(roomName).SendAsync("ReceiveMessage", Context.ConnectionId, message);
    }
}

JavaScript Client

HTML
<!-- wwwroot/index.html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.0/signalr.min.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

// Listen for messages from server
connection.on("ReceiveMessage", (user, message, timestamp) => {
    const li = document.createElement("li");
    li.textContent = `[${new Date(timestamp).toLocaleTimeString()}] ${user}: ${message}`;
    document.getElementById("messagesList").appendChild(li);
});

// Start connection
connection.start()
    .then(() => console.log("Connected!"))
    .catch(err => console.error(err));

// Send message
document.getElementById("sendButton").addEventListener("click", () => {
    const user = document.getElementById("userInput").value;
    const msg  = document.getElementById("messageInput").value;
    connection.invoke("SendMessage", user, msg)
        .catch(err => console.error(err));
});

// Join a room
connection.invoke("JoinRoom", "general");
</script>

Pushing from Server (Outside Hub)

C#
// Inject IHubContext to push from controllers or background services
public class OrderController : ControllerBase
{
    private readonly IHubContext<OrderHub> _hub;

    public OrderController(IHubContext<OrderHub> hub)
    {
        _hub = hub;
    }

    [HttpPost("{id}/ship")]
    public async Task<IActionResult> ShipOrder(int id)
    {
        // ... process order ...

        // Push update to ALL clients
        await _hub.Clients.All.SendAsync("OrderShipped", new { orderId = id, status = "Shipped" });
        return Ok();
    }
}

Authentication with SignalR

C#
// Hub with auth
[Authorize]
public class SecureHub : Hub
{
    public async Task SendMessage(string message)
    {
        var userId = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        await Clients.All.SendAsync("ReceiveMessage", userId, message);
    }
}

// JavaScript — pass JWT
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/securehub", {
        accessTokenFactory: () => localStorage.getItem("token")
    })
    .build();

Key Takeaways

  1. SignalR abstracts WebSockets — it falls back to Server-Sent Events then long polling automatically
  2. Hub methods are called by clients; Clients.* sends from server to clients
  3. Groups implement rooms and channels — add/remove connections per message
  4. Use IHubContext<T> to push from outside the hub (controllers, background services)
  5. Add withAutomaticReconnect() on the client — networks drop, connections should recover

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.