SignalR is ASP.NET Core’s library for adding real-time bidirectional communication to web applications. It abstracts over transport protocols — WebSockets (preferred), Server-Sent Events, and Long Polling — and automatically falls back based on browser and network support. A Hub is the server-side class that coordinates communication: clients invoke Hub methods (client → server), and the Hub invokes methods on clients (server → client). For the Angular full-stack application, SignalR enables live notifications when posts are published, real-time comment updates, and presence features without polling.
SignalR Setup
// dotnet add package Microsoft.AspNetCore.SignalR
// ── Register SignalR ───────────────────────────────────────────────────────
builder.Services.AddSignalR(opts =>
{
opts.EnableDetailedErrors = app.Environment.IsDevelopment();
opts.KeepAliveInterval = TimeSpan.FromSeconds(15);
opts.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
opts.MaximumReceiveMessageSize = 32 * 1024; // 32KB max message size
})
.AddJsonProtocol(opts =>
{
// Consistent camelCase naming with the REST API JSON settings
opts.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
// Optional: .AddMessagePackProtocol() for binary (smaller + faster)
// ── CORS — must explicitly allow SignalR origin and credentials ────────────
builder.Services.AddCors(opts =>
opts.AddPolicy("AllowAngular", policy =>
policy.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials())); // required for WebSocket upgrade
// ── Map the Hub endpoint ──────────────────────────────────────────────────
app.UseCors("AllowAngular"); // must be before MapHub
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<NotificationHub>("/hubs/notifications");
app.MapHub<PostHub>("/hubs/posts");
// ── Hub — the real-time communication endpoint ────────────────────────────
public class NotificationHub : Hub
{
private readonly ILogger<NotificationHub> _logger;
public NotificationHub(ILogger<NotificationHub> logger) => _logger = logger;
// Called automatically when a client connects
public override async Task OnConnectedAsync()
{
_logger.LogInformation("Client connected: {ConnectionId}", Context.ConnectionId);
await base.OnConnectedAsync();
}
// Called automatically when a client disconnects
public override async Task OnDisconnectedAsync(Exception? exception)
{
_logger.LogInformation("Client disconnected: {ConnectionId} ({Reason})",
Context.ConnectionId, exception?.Message ?? "clean");
await base.OnDisconnectedAsync(exception);
}
// Hub method — Angular client invokes this
public async Task SendMessage(string message)
{
// Broadcast to ALL connected clients
await Clients.All.SendAsync("ReceiveMessage", message, Context.ConnectionId);
}
}
opts.Transports = HttpTransportType.WebSockets to WebSocket-only if you know your environment supports it.EnableDetailedErrors = app.Environment.IsDevelopment() to get detailed SignalR error messages in development (hub method names, exception messages) while keeping them suppressed in production. Without this, SignalR surfaces generic errors on the Angular side that make debugging difficult. The detailed errors are very helpful during development but should never reach production as they may expose internal implementation details.AllowCredentials() is required in the CORS policy for SignalR WebSocket connections. Without it, the WebSocket upgrade request from Angular is blocked by the browser’s CORS policy — SignalR silently falls back to Long Polling, which is significantly less efficient. If you see SignalR connections falling back to polling in development, check the browser’s Network tab for failed pre-flight requests and verify AllowCredentials() is in the CORS policy.Transport Types and Selection
| Transport | Direction | Support | Efficiency |
|---|---|---|---|
| WebSockets | Bidirectional | All modern browsers | ✅ Best |
| Server-Sent Events | Server → Client only | All except IE | Good |
| Long Polling | Bidirectional (simulated) | All browsers | Worst (high overhead) |
Common Mistakes
Mistake 1 — Missing AllowCredentials() in CORS (WebSockets fail, falls back to polling)
❌ Wrong — CORS policy without AllowCredentials(); WebSocket upgrade fails; Angular uses polling.
✅ Correct — always include .AllowCredentials() when using SignalR with CORS.
Mistake 2 — Injecting Scoped services into Hub constructor (Hub scope issues)
❌ Wrong — Hub is created per invocation; Scoped services may behave unexpectedly.
✅ Correct — Hubs support constructor injection; services resolve from the current scope per connection.