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
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
Minimal checklist to get a working WASM-hosted chat
dotnet new blazorwasm --hosted -o BlazorSignalRApp
cd BlazorSignalRApp/Client
-> dotnet add package Microsoft.AspNetCore.SignalR.Client
Add ChatHub
class to Server and builder.Services.AddSignalR()
+ app.MapHub<ChatHub>("/chathub")
.
Add CORS policy allowing client origin + AllowCredentials()
.
Add a chat component to the Client with HubConnectionBuilder
and .On("ReceiveMessage", ...)
.
Run the Server and open the client in the browser.