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
71: Making HTTP Requests with HttpClient
A few years ago, I was reviewing a PR for a developer who had built a high-traffic telemetry dashboard. Everything looked great on paper, but as soon as they pushed to staging, the server started throwing SocketException errors under load. The culprit? They were wrapping their HttpClient in a using block inside a loop. They thought they were being "clean" by disposing of the object, but in reality, they were exhausting the server's available sockets because the underlying connection doesn't close immediately. It's a rite of passage for C# developers, and it's exactly why we need to talk about how to actually use this class.
Fetching Data with GetAsync
At its core, HttpClient is your gateway to the rest of the web. Whether you're hitting a REST API or downloading a file, you'll mostly be spending your time with GetAsync. The key here is that almost every operation in HttpClient is asynchronous. If you try to block these calls with .Result or .Wait(), you're asking for a deadlock, especially in UI or legacy ASP.NET applications.
Let's look at a real example. Imagine we're building a tool to track cryptocurrency prices using a public API. We don't just want the raw response; we want to ensure the request actually succeeded before we try to read the body.
using System.Net.Http;
// I'm using a static instance here to avoid the socket exhaustion I mentioned earlier
static readonly HttpClient client = new HttpClient();
async Task GetCryptoPrice()
{
string url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
HttpResponseMessage response = await client.GetAsync(url);
// This throws an exception if the status code isn't 2xx
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Raw JSON: {responseBody}");
}
Turning JSON Strings into C# Objects
Rarely do we actually want a giant string of JSON. We want strongly typed objects we can actually use in our logic. In the modern .NET ecosystem, System.Text.Json is the gold standard. Instead of reading the content as a string and then parsing it, you can stream the data directly from the network into your object. This is significantly more memory-efficient because it avoids creating a massive string in the heap.
First, you'll need a class that matches the JSON structure. If the API returns {"usd": 65000}, your class should look like this:
public class PriceResponse
{
public decimal usd { get; set; }
}
Now, you can use GetFromJsonAsync, which is a convenient extension method that combines the request and the deserialization into one line. I personally prefer this for 90% of my API calls because it removes the boilerplate of handling the response message manually.
using System.Net.Http.Json;
async Task PrintBitcoinPrice()
{
string url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
// Note: The API returns a nested object {"bitcoin": {"usd": 65000}}
// So we'd actually need a wrapper class or a Dictionary
var data = await client.GetFromJsonAsync<Dictionary<string, PriceResponse>>(url);
if (data != null && data.ContainsKey("bitcoin"))
{
Console.WriteLine($"The current price of Bitcoin is ${data["bitcoin"].usd}");
}
}
The Socket Exhaustion Trap
I mentioned this at the start, but it bears repeating: Do not instantiate a new HttpClient for every request.
Even though HttpClient implements IDisposable, disposing of it doesn't immediately close the socket; it stays in a TIME_WAIT state. If your app makes hundreds of requests per second, you'll run out of available ports, and your app will crash. In a simple console app, a static or singleton instance is fine. In a professional ASP.NET Core application, you should use IHttpClientFactory, which manages the lifetime of the underlying handlers for you. It's a bit more setup, but it's the only way to ensure your app remains stable under heavy load.
📋 Practical Task
Exercise: Building a Random User Profile Fetcher
Your task is to create a small console application that fetches a random user's profile from the randomuser.me API and displays their full name and email address to the console.
Requirements:
- Use a single, static
HttpClientinstance to avoid socket exhaustion. - Create the necessary C# classes (POCOs) to map the JSON response. Note that the API returns the user data inside a
resultsarray. - Use
GetFromJsonAsyncto fetch and deserialize the data in one step. - Handle potential network errors using a
try-catchblock to ensure the app doesn't crash if the API is offline. - Target URL:
https://randomuser.me/api/
There are no comments for now.