C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
73: Working with WebSockets in .NET
I've spent a lot of time building dashboards, and there's a specific kind of frustration that comes with "polling." You know the drill: the client asks the server every two seconds, "Do you have new data yet?" Most of the time, the server says "No," and you've just wasted a request and a bit of battery life. For a live system health monitor—where we want to see CPU spikes the moment they happen—polling is just clunky.
The frustration with Request-Response
I started by trying to build a simple resource monitor using a standard API endpoint. I had the client hitting /api/health every second. It worked, but the network tab in my browser looked like a strobe light. I wanted a persistent pipe where the server could just push a message whenever the CPU hit 90%. That's where WebSockets come in. Unlike HTTP, which is a one-way street (client asks, server answers), a WebSocket is a two-way tunnel that stays open.
First, I had to tell the .NET middleware to actually allow these kinds of connections. I added this to my Program.cs:
app.UseWebSockets();
Simple enough. But then I hit my first wall. I tried to treat the WebSocket connection like a regular controller action, expecting it to just "return" a value. I quickly realized that a WebSocket isn't a request; it's a handover. You aren't returning a result; you're hijacking the connection to keep it open.
Opening the Pipe
I decided to write a small piece of middleware to handle the "handshake." I wanted to check if the incoming request was actually asking for a WebSocket. If it was, I'd accept it and enter a loop. Here is how I tackled the initial connection logic:
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
// I'm now "inside" the tunnel.
// If I exit this method, the connection closes.
await HandleMonitorConnection(webSocket);
}
else
{
await next();
}
});
The AcceptWebSocketAsync call is the magic moment. It upgrades the HTTP connection to a WebSocket connection. But here's the catch: if HandleMonitorConnection returns, the connection dies. To keep the monitor alive, I have to keep the code execution trapped in a loop as long as the socket is open.
Wrestling with Byte Buffers
This is where things got messy. WebSockets don't speak "Strings" or "JSON" natively; they speak bytes. I tried to just send a string, but the SendAsync method demanded an ArraySegment<byte>. It felt like I was stepping back into 1995.
I wrote a helper to push my CPU metrics. I noticed that if I didn't manage the buffer correctly, I was allocating memory like crazy every time I sent a packet. I shifted to using Encoding.UTF8.GetBytes and wrapping it in an ArraySegment. Here's the logic I settled on for pushing data:
async Task HandleMonitorConnection(WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
// Simulating a CPU metric read
string metric = $"CPU: {Random.Shared.Next(10, 90)}% | Mem: {Random.Shared.Next(2000, 8000)}MB";
var bytes = Encoding.UTF8.GetBytes(metric);
await webSocket.SendAsync(
new ArraySegment<byte>(bytes),
WebSocketMessageType.Text,
true,
CancellationToken.None);
await Task.Delay(1000); // Push every second
}
}
One thing I noticed during testing: if I closed the browser tab, the server would throw an exception on the next SendAsync call. The server doesn't magically know the client is gone until it tries to send something and fails. I had to wrap the loop in a try-catch block specifically for WebSocketException to ensure the server cleaned up the resources gracefully without crashing the whole process.
Handling the Disconnect
I realized that just catching exceptions isn't "clean." A proper WebSocket implementation should handle the close handshake. The client sends a "Close" frame, and the server should acknowledge it. I adjusted my loop to check for ReceiveAsync. Even if the server is the one pushing data, it still needs to "listen" for the client's request to close the connection.
By adding a ReceiveAsync call in a separate Task or as part of the loop, I could detect when result.MessageType == WebSocketMessageType.Close. Only then did I call webSocket.CloseAsync. This prevents those annoying "Connection reset by peer" errors in the logs and makes the teardown smooth.
📋 Practical Task
Build a Real-Time System Resource Tracker
Your goal is to implement a WebSocket handler that mimics a system monitor. Instead of just random numbers, you will create a service that pushes the actual current memory usage of the application to a connected client.
- The Setup: Create a .NET Minimal API project and enable WebSockets.
- The Logic: Create a middleware or a specific route that accepts a WebSocket connection.
- The Data: Use
GC.GetTotalMemory(false)to get the current memory usage of the app. - The Loop: Every 500ms, send the memory usage (formatted as a string) to the client.
- The Safety: Implement a try-catch block to handle
WebSocketExceptionso that if the client disconnects abruptly, the server logs "Client disconnected" instead of crashing. - The Handshake: Ensure you use
webSocket.CloseAsyncwhen the client sends a close frame.
Test your implementation using a browser-based WebSocket client (like a Chrome extension or a simple JS snippet in the console) to verify that the memory numbers are updating in real-time.
There are no comments for now.