C# – SignalR

By | 10/12/2025

In this post, we will see what SignalR is and how we can use it in our .net projects.
SignalR is a library for .NET developers that simplifies adding real-time web functionality to our applications.
In short, it enables server-push.
Instead of the client always having to initiate a request, SignalR opens a persistent, two-way connection. This allows our server-side code to call JavaScript methods on the client’s browser (and other clients) instantly.
Under the hood, SignalR uses WebSockets when available, but gracefully falls back to other techniques like Server-Sent Events or Long Polling when WebSockets aren’t supported.
The beauty of SignalR is that we don’t have to worry about these details becuase it handles all the complexity for us.

When to use SignalR
We should choose SignalR when we need low-latency, bidirectional communication between server and many clients that stay connected for a while. It is ideal if we want to broadcast the same update to multiple users, or allow clients to call server methods without an HTTP request/response cycle each time. It also fits well when we want to stream incremental results from a background process to the browser as the work progresses.
If we use case is simply “tell another backend that an event happened” and we don’t control the recipient’s runtime or connectivity, we probably want a webhook instead of a persistent connection. Webhooks are “fire and forget” HTTP callbacks; SignalR is a live pipe.


Now, let’s build a simple notification system where the server can broadcast messages to all connected clients. We’ll create both the server and a client.

We start to create a new ASP.NET Core Web API project and install SignalR package.
Then, we create the HUB, that is the core abstraction in SignalR. It’s a high-level pipeline that allows clients and servers to call methods on each other.

[NOTIFICATIONHUB.CS]

using Microsoft.AspNetCore.SignalR;

namespace SignalR;

// The Hub is the central class in SignalR responsible for handling real-time communication.
// Each connected client maintains a persistent connection with the Hub.
public class NotificationHub : Hub
{
    // This method is called by clients to send notification
    // The parameters "user" and "message" are sent from the client and received here.
    // In this example, we simply broadcast the message to all connected clients.
    public async Task SendNotification(string user, string message)
    {
        // Broadcast to all connected clients
        await Clients.All.SendAsync("ReceiveNotification", user, message);
    }
    
    // Optional: you can override connection lifecycle methods.
    // This one runs every time a client connects to the Hub.
    public override async Task OnConnectedAsync()
    {
        // Broadcast a system message announcing that a new connection joined.
        // Context.ConnectionId uniquely identifies each client.
        await Clients.All.SendAsync("ReceiveNotification", 
            "System", $"{Context.ConnectionId} joined");
        
        // Always call the base implementation so SignalR can perform its internal setup.
        await base.OnConnectedAsync();
    }
}

Then, we define the NotificationRequest record:
[NOTIFICATIONREQUEST.CS]

namespace SignalR;

public record NotificationRequest(string User, string Message);

Finally, we modify the Program file:
[PROGRAM.CS]

using Microsoft.AspNetCore.SignalR;
using SignalR;

var builder = WebApplication.CreateBuilder(args);

// Add OpenAPI (for Swagger UI if you enabled it)
builder.Services.AddOpenApi();

// Register SignalR services
builder.Services.AddSignalR();

// Configure CORS to allow client connections
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy =>
    {
        policy.WithOrigins("http://localhost:5117", "null") // Allow browser files or local client
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials(); // Required for SignalR
    });
});

var app = builder.Build();

app.UseCors("AllowAll"); // Apply CORS policy

// Map the SignalR Hub endpoint
app.MapHub<NotificationHub>("/notificationHub");

// Minimal API to send notifications to all clients
app.MapPost("/api/notify", async (IHubContext<NotificationHub> hubContext, 
    NotificationRequest request) =>
{
    // Broadcast message to all connected clients
    await hubContext.Clients.All.SendAsync("ReceiveNotification", 
        request.User, request.Message);
    return Results.Ok("Notification sent");
});

app.Run();

Now, we run the Web API and after that, we will create the ‘client’ that will connect to the hub, listens for notifications, and appends them to the page.


[INDEX.HTML]

<!DOCTYPE html>
<html>
<head>
    <title>SignalR Client</title>
    <!-- Load the SignalR JavaScript client library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
</head>
<body>
    <h1>SignalR Notification Client</h1>
    
    <!-- Display area for incoming notifications -->
    <div id="notifications" style="border: 1px solid #ccc; padding: 10px; margin: 10px 0; height: 300px; overflow-y: auto;">
        <p><em>Waiting for notifications...</em></p>
    </div>
    
    <!-- Inputs to send notifications -->
    <input type="text" id="userInput" placeholder="Your name" value="User1" />
    <input type="text" id="messageInput" placeholder="Message" />
    <button onclick="sendNotification()">Send Notification</button>

    <script>
        // Create a connection to the SignalR hub
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("http://localhost:5117/notificationHub") // Hub endpoint from the server
            .withAutomaticReconnect() // Reconnect automatically if connection drops
            .build();

        // Handle messages received from the server
        connection.on("ReceiveNotification", (user, message) => {
            const notifications = document.getElementById("notifications");
            const notification = document.createElement("p");
            notification.innerHTML = `<strong>${user}:</strong> ${message} <small>(${new Date().toLocaleTimeString()})</small>`;
            notifications.appendChild(notification);
            notifications.scrollTop = notifications.scrollHeight; // Auto-scroll to bottom
        });

        // Start the connection to the hub
        connection.start()
            .then(() => {
                console.log("Connected to SignalR hub");
                document.getElementById("notifications").innerHTML = "<p><em>Connected! Waiting for notifications...</em></p>";
            })
            .catch(err => console.error("Connection error: ", err));

        // Send a message to the hub
        function sendNotification() {
            const user = document.getElementById("userInput").value;
            const message = document.getElementById("messageInput").value;
            
            if (message) {
                connection.invoke("SendNotification", user, message) // Call the hub method
                    .catch(err => console.error("Send error: ", err));
                document.getElementById("messageInput").value = ""; // Clear input
            }
        }

        // Allow Enter key to send the message
        document.getElementById("messageInput").addEventListener("keypress", (e) => {
            if (e.key === "Enter") sendNotification();
        });
    </script>
</body>
</html>


Now, to test the application we need to follow these steps:

  • Run the ASP.NET Core application
  • Open the HTML file in multiple browser tabs
  • Type a message in one tab and click “Send Notification”
  • Watch the message appear instantly in all tabs!


Before closing the post, I want to test the ‘api/notify’ endpoint.
It can be used to trigger a SignalR broadcast from an external source like for example, another backend service, a background job, or any system that can send an HTTP POST request.
In order to test it, we’ll call the endpoint using a Web API client (in my case Insomnia), and we’ll see the message appear instantly in both open pages connected to the hub.




Leave a Reply

Your email address will not be published. Required fields are marked *