-
Notifications
You must be signed in to change notification settings - Fork 378
Add implementing-websocket-endpoints skill #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,232 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```skill | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: implementing-websocket-endpoints | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: Implement WebSocket endpoints in ASP.NET Core 8+ using the built-in middleware. Use when adding real-time bidirectional communication to an API. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Implementing WebSocket Endpoints in ASP.NET Core | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## When to Use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Real-time bidirectional communication (chat, live updates, gaming) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Need to push data from server to client without polling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Lower overhead than HTTP long-polling or SSE for high-frequency updates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Client needs to send messages to server unprompted | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## When Not to Use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Server-to-client only (use Server-Sent Events — simpler) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Need automatic reconnection and hub abstraction (use SignalR instead) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Simple request/response patterns (use regular HTTP) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Inputs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Input | Required | Description | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |-------|----------|-------------| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | WebSocket path | Yes | URL path for WebSocket endpoint | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Message format | No | Text (JSON) or binary | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| | Connection management | No | How to track connected clients | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Step 1: CRITICAL — There is no `MapWebSocket()` method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```csharp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // COMMON MISTAKE: Developers look for a MapWebSocket method. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // It does NOT exist in ASP.NET Core. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // WRONG — these don't exist: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.MapWebSocket("/ws", handler); // ❌ NOT a real method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.MapGet("/ws").UseWebSocket(); // ❌ NOT a real method | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // CORRECT — WebSocket is middleware-based, not endpoint-routing: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.UseWebSockets(); // ← Register the middleware | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Then handle WebSocket requests in regular middleware or endpoints: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| app.Map("/ws", async (HttpContext context) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!context.WebSockets.IsWebSocketRequest) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context.Response.StatusCode = StatusCodes.Status400BadRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using var ws = await context.WebSockets.AcceptWebSocketAsync(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await HandleWebSocketAsync(ws, context.RequestAborted); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ### Step 2: Configure WebSocket middleware | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```csharp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Program.cs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Services.AddWebSockets(options => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // WRONG — this method doesn't exist! Use raw middleware options: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+66
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.Services.AddWebSockets(options => | |
| { | |
| // WRONG — this method doesn't exist! Use raw middleware options: | |
| }); | |
| // Note: There is no AddWebSockets(...) service registration method. | |
| // You configure WebSocket options when calling UseWebSockets on IApplicationBuilder. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider setting KeepAliveTimeout as well.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The UseWebSockets call is duplicated - once on line 71 with WebSocketOptions, and again on line 83 without parameters. The second call on line 83 should be removed as the middleware is already registered on line 71. Additionally, the comment about ordering is misleading because the code shows UseWebSockets being called with options before UseRouting, which is the correct approach - you don't need to call it twice.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic here is inconsistent with the comment. The code checks if EndOfMessage is false and comments "Don't process partial messages!" but then proceeds to process and echo the message anyway on lines 116-121 regardless of the EndOfMessage value. The echo should only happen when EndOfMessage is true, or fragments should be accumulated first. As written, this will send incomplete message echoes back to the client.
| while (!result.CloseStatus.HasValue) | |
| { | |
| if (result.MessageType == WebSocketMessageType.Text) | |
| { | |
| var message = Encoding.UTF8.GetString(buffer, 0, result.Count); | |
| // CRITICAL: For large messages, EndOfMessage may be false | |
| // You must accumulate fragments until EndOfMessage == true | |
| if (!result.EndOfMessage) | |
| { | |
| // Accumulate into a MemoryStream or larger buffer | |
| // Don't process partial messages! | |
| } | |
| // Echo back (or process the message) | |
| var responseBytes = Encoding.UTF8.GetBytes($"Echo: {message}"); | |
| await webSocket.SendAsync( | |
| new ArraySegment<byte>(responseBytes), | |
| WebSocketMessageType.Text, | |
| endOfMessage: true, // ← MUST set this for the last (or only) fragment | |
| ct); | |
| var messageBuilder = new StringBuilder(); | |
| while (!result.CloseStatus.HasValue) | |
| { | |
| if (result.MessageType == WebSocketMessageType.Text) | |
| { | |
| var chunk = Encoding.UTF8.GetString(buffer, 0, result.Count); | |
| messageBuilder.Append(chunk); | |
| // CRITICAL: For large messages, EndOfMessage may be false | |
| // You must accumulate fragments until EndOfMessage == true | |
| if (!result.EndOfMessage) | |
| { | |
| // Accumulate into the StringBuilder | |
| // Don't process partial messages! | |
| } | |
| else | |
| { | |
| var message = messageBuilder.ToString(); | |
| messageBuilder.Clear(); | |
| // Echo back (or process the message) | |
| var responseBytes = Encoding.UTF8.GetBytes($"Echo: {message}"); | |
| await webSocket.SendAsync( | |
| new ArraySegment<byte>(responseBytes), | |
| WebSocketMessageType.Text, | |
| endOfMessage: true, // ← MUST set this for the last (or only) fragment | |
| ct); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else binary??
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on line 166 says "ToList() snapshot to avoid modification during iteration" but the code doesn't actually call ToList(). The LINQ query creates a deferred execution chain that will still iterate over the live ConcurrentDictionary.Values collection. To create a snapshot, add .ToList() after the Where clause and before the Select, like: _connections.Values.Where(s => s.State == WebSocketState.Open).ToList().Select(...).
| .Where(s => s.State == WebSocketState.Open) // Only open sockets | |
| .Where(s => s.State == WebSocketState.Open) // Only open sockets | |
| .ToList() |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is slightly misleading. The browser WebSocket API doesn't support custom headers at all, not just "after the initial handshake". The comment should clarify that custom headers can only be sent during the HTTP upgrade request, and the browser WebSocket API doesn't provide a way to set these (hence the need for query strings or cookies). The phrase "after the initial handshake" suggests headers work initially but not later, when really the browser API prevents setting custom headers entirely.
| // CRITICAL: WebSocket connections don't support standard HTTP auth headers | |
| // after the initial handshake. The auth happens on the HTTP upgrade request. | |
| // CRITICAL: HTTP auth headers can only be sent on the initial HTTP upgrade request. | |
| // Browser WebSocket APIs cannot set custom headers at all, so use query strings or cookies. |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using an access_token in the query string for WebSocket authentication (ws://server/ws?access_token=xxx) risks leaking the token via server/proxy logs, browser history, and Referer headers, allowing unauthorized reuse of the token. An attacker (or any party with log access) who obtains this URL can impersonate the user over the WebSocket until the token is revoked or expires. Prefer cookie-based authentication or established ASP.NET Core auth middleware (e.g., bearer or cookie auth tied to [Authorize]) so credentials are sent in headers/cookies over wss:// and are not embedded in the URL.
| // Option 1: Query string token (common for browser clients) | |
| app.Map("/ws", async (HttpContext context) => | |
| { | |
| // Browser WebSocket API doesn't support custom headers | |
| // Use query string: ws://server/ws?access_token=xxx | |
| var token = context.Request.Query["access_token"]; | |
| if (string.IsNullOrEmpty(token)) | |
| { | |
| context.Response.StatusCode = 401; | |
| return; | |
| } | |
| // Validate token here... | |
| if (context.WebSockets.IsWebSocketRequest) | |
| { | |
| using var ws = await context.WebSockets.AcceptWebSocketAsync(); | |
| await HandleWebSocketAsync(ws, context.RequestAborted); | |
| } | |
| }); | |
| // Option 2: Cookie auth works naturally (cookies are sent on upgrade request) | |
| // Option 3: Use [Authorize] attribute if using cookie or negotiate auth | |
| // | |
| // DO NOT pass access tokens in the query string, e.g.: | |
| // ws://server/ws?access_token=xxx | |
| // URLs are often logged and may leak tokens. Instead, rely on standard | |
| // ASP.NET Core authentication (cookies or bearer tokens in headers). | |
| // Example: secure WebSocket endpoint using ASP.NET Core auth | |
| // Assumes you've configured authentication/authorization in Program.cs: | |
| // builder.Services.AddAuthentication(...); | |
| // builder.Services.AddAuthorization(); | |
| // app.UseAuthentication(); | |
| // app.UseAuthorization(); | |
| app.Map("/ws", async (HttpContext context) => | |
| { | |
| // Authentication happens on the HTTP upgrade request. | |
| // Cookies and Authorization headers are sent automatically. | |
| if (!context.User?.Identity?.IsAuthenticated ?? true) | |
| { | |
| context.Response.StatusCode = StatusCodes.Status401Unauthorized; | |
| return; | |
| } | |
| if (!context.WebSockets.IsWebSocketRequest) | |
| { | |
| context.Response.StatusCode = StatusCodes.Status400BadRequest; | |
| return; | |
| } | |
| using var ws = await context.WebSockets.AcceptWebSocketAsync(); | |
| await HandleWebSocketAsync(ws, context.RequestAborted); | |
| }) | |
| .RequireAuthorization(); // Enforce auth using configured schemes (cookie, bearer, etc.) | |
| // Cookie auth works naturally (cookies are sent on the upgrade request). | |
| // Bearer tokens can be sent in the Authorization header (not in the URL). | |
| // You can also use [Authorize] on minimal APIs/controllers that upgrade to WebSockets. |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The closing triple-backtick wrapping the entire file should be removed. The file should end after the final common mistake item, with no code block wrapper around the entire content.
| ``` |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scenarios: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: "Implement WebSocket chat endpoint in ASP.NET Core 8" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| I need to add a WebSocket endpoint to my ASP.NET Core 8 API for a real-time chat feature. Requirements: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. WebSocket endpoint at /ws/chat | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. Track connected clients and broadcast messages to all when one client sends | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3. Handle proper connect/disconnect lifecycle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4. Authenticate users via a token in the query string (browser WebSocket API doesn't support custom headers) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5. Only allow connections from our frontend at https://myapp.com | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| I've been looking for something like `app.MapWebSocket("/ws/chat", handler)` but can't find it. How does WebSocket work in ASP.NET Core 8? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assertions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - type: "output_matches" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pattern: "(UseWebSockets|WebSocketOptions)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - type: "output_matches" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pattern: "(AcceptWebSocketAsync)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - type: "output_matches" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pattern: "(ReceiveAsync|SendAsync)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - type: "output_matches" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pattern: "(AllowedOrigins|Origin)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rubric: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Explained that MapWebSocket does not exist in ASP.NET Core — WebSockets use UseWebSockets() middleware with manual upgrade via AcceptWebSocketAsync" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Configured WebSocketOptions with KeepAliveInterval and AllowedOrigins restricted to https://myapp.com for cross-origin protection" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Implemented a proper receive loop checking EndOfMessage for fragmented messages and CloseStatus for disconnect" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Used CloseOutputAsync (not CloseAsync) when responding to client-initiated close to avoid deadlock" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Implemented a thread-safe connection manager using ConcurrentDictionary for tracking and broadcasting to connected clients" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - "Handled authentication via query string token since browser WebSocket API cannot send custom headers after handshake" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect_tools: ["bash"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout: 120 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+30
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt: | | |
| I need to add a WebSocket endpoint to my ASP.NET Core 8 API for a real-time chat feature. Requirements: | |
| 1. WebSocket endpoint at /ws/chat | |
| 2. Track connected clients and broadcast messages to all when one client sends | |
| 3. Handle proper connect/disconnect lifecycle | |
| 4. Authenticate users via a token in the query string (browser WebSocket API doesn't support custom headers) | |
| 5. Only allow connections from our frontend at https://myapp.com | |
| I've been looking for something like `app.MapWebSocket("/ws/chat", handler)` but can't find it. How does WebSocket work in ASP.NET Core 8? | |
| assertions: | |
| - type: "output_matches" | |
| pattern: "(UseWebSockets|WebSocketOptions)" | |
| - type: "output_matches" | |
| pattern: "(AcceptWebSocketAsync)" | |
| - type: "output_matches" | |
| pattern: "(ReceiveAsync|SendAsync)" | |
| - type: "output_matches" | |
| pattern: "(AllowedOrigins|Origin)" | |
| rubric: | |
| - "Explained that MapWebSocket does not exist in ASP.NET Core — WebSockets use UseWebSockets() middleware with manual upgrade via AcceptWebSocketAsync" | |
| - "Configured WebSocketOptions with KeepAliveInterval and AllowedOrigins restricted to https://myapp.com for cross-origin protection" | |
| - "Implemented a proper receive loop checking EndOfMessage for fragmented messages and CloseStatus for disconnect" | |
| - "Used CloseOutputAsync (not CloseAsync) when responding to client-initiated close to avoid deadlock" | |
| - "Implemented a thread-safe connection manager using ConcurrentDictionary for tracking and broadcasting to connected clients" | |
| - "Handled authentication via query string token since browser WebSocket API cannot send custom headers after handshake" | |
| expect_tools: ["bash"] | |
| timeout: 120 | |
| prompt: | | |
| I need to add a WebSocket endpoint to my ASP.NET Core 8 API for a real-time chat feature. Requirements: | |
| 1. WebSocket endpoint at /ws/chat | |
| 2. Track connected clients and broadcast messages to all when one client sends | |
| 3. Handle proper connect/disconnect lifecycle | |
| 4. Authenticate users via a token in the query string (browser WebSocket API doesn't support custom headers) | |
| 5. Only allow connections from our frontend at https://myapp.com | |
| I've been looking for something like `app.MapWebSocket("/ws/chat", handler)` but can't find it. How does WebSocket work in ASP.NET Core 8? | |
| assertions: | |
| - type: "output_matches" | |
| pattern: "(UseWebSockets|WebSocketOptions)" | |
| - type: "output_matches" | |
| pattern: "(AcceptWebSocketAsync)" | |
| - type: "output_matches" | |
| pattern: "(ReceiveAsync|SendAsync)" | |
| - type: "output_matches" | |
| pattern: "(AllowedOrigins|Origin)" | |
| rubric: | |
| - "Explained that MapWebSocket does not exist in ASP.NET Core — WebSockets use UseWebSockets() middleware with manual upgrade via AcceptWebSocketAsync" | |
| - "Configured WebSocketOptions with KeepAliveInterval and AllowedOrigins restricted to https://myapp.com for cross-origin protection" | |
| - "Implemented a proper receive loop checking EndOfMessage for fragmented messages and CloseStatus for disconnect" | |
| - "Used CloseOutputAsync (not CloseAsync) when responding to client-initiated close to avoid deadlock" | |
| - "Implemented a thread-safe connection manager using ConcurrentDictionary for tracking and broadcasting to connected clients" | |
| - "Handled authentication via query string token since browser WebSocket API cannot send custom headers after handshake" | |
| expect_tools: ["bash"] | |
| timeout: 120 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire file is incorrectly wrapped in a ```skill code block. This differs from the established convention where SKILL.md files have frontmatter directly at the top (without a code block wrapper). The opening triple-backtick on line 1 and closing triple-backtick on line 232 should be removed so the frontmatter and content are directly in markdown format, not inside a code block.