Skip to Content
Course content

71: Making HTTP Requests with HttpClient

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 HttpClient instance 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 results array.
  • Use GetFromJsonAsync to fetch and deserialize the data in one step.
  • Handle potential network errors using a try-catch block to ensure the app doesn't crash if the API is offline.
  • Target URL: https://randomuser.me/api/
Rating
0 0

There are no comments for now.

to be the first to leave a comment.