Skip to main content
The Intent.AspNetCore.SignalR module enables real-time, bidirectional communication between server and clients using ASP.NET Core SignalR. Perfect for features like live notifications, chat, dashboards, and collaborative editing.

Overview

SignalR automatically manages connections and enables your server to push content to connected clients instantly. It abstracts away the complexities of WebSockets, Server-Sent Events, and Long Polling, automatically choosing the best transport method.

What Gets Generated

Hub Classes

SignalR hubs handle client-server communication:
public class NotificationHub : Hub
{
    private readonly INotificationService _notificationService;

    public NotificationHub(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public override async Task OnConnectedAsync()
    {
        var userId = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        if (userId != null)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, userId);
        }
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        var userId = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        if (userId != null)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId);
        }
        await base.OnDisconnectedAsync(exception);
    }
}

Hub Service

Services that send messages to clients:
public interface INotificationHubService
{
    Task NotifyUser(Guid userId, string message);
    Task NotifyAll(string message);
    Task NotifyGroup(string groupName, string message);
}

public class NotificationHubService : INotificationHubService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public NotificationHubService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task NotifyUser(Guid userId, string message)
    {
        await _hubContext.Clients
            .Group(userId.ToString())
            .SendAsync("ReceiveNotification", message);
    }

    public async Task NotifyAll(string message)
    {
        await _hubContext.Clients.All
            .SendAsync("ReceiveNotification", message);
    }

    public async Task NotifyGroup(string groupName, string message)
    {
        await _hubContext.Clients.Group(groupName)
            .SendAsync("ReceiveNotification", message);
    }
}

SignalRConfiguration

Configures SignalR services:
public static class SignalRConfiguration
{
    public static IServiceCollection AddSignalRConfiguration(
        this IServiceCollection services)
    {
        services.AddSignalR(options =>
        {
            options.EnableDetailedErrors = true;
            options.KeepAliveInterval = TimeSpan.FromSeconds(15);
            options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
            options.MaximumReceiveMessageSize = 102400; // 100 KB
        });

        services.AddScoped<INotificationHubService, NotificationHubService>();

        return services;
    }

    public static IApplicationBuilder UseSignalRConfiguration(
        this IApplicationBuilder app)
    {
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<NotificationHub>("/hubs/notifications");
        });

        return app;
    }
}

Key Features

Real-Time Updates

Push updates to clients instantly

Automatic Transport

Automatically chooses best transport method

Groups

Send messages to specific groups of clients

Scaling

Scale across multiple servers with backplane

Creating Hubs

Basic Hub

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

Hub with Authorization

[Authorize]
public class SecureChatHub : Hub
{
    public async Task SendMessage(string message)
    {
        var userName = Context.User?.Identity?.Name;
        await Clients.Others.SendAsync("ReceiveMessage", userName, message);
    }

    [Authorize(Roles = "Admin")]
    public async Task SendAdminMessage(string message)
    {
        await Clients.All.SendAsync("ReceiveAdminMessage", message);
    }
}

Hub with Dependency Injection

public class OrderHub : Hub
{
    private readonly IOrderService _orderService;
    private readonly ICurrentUserService _currentUserService;

    public OrderHub(
        IOrderService orderService,
        ICurrentUserService currentUserService)
    {
        _orderService = orderService;
        _currentUserService = currentUserService;
    }

    public async Task PlaceOrder(CreateOrderDto dto)
    {
        var orderId = await _orderService.CreateAsync(dto, default);
        var userId = _currentUserService.UserId;
        
        await Clients.User(userId.ToString())
            .SendAsync("OrderPlaced", orderId);
    }
}

Client Targets

Send to All Clients

await Clients.All.SendAsync("ReceiveMessage", "Hello everyone!");

Send to Specific Client

await Clients.Client(connectionId).SendAsync("ReceiveMessage", "Hello you!");

Send to All Except Caller

public async Task SendMessage(string message)
{
    await Clients.Others.SendAsync("ReceiveMessage", message);
}

Send to Specific User

await Clients.User(userId).SendAsync("ReceiveNotification", notification);

Send to Group

await Clients.Group("AdminGroup").SendAsync("ReceiveAlert", alert);

Send to Multiple Clients

var connectionIds = new[] { "conn1", "conn2", "conn3" };
await Clients.Clients(connectionIds).SendAsync("ReceiveMessage", message);

Groups

Adding to Groups

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

    public async Task LeaveRoom(string roomName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
        await Clients.Group(roomName).SendAsync(
            "UserLeft",
            Context.User?.Identity?.Name);
    }

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

Automatic Group Management

public class NotificationHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        // Add user to their personal group
        var userId = Context.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        if (userId != null)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{userId}");
        }

        // Add user to role groups
        var roles = Context.User?.FindAll(ClaimTypes.Role).Select(c => c.Value);
        if (roles != null)
        {
            foreach (var role in roles)
            {
                await Groups.AddToGroupAsync(Context.ConnectionId, $"role-{role}");
            }
        }

        await base.OnConnectedAsync();
    }
}

Using Hub Services

Call hub services from your application logic:
public class OrderService : IOrderService
{
    private readonly IApplicationDbContext _dbContext;
    private readonly IOrderHubService _hubService;

    public OrderService(
        IApplicationDbContext dbContext,
        IOrderHubService hubService)
    {
        _dbContext = dbContext;
        _hubService = hubService;
    }

    public async Task<Guid> CreateOrderAsync(
        CreateOrderDto dto,
        CancellationToken cancellationToken)
    {
        var order = new Order
        {
            // ... populate order
        };

        _dbContext.Orders.Add(order);
        await _dbContext.SaveChangesAsync(cancellationToken);

        // Notify user via SignalR
        await _hubService.NotifyOrderCreated(order.CustomerId, order.Id);

        return order.Id;
    }

    public async Task UpdateOrderStatusAsync(
        Guid orderId,
        OrderStatus status,
        CancellationToken cancellationToken)
    {
        var order = await _dbContext.Orders.FindAsync(orderId);
        order.Status = status;
        await _dbContext.SaveChangesAsync(cancellationToken);

        // Real-time status update
        await _hubService.NotifyOrderStatusChanged(order.CustomerId, orderId, status);
    }
}

Client Integration

JavaScript/TypeScript

import * as signalR from "@microsoft/signalr";

class SignalRService {
  private connection: signalR.HubConnection;

  constructor() {
    this.connection = new signalR.HubConnectionBuilder()
      .withUrl("/hubs/notifications", {
        accessTokenFactory: () => this.getAccessToken()
      })
      .withAutomaticReconnect()
      .configureLogging(signalR.LogLevel.Information)
      .build();

    this.setupHandlers();
  }

  private setupHandlers(): void {
    this.connection.on("ReceiveNotification", (message: string) => {
      console.log("Notification:", message);
      this.showNotification(message);
    });

    this.connection.on("ReceiveMessage", (user: string, message: string) => {
      console.log(`${user}: ${message}`);
    });
  }

  async start(): Promise<void> {
    try {
      await this.connection.start();
      console.log("SignalR connected");
    } catch (err) {
      console.error("SignalR connection error:", err);
      setTimeout(() => this.start(), 5000);
    }
  }

  async sendMessage(message: string): Promise<void> {
    await this.connection.invoke("SendMessage", message);
  }

  async joinRoom(roomName: string): Promise<void> {
    await this.connection.invoke("JoinRoom", roomName);
  }

  private getAccessToken(): string {
    return localStorage.getItem("token") || "";
  }

  private showNotification(message: string): void {
    // Display notification to user
  }
}

// Usage
const signalR = new SignalRService();
await signalR.start();

C# Client

public class SignalRClient
{
    private readonly HubConnection _connection;

    public SignalRClient(string hubUrl, string accessToken)
    {
        _connection = new HubConnectionBuilder()
            .WithUrl(hubUrl, options =>
            {
                options.AccessTokenProvider = () => Task.FromResult(accessToken);
            })
            .WithAutomaticReconnect()
            .Build();

        SetupHandlers();
    }

    private void SetupHandlers()
    {
        _connection.On<string>("ReceiveNotification", message =>
        {
            Console.WriteLine($"Notification: {message}");
        });

        _connection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            Console.WriteLine($"{user}: {message}");
        });
    }

    public async Task StartAsync()
    {
        try
        {
            await _connection.StartAsync();
            Console.WriteLine("Connected to SignalR hub");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error connecting: {ex.Message}");
            await Task.Delay(5000);
            await StartAsync();
        }
    }

    public async Task SendMessageAsync(string message)
    {
        await _connection.InvokeAsync("SendMessage", message);
    }

    public async Task StopAsync()
    {
        await _connection.StopAsync();
    }
}

Authentication

JWT Bearer Authentication

services.AddSignalR(options =>
{
    // Configure options
})
.AddJsonProtocol();

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        // JWT configuration...
        
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) &&
                    path.StartsWithSegments("/hubs"))
                {
                    context.Token = accessToken;
                }
                
                return Task.CompletedTask;
            }
        };
    });
Client includes token:
const connection = new signalR.HubConnectionBuilder()
  .withUrl("/hubs/notifications", {
    accessTokenFactory: () => localStorage.getItem("token")
  })
  .build();

Scaling with Azure SignalR Service

For production scaling:
services.AddSignalR()
    .AddAzureSignalR(configuration["Azure:SignalR:ConnectionString"]);

Redis Backplane

Scale across multiple servers:
services.AddSignalR()
    .AddStackExchangeRedis(configuration.GetConnectionString("Redis"));

Best Practices

  • Implement automatic reconnection
  • Handle connection lifecycle events
  • Clean up resources on disconnect
  • Monitor connection status
  • Keep messages small
  • Use binary protocols for large data
  • Implement backpressure handling
  • Monitor connection count
  • Always use authentication for sensitive data
  • Validate all client inputs
  • Implement authorization for hub methods
  • Use HTTPS in production
  • Handle hub method exceptions
  • Implement client-side error handling
  • Log connection failures
  • Provide meaningful error messages

Common Use Cases

Real-Time Notifications

public async Task NotifyUserAsync(Guid userId, NotificationDto notification)
{
    await _hubContext.Clients
        .Group($"user-{userId}")
        .SendAsync("ReceiveNotification", notification);
}

Live Dashboard Updates

public async Task BroadcastDashboardUpdate(DashboardData data)
{
    await _hubContext.Clients
        .Group("dashboard-viewers")
        .SendAsync("UpdateDashboard", data);
}

Chat Application

public async Task SendChatMessage(string roomId, ChatMessage message)
{
    await _hubContext.Clients
        .Group($"chat-{roomId}")
        .SendAsync("ReceiveMessage", message);
}

Progress Updates

public async Task ReportProgress(Guid taskId, int progress)
{
    await _hubContext.Clients
        .Group($"task-{taskId}")
        .SendAsync("ProgressUpdate", progress);
}

Installation

Intent.AspNetCore.SignalR

Dependencies

  • Intent.Common.CSharp
  • Intent.Modelers.Services
  • Intent.OutputManager.RoslynWeaver

Next Steps

Controllers

Integrate SignalR with REST APIs

Security

Secure SignalR connections

ASP.NET Core

Learn about the core infrastructure

Build docs developers (and LLMs) love