Skip to content
Back to skills

Signalr

ASecurity

Implement or review SignalR hubs, streaming, reconnection, transport, and real-time delivery patterns in ASP.NET Core applications. USE FOR: building chat, notification, collaboration, or live-update features; debugging hub lifetime, connection state, or transport issues; deciding whether SignalR or another. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, ...

  • 485 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
developmentjavascriptjavaazuredebuggingapidatabasesecurityperformancedocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add managedcode/dotnet-skills --skill signalr --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Signalr?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Signalr
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/managedcode-signalr/badge)](https://www.skillsdirectory.com/skills/managedcode-signalr)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: signalr
description: "Implement or review SignalR hubs, streaming, reconnection, transport, and real-time delivery patterns in ASP.NET Core applications. USE FOR: building chat, notification, collaboration, or live-update features; debugging hub lifetime, connection state, or transport issues; deciding whether SignalR or another. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made."
compatibility: "Requires ASP.NET Core SignalR server or client code."
---

# SignalR

## Trigger On

- building chat, notification, collaboration, or live-update features
- debugging hub lifetime, connection state, or transport issues
- deciding whether SignalR or another transport better fits the scenario
- implementing real-time broadcasting to groups of connected clients
- scaling SignalR across multiple servers

## Documentation

- [ASP.NET Core SignalR Overview](https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-10.0)
- [SignalR Hubs](https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-10.0)
- [SignalR API Design Considerations](https://learn.microsoft.com/en-us/aspnet/core/signalr/api-design?view=aspnetcore-10.0)
- [SignalR Production Hosting and Scaling](https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-10.0)
- [SignalR Configuration](https://learn.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-10.0)

### References

- [patterns.md](references/patterns.md) - Detailed hub patterns, streaming, groups, presence, and advanced messaging techniques
- [anti-patterns.md](references/anti-patterns.md) - Common SignalR mistakes and how to avoid them

## Workflow

1. Use SignalR for broadcast-style or connection-oriented real-time features; do not force gRPC into hub-style fan-out scenarios.
2. Model hub contracts intentionally and keep hub methods thin, delegating durable work elsewhere.
3. Plan for reconnection, backpressure, auth, and fan-out costs instead of treating real-time messaging as stateless request/response.
4. Use groups, presence, and connection metadata deliberately so scale-out behavior is understandable.
5. If Native AOT or trimming is in play, validate supported protocols and serialization choices explicitly.
6. Test connection behavior and failure modes, not just happy-path message delivery.

## Hub Patterns

### Strongly-Typed Hub (Recommended)
```csharp
// Define the client interface
public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
    Task UserJoined(string user);
    Task UserLeft(string user);
}

// Implement the strongly-typed hub
public class ChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
    {
        // Compiler checks client method calls
        await Clients.All.ReceiveMessage(user, message);
    }

    public override async Task OnConnectedAsync()
    {
        await Clients.Others.UserJoined(Context.User?.Identity?.Name ?? "Anonymous");
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        await Clients.Others.UserLeft(Context.User?.Identity?.Name ?? "Anonymous");
        await base.OnDisconnectedAsync(exception);
    }
}
```

### Using Groups for Targeted Messaging
```csharp
public class NotificationHub : Hub<INotificationClient>
{
    public async Task JoinGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
        await Clients.Group(groupName).UserJoined(Context.User?.Identity?.Name);
    }

    public async Task LeaveGroup(string groupName)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
    }

    public async Task SendToGroup(string groupName, string message)
    {
        await Clients.Group(groupName).ReceiveNotification(message);
    }
}
```

### Hub Method with Custom Object Parameters (API Versioning)
```csharp
// Use custom objects to avoid breaking changes
public class SendMessageRequest
{
    public string Message { get; set; } = string.Empty;
    public string? Recipient { get; set; }  // Added later without breaking clients
    public int? Priority { get; set; }       // Added later without breaking clients
}

public class ChatHub : Hub<IChatClient>
{
    public async Task SendMessage(SendMessageRequest request)
    {
        // Handle both old and new clients
        if (request.Recipient != null)
        {
            await Clients.User(request.Recipient).ReceiveMessage(request.Message);
        }
        else
        {
            await Clients.All.ReceiveMessage(request.Message);
        }
    }
}
```

## Client Patterns

### JavaScript Client with Automatic Reconnection
```javascript
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // Retry delays
    .configureLogging(signalR.LogLevel.Information)
    .build();

// Handle reconnection events
connection.onreconnecting(error => {
    console.log("Reconnecting...", error);
    updateUIForReconnecting();
});

connection.onreconnected(connectionId => {
    console.log("Reconnected with ID:", connectionId);
    // Rejoin groups - reconnection does not restore group membership
    rejoinGroups();
    updateUIForConnected();
});

connection.onclose(error => {
    console.log("Connection closed", error);
    updateUIForDisconnected();
});

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
}

start();
```

### .NET Client with Reconnection
```csharp
var connection = new HubConnectionBuilder()
    .WithUrl("https://localhost:5001/chatHub", options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(GetAccessToken());
    })
    .WithAutomaticReconnect()
    .Build();

connection.Reconnecting += error =>
{
    _logger.LogWarning("Connection lost. Reconnecting: {Error}", error?.Message);
    return Task.CompletedTask;
};

connection.Reconnected += connectionId =>
{
    _logger.LogInformation("Reconnected with ID: {ConnectionId}", connectionId);
    // Rejoin groups after reconnection
    return RejoinGroupsAsync();
};

connection.Closed += async error =>
{
    _logger.LogError("Connection closed: {Error}", error?.Message);
    await Task.Delay(Random.Shared.Next(0, 5) * 1000);
    await connection.StartAsync();
};

await connection.StartAsync();
```

## Server Configuration

### Hub Registration with Authentication
```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
    options.StreamBufferCapacity = 10;
    options.KeepAliveInterval = TimeSpan.FromSeconds(15);
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
})
.AddMessagePackProtocol(); // Binary protocol for performance

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Read token from query string for WebSocket connections
                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;
            }
        };
    });

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapHub<ChatHub>("/hubs/chat");
```

### Sending Messages from Outside a Hub
```csharp
public class NotificationService
{
    private readonly IHubContext<NotificationHub, INotificationClient> _hubContext;

    public NotificationService(IHubContext<NotificationHub, INotificationClient> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task NotifyAllAsync(string message)
    {
        await _hubContext.Clients.All.ReceiveNotification(message);
    }

    public async Task NotifyUserAsync(string userId, string message)
    {
        await _hubContext.Clients.User(userId).ReceiveNotification(message);
    }

    public async Task NotifyGroupAsync(string groupName, string message)
    {
        await _hubContext.Clients.Group(groupName).ReceiveNotification(message);
    }
}
```

## Scaling with Redis Backplane

```csharp
builder.Services.AddSignalR()
    .AddStackExchangeRedis(connectionString, options =>
    {
        options.Configuration.ChannelPrefix = RedisChannel.Literal("MyApp");
    });
```

## Anti-Patterns to Avoid

| Anti-Pattern | Why It's Bad | Better Approach |
|--------------|--------------|-----------------|
| Storing state in Hub properties | Hub instances are created per method call | Use `IMemoryCache`, database, or external store |
| Instantiating Hub directly | Bypasses SignalR infrastructure | Use `IHubContext<THub>` for external messaging |
| Not awaiting `SendAsync` calls | Messages may not be sent before hub method completes | Always `await` async hub calls |
| Adding method parameters without versioning | Breaking change for existing clients | Use custom object parameters |
| Ignoring reconnection group loss | Clients lose group membership on reconnect | Re-add to groups in `OnConnectedAsync` or client reconnect handler |
| Large payloads over SignalR | Memory pressure, bandwidth issues | Use REST/gRPC for bulk data, SignalR for notifications |
| Missing backplane in multi-server | Messages only reach clients on same server | Use Redis backplane or Azure SignalR Service |
| Exposing ORM entities directly | May serialize sensitive data | Use DTOs with explicit properties |
| Not validating incoming messages | Security risk after initial auth | Validate every hub method input |

## Best Practices

### Connection Management
1. **Enable automatic reconnection** with exponential backoff delays
2. **Handle group rejoining** explicitly after reconnection (connection ID changes)
3. **Implement heartbeat monitoring** on the client to detect stale connections
4. **Use sticky sessions** when scaling across multiple servers (unless using Azure SignalR Service)

### Performance
1. **Use MessagePack protocol** for smaller message sizes and faster serialization
2. **Throttle high-frequency events** like typing indicators or mouse movements
3. **Batch messages** when possible instead of many small sends
4. **Set appropriate buffer sizes** based on expected message throughput

### Security
1. **Authenticate at connection time** using JWT tokens via query string
2. **Authorize hub methods** using `[Authorize]` attribute
3. **Validate all incoming messages** even after authentication
4. **Use HTTPS** for all SignalR connections

### API Design
1. **Use strongly-typed hubs** to catch client method name typos at compile time
2. **Use custom object parameters** to enable backward-compatible API evolution
3. **Version hub names** (e.g., `ChatHubV2`) for breaking changes
4. **Keep hub methods thin** and delegate business logic to services

### Observability
1. **Log connection events** (connect, disconnect, reconnect)
2. **Track transport type** used by each connection
3. **Monitor message delivery** latency and failure rates
4. **Integrate with Application Insights** or other APM tools

## Deliver

- clear hub contracts and connection behavior
- real-time delivery that matches the product scenario
- validation for reconnection and authorization flows
- appropriate scale-out strategy for multi-server deployments

## Validate

- SignalR is the correct transport for the use case
- hub methods remain orchestration-oriented
- group and auth behavior are explicit and tested
- reconnection and group membership are handled correctly
- backplane is configured for multi-server scenarios
- message validation is implemented in hub methods

Files in this skill

  • SKILL.md12.1 KB
  • manifest.json98 B
  • references/anti-patterns.md19.5 KB
  • references/patterns.md20.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.