SignalR  

SignalR using Blazor

I’ll cover the two most common scenarios:

  • Blazor WebAssembly (hosted) β€” a WASM client + ASP.NET Core server that exposes a Hub (typical chat/notifications scenario).

  • Blazor Server β€” explanation + the recommended patterns for server-side apps (because Blazor Server already uses SignalR under the hood).

I’ll include full code snippets (Hub, Program.cs, Blazor component), CORS/auth tips, reconnection, groups, streaming, and a quick background-service example for server push.

Quick comparison β€” which to pick

  • Blazor WebAssembly (hosted) β€” choose when you have a separate server that needs to push to browser clients (classic SignalR use).

  • Blazor Server β€” you already have real-time updates via Blazor circuits; for server-originated events use a shared notification service or a hub if you need non-Blazor clients to receive messages.

Prerequisites

  • .NET 6/7/8+ SDK installed (examples below use the minimal hosting model).

  • Basic knowledge of C# + Blazor.

  • CLI commands use dotnet .

A. Blazor WebAssembly (hosted) β€” example: simple Chat

1) Create a hosted Blazor WebAssembly solution

  
    dotnet new blazorwasm --hosted -o BlazorSignalRApp
 cd BlazorSignalRApp
  

This creates three projects: Client , Server , Shared .

2) Add packages

  • Server needs SignalR server (usually already present in ASP.NET Core): builder.Services.AddSignalR();

  • Client needs SignalR client package:

  
    cd Client
 dotnet add package Microsoft.AspNetCore.SignalR.Client
  

3) Create the Hub (Server project)
Server/Hubs/ChatHub.cs

  
    using Microsoft.AspNetCore.SignalR;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        // Broadcast to all connected clients
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}
  

4) Wire up SignalR & CORS (Server Program.cs)
Server/Program.cs (minimal example)

  
    var builder = WebApplication.CreateBuilder(args);

// Allow the client origin (adjust ports/urls as needed)
builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", policy =>
        policy.WithOrigins("https://localhost:5001") // client origin(s)
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials());
});

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors("CorsPolicy");

app.MapRazorPages();
app.MapControllers();
app.MapHub<ChatHub>("/chathub"); // hub endpoint

app.MapFallbackToFile("index.html");
app.Run();
  

Important: If client/server run on different origins, AllowCredentials() and WithOrigins(...) are required and you must not use AllowAnyOrigin() with credentials.

5) Create the Blazor client UI & connect (Client project)
Client/Pages/Chat.razor

  
    @page "/chat"
@using Microsoft.AspNetCore.SignalR.Client
@using Microsoft.AspNetCore.Components.Web
@inject NavigationManager Navigation

<h3>SignalR Chat</h3>

<input placeholder="Your name" @bind="user" />
<input placeholder="Type a message" @bind="message" @onkeydown="HandleKeyDown"/>
<button @onclick="Send">Send</button>

<ul>
    @foreach (var m in messages)
    {
        <li>@m</li>
    }
</ul>

@code {
    private HubConnection? hubConnection;
    private string user = "Guest";
    private string message = "";
    private List<string> messages = new();

    protected override async Task OnInitializedAsync()
    {
        // Build connection to the hub path on the *server* project
        hubConnection = new HubConnectionBuilder()
            .WithUrl(Navigation.ToAbsoluteUri("/chathub")) // URL maps to Server/MapHub
            .WithAutomaticReconnect()
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (userFromServer, messageFromServer) =>
        {
            messages.Add($"{userFromServer}: {messageFromServer}");
            InvokeAsync(StateHasChanged);
        });

        await hubConnection.StartAsync();
    }

    private async Task Send()
    {
        if (string.IsNullOrWhiteSpace(message) || hubConnection is null) return;
        await hubConnection.InvokeAsync("SendMessage", user, message);
        message = "";
    }

    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        if (e.Key == "Enter") await Send();
    }

    public async ValueTask DisposeAsync()
    {
        if (hubConnection != null)
        {
            await hubConnection.DisposeAsync();
        }
    }
}
  

6) Run

  • Start the Server project (it hosts client files too). In VS or CLI: dotnet run --project Server (or run the solution).

  • Open the client URL (usually served by Server project) and try multiple browser tabs.

B β€” Blazor Server β€” recommended patterns

Short answer: Blazor Server already runs over SignalR; you don’t need SignalR to update component UI from server code that runs in the same app. Instead use patterns below to push server-originated updates.

Pattern 1. In-app notifications (recommended)

Create a singleton notification service that components subscribe to; a background task pushes events into it; components call StateHasChanged .

Services/NotificationService.cs

  
    public class NotificationService
{
    public event Action<string>? OnNotify;

    public void Notify(string message) => OnNotify?.Invoke(message);
}
  

Register

  
    builder.Services.AddSingleton<NotificationService>();
  

Component subscribes in OnInitialized and calls StateHasChanged when notified.

Pattern 2. Create a SignalR Hub if you need non-Blazor clients (JS or WASM) to receive messages

  • Add a Hub as shown earlier and call IHubContext<YourHub> from server code/controllers/background services to broadcast.

  • Browser clients (JS or WASM) connect to that Hub. Blazor Server components themselves don't automatically connect to that hub unless you add a JavaScript client in the browser and forward messages via JS interop to the component.

Example. push from background service via IHubContext (Server)

SomeBackgroundService.cs

  
    public class SomeBackgroundService : BackgroundService
{
    private readonly IHubContext<ChatHub> _hubContext;

    public SomeBackgroundService(IHubContext<ChatHub> hubContext)
    {
        _hubContext = hubContext;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
            await _hubContext.Clients.All.SendAsync("ReceiveMessage", "Server", "Ping from background service");
        }
    }
}
  

Register it: builder.Services.AddHostedService<SomeBackgroundService>();

C. Useful features & tips

Automatic reconnect

  
    var hub = new HubConnectionBuilder()
    .WithUrl(url)
    .WithAutomaticReconnect() // default retry behavior
    .Build();
  

Passing auth token (WASM)

  
    var hub = new HubConnectionBuilder()
  .WithUrl(Navigation.ToAbsoluteUri("/chathub"), options =>
  {
      options.AccessTokenProvider = async () => /* get JWT token string */;
  })
  .Build();
  

Groups
Server:

  
    public Task JoinGroup(string groupName) => Groups.AddToGroupAsync(Context.ConnectionId, groupName);
public Task SendToGroup(string groupName, string message) =>
    Clients.Group(groupName).SendAsync("ReceiveMessage", message);
  

Streaming (server β†’ client)
Server (IAsyncEnumerable):

  
    public async IAsyncEnumerable<int> Counter(int count, [EnumeratorCancellation] CancellationToken cancellationToken)
{
    for (int i = 0; i < count; i++)
    {
        yield return i;
        await Task.Delay(1000, cancellationToken);
    }
}
  

Client

  
    var stream = hubConnection.StreamAsync<int>("Counter", 10);
await foreach (var item in stream)
{
    // handle streaming items
}
  

Debugging tips

  • Check the browser console for SignalR errors.

  • Ensure CORS & AllowCredentials() are set correctly when the client/server are different origins.

  • If using HTTPS locally, use the same scheme and certificate trust.

  • If you see 403 for access token, confirm AccessTokenProvider is returning a valid token and the server accepts it.

Scaling

  • For multiple server instances, use a backplane (Redis) or Azure SignalR Service: AddSignalR().AddAzureSignalR("<connectionString>")

Minimal checklist to get a working WASM-hosted chat

  1. dotnet new blazorwasm --hosted -o BlazorSignalRApp

  2. cd BlazorSignalRApp/Client -> dotnet add package Microsoft.AspNetCore.SignalR.Client

  3. Add ChatHub class to Server and builder.Services.AddSignalR() + app.MapHub<ChatHub>("/chathub") .

  4. Add CORS policy allowing client origin + AllowCredentials() .

  5. Add a chat component to the Client with HubConnectionBuilder and .On("ReceiveMessage", ...) .

  6. Run the Server and open the client in the browser.