Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
{"short-description":".NET skill guidance for api tasks"}
dotnet-realtime-communication
Real-time communication patterns for .NET applications. Compares SignalR (full-duplex over WebSockets with automatic
fallback), Server-Sent Events (SSE, built-in to ASP.NET Core in .NET 10), JSON-RPC 2.0 (structured request-response over
any transport), and gRPC streaming (high-performance binary streaming). Provides decision guidance for choosing the
right protocol based on requirements.
Scope
SignalR hubs (WebSocket, auto-fallback, scaling)
Server-Sent Events (SSE, built-in .NET 10)
JSON-RPC 2.0 over any transport
gRPC streaming for high-performance binary
Protocol comparison and decision guidance
Out of scope
HTTP client factory and resilience pipelines -- see [skill:dotnet-http-client] and [skill:dotnet-resilience]
Native AOT architecture and trimming -- see [skill:dotnet-native-aot] and [skill:dotnet-trimming]
Blazor-specific SignalR usage -- see [skill:dotnet-blazor-patterns]
Cross-references: [skill:dotnet-grpc] for gRPC streaming implementation details and all four streaming patterns. See
[skill:dotnet-integration-testing] for testing real-time communication endpoints. See [skill:dotnet-blazor-patterns] for
Blazor-specific SignalR circuit management and render mode interaction.
SignalR: You need bidirectional real-time communication with browser clients. SignalR handles transport
negotiation automatically (WebSocket preferred, falls back to SSE, then Long Polling). Use when clients need to both
send and receive in real time.
SSE (.NET 10 built-in): You only need server-to-client push. Simpler than SignalR when bidirectional communication
is not required. Built into ASP.NET Core in .NET 10 -- no additional packages needed. Works with the browser's native
EventSource API.
JSON-RPC 2.0: You need structured request-response semantics over a simple transport. Used by Language Server
Protocol (LSP) and some .NET tooling. Not a streaming protocol -- use when you need named methods with typed
parameters over WebSocket or stdio.
gRPC streaming: Service-to-service streaming with maximum performance. Supports all four streaming patterns
(unary, server streaming, client streaming, bidirectional). Best when both endpoints are .NET services or
gRPC-compatible. See [skill:dotnet-grpc] for implementation details.
SignalR
SignalR provides real-time web functionality with automatic connection management and transport negotiation.
Server Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR(options =>
{
options.EnableDetailedErrors = builder.Environment.IsDevelopment();
options.MaximumReceiveMessageSize = 64 * 1024; // 64 KB
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
});
var app = builder.Build();
app.MapHub<NotificationHub>("/hubs/notifications");
```text
### Hub Implementation
```csharp
publicsealedclassNotificationHub(
ILogger<NotificationHub> logger) : Hub
{
publicoverrideasync Task OnConnectedAsync()
{
var userId = Context.UserIdentifier;
if (userId isnotnull)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"user:{userId}");
}
awaitbase.OnConnectedAsync();
}
// Client-to-server methodpublicasync Task SendMessage(string channel, string message)
{
// Broadcast to all clients in the channel groupawait Clients.Group(channel).SendAsync("ReceiveMessage",
Context.UserIdentifier, message);
}
// Server-to-client streamingpublic <> ()
{
(!cancellationToken.IsCancellationRequested)
{
;
Task.Delay(, cancellationToken);
}
}
}
```text
Use interfaces to compile-time safety client method calls:
```csharp
{
;
;
}
{
{
Clients.Group(channel).ReceiveMessage(
Context.UserIdentifier!, message);
}
}
```text
Inject `IHubContext` to send messages background services controllers:
```
{
{
hubContext.Clients.Group()
.OrderStatusChanged(orderId, status);
}
}
```text
SignalR automatically negotiates the best transport:
**WebSocket** (preferred) -- full-duplex, lowest latency
**Server-Sent Events** -- server-to-client only, falls back WebSockets unavailable
**Long Polling** -- universal fallback, highest latency
Force a specific transport needed:
```csharp
app.MapHub<NotificationHub>(, options =>
{
options.Transports = HttpTransportType.WebSockets |
HttpTransportType.ServerSentEvents;
});
```text
Use MessagePack smaller payloads faster serialization:
```csharp
builder.Services.AddSignalR()
.AddMessagePackProtocol();
```text
Override `OnConnectedAsync` `OnDisconnectedAsync` to manage connection state:
```
{
{
userId = Context.UserIdentifier;
connectionId = Context.ConnectionId;
logger.LogInformation(,
connectionId, userId);
(userId )
{
tracker.AddConnectionAsync(userId, connectionId);
Groups.AddToGroupAsync(connectionId, );
}
.OnConnectedAsync();
}
{
userId = Context.UserIdentifier;
connectionId = Context.ConnectionId;
(exception )
{
logger.LogWarning(exception,
, connectionId);
}
(userId )
{
tracker.RemoveConnectionAsync(userId, connectionId);
}
.OnDisconnectedAsync(exception);
}
}
```text
Groups provide a lightweight pub/sub mechanism. Connections can belong to multiple groups membership managed per-connection:
```csharp
: <>
{
{
Groups.AddToGroupAsync(Context.ConnectionId, roomName);
Clients.Group(roomName).UserJoined(Context.UserIdentifier!, roomName);
}
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, roomName);
Clients.Group(roomName).UserLeft(Context.UserIdentifier!, roomName);
}
{
Clients.Group(roomName).ReceiveMessage(
Context.UserIdentifier!, message);
}
{
Clients.Others.ReceiveMessage(
Context.UserIdentifier!, message);
}
}
```text
Groups are persisted -- they are cleared a connection disconnects. Re- connections to groups `OnConnectedAsync`
{
{
( reading stream.WithCancellation(cancellationToken))
{
ProcessReading(reading);
}
}
}
```text
SignalR uses the same authentication the ASP.NET Core host. For WebSocket connections, the access token sent via query because WebSocket does support custom headers:
```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = ;
options.Events = JwtBearerEvents
{
OnMessageReceived = context =>
{
accessToken = context.Request.Query[];
path = context.HttpContext.Request.Path;
(!.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments())
{
context.Token = accessToken;
}
Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<NotificationHub>()
.RequireAuthorization();
```text
Access `Context.UserIdentifier` the hub to identify the authenticated user. By maps to the `ClaimTypes.NameIdentifier` claim. Customize `IUserIdProvider`:
```csharp
:
{
? GetUserId(HubConnectionContext connection)
{
connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
builder.Services.AddSingleton<IUserIdProvider, EmailUserIdProvider>();
```text
For multi-server deployments, use a backplane to synchronize messages across instances. Without a backplane, messages sent one server are visible to connections other servers.
**Redis backplane:**
```csharp
builder.Services.AddSignalR()
.AddStackExchangeRedis(builder.Configuration.GetConnectionString()!,
options =>
{
options.Configuration.ChannelPrefix =
RedisChannel.Literal();
});
```text
**;
```csharp
Azure SignalR Service offloads connection management entirely -- the ASP.NET Core server handles hub logic Azure manages WebSocket connections, scaling, message routing.
---
.NET adds built- SSE support to ASP.NET Core, making server-to-client streaming straightforward without additional packages.
```csharp
app.MapGet(, (
OrderEventService eventService,
CancellationToken cancellationToken) =>
{
TypedResults.ServerSentEvents(
eventService.GetOrderEventsAsync(cancellationToken));
});
```text
```csharp
{
IAsyncEnumerable<SseItem<OrderEvent>> GetOrderEventsAsync(
[] CancellationToken cancellationToken)
{
(!cancellationToken.IsCancellationRequested)
{
evt = WaitForNextEvent(cancellationToken);
;
}
}
}
```text
```javascript
source = EventSource();
source.addEventListener(, () => {
order = JSON.parse(.data);
updateDashboard(order);
});
source.onerror = () => {
console.log();
};
```text
- **One-way push only** -- SSE simpler you need client-to-server messages
- **Browser native** --
{: , : , : {...}, : }
{: , : {...}, : }
{: , : , : {...}}
{: , : {: , : }, : }
```json
`StreamJsonRpc` the primary .NET library JSON-RPC :
```xml
<PackageReference Include= Version= />
```json
```csharp
StreamJsonRpc;
{
=> a + b;
=>
b == ? ArgumentException()
: Task.FromResult(a / b);
}
app.UseWebSockets();
app.Map(, (HttpContext context) =>
{
(!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = ;
;
}
ws = context.WebSockets.AcceptWebSocketAsync();
rpc = JsonRpc( WebSocketMessageHandler(ws));
rpc.AddLocalRpcTarget( CalculatorService());
rpc.StartListening();
rpc.Completion;
});
```text
```csharp
ws = ClientWebSocket();
ws.ConnectAsync( Uri(),
CancellationToken.None);
rpc = JsonRpc( WebSocketMessageHandler(ws));
rpc.StartListening();
result = rpc.InvokeAsync<>(, , );
```text
-
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine
ifneeded (e.g., from a database or cache).
### Client-to-Server Streaming
Clients can stream data to the hub using `IAsyncEnumerable<T>` or `ChannelReader<T>`:
```csharp
publicsealedclass UploadHub : Hub
no JavaScript library needed (uses `EventSource` API)
- **Automatic reconnection** -- browsers reconnect automatically with `Last-Event-ID`
- **HTTP/1.1 compatible** -- works through proxies that donot support WebSocket upgrade
---
## JSON-RPC 2.0
JSON-RPC 2.0 is a stateless, transport-agnostic remote procedure call protocol encoded in JSON. It is the foundation of the Language Server Protocol (LSP) andis used in some .NET tooling scenarios.
### Protocol Structure
```json
// Request
"jsonrpc"
"2.0"
"method"
"textDocument/completion"
"params"
"id"
1
// Response
"jsonrpc"
"2.0"
"result"
"id"
1
// Notification (no response expected)
"jsonrpc"
"2.0"
"method"
"textDocument/didChange"
"params"
// Error
"jsonrpc"
"2.0"
"error"
"code"
-32600
"message"
"Invalid Request"
"id"
1
### StreamJsonRpc (.NET Library)
is
for
2.0
"StreamJsonRpc"
"2.*"
// Server: expose methods via JSON-RPC over a stream
using
public
sealed
class
CalculatorService
publicintAdd(int a, int b)
public Task<double> DivideAsync(double a, double b)
0
throw
new
"Division by zero"
// Wire up over a WebSocket -- UseWebSockets() is required for upgrade handling
"/jsonrpc"
async
if
400
return
var
await
using
var
new
new
new
await
// Client
using
var
new
await
new
"ws://localhost:5000/jsonrpc"
using
var
new
new
var
await
int
"Add"
2
3
// result == 5
### When to Use JSON-RPC 2.0
Building or integrating with Language Server Protocol (LSP) implementations
- Simple RPC over WebSocket or stdio where gRPC is too heavyweight
- Interoperating with non-.NET systems that speak JSON-RPC
- Tooling and editor integrations
---
## gRPC Streaming
See [skill:dotnet-grpc] for complete gRPC implementation details including all four streaming patterns (unary, server streaming, client streaming, bidirectional streaming), authentication, load balancing, and health checks.
### Quick Decision: gRPC Streaming vs SignalR vs SSE
| Requirement | Choose |
|-------------|--------|
| Service-to-service, both .NET | gRPC streaming |
| Browser client needs bidirectional | SignalR |
| Browser client needs server push only | SSE |
| Maximum throughput, binary payloads | gRPC streaming |
| Automatic reconnection with browser clients | SSE (native) orSignalR (built-in) |
| Multiple client platforms (JS, mobile, .NET) | SignalR |
---
## Key Principles
- **Default to SignalR for browser-facing real-time** -- it handles transport negotiation, reconnection, and grouping out of the box
- **Use SSE for simple server push** -- .NET 10 built-in support makes it the lightest option for one-way notifications
- **Use gRPC streaming for service-to-service** -- highest performance, strongly typed contracts, all four streaming patterns
- **Use JSON-RPC 2.0 for tooling protocols** -- when you need structured RPC over simple transports (WebSocket, stdio)
- **Use strongly-typed hubs** -- `Hub<T>` catches method name typos at compile time instead of runtime
- **Scale SignalR with a backplane** -- Redis or Azure SignalR Service for multi-server deployments
See [skill:dotnet-native-aot] for AOT compilation pipeline and [skill:dotnet-aot-architecture] for AOT-compatible real-time communication patterns.
---
## Agent Gotchas
1. **Do not use SignalR when SSE suffices** -- if you only need server-to-client push without bidirectional communication, SSE is simpler and lighter.
2. **Do not forget `AddMessagePackProtocol()` on the server when the client uses MessagePack** -- mismatched protocols cause silent connection failures.
3. **Do not use Long Polling transport with SignalR unless required** -- it has significantly higher latency and server resource usage compared to WebSockets.
4. **Do not store connection IDs long-term** -- SignalR connection IDs change on reconnection. Use user identifiers or groups for addressing.
5. **Do not use gRPC streaming to browsers directly** -- browsers donot support HTTP/2 trailers natively. Use gRPC-Web with a proxy or choose SignalR/SSE instead.
6. **Do not confuse SSE with WebSocket** -- SSE isunidirectional (server-to-client only). If you need client-to-server messages, use SignalR or WebSocket directly.
7. **Do not forget `OnMessageReceived` for JWT with SignalR** -- WebSocket connections cannot send custom HTTP headers after the initial handshake. The access token must be read from the query stringin `JwtBearerEvents.OnMessageReceived`.
8. **Do not assume group membership persists across reconnections** -- groups are tied to connection IDs, which change on reconnect. Re-add connections to groups in `OnConnectedAsync`.
9. **Do not deploy multi-server SignalR without a backplane** -- without Redis or Azure SignalR Service, messages sent on one server instance are invisible to connections on other instances.
---
## Attribution
Adapted from [Aaronontheweb/dotnet-skills](https://github.com/Aaronontheweb/dotnet-skills) (MIT license).
---
## References
- [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)
- [Server-Sent Events in .NET 10](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/server-sent-events?view=aspnetcore-10.0)
- [StreamJsonRpc](https://github.com/microsoft/vs-streamjsonrpc)
- [gRPC streaming](https://learn.microsoft.com/en-us/aspnet/core/grpc/client?view=aspnetcore-10.0)
- [SignalR scaling with Redis](https://learn.microsoft.com/en-us/aspnet/core/signalr/redis-backplane?view=aspnetcore-10.0)
- [SignalR authentication and authorization](https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-10.0)
- [Azure SignalR Service](https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-overview)