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
209: HttpClientFactory Pattern
If you've spent any time in C#, you've been conditioned to follow a very specific rule: if a class implements IDisposable, wrap it in a using block. It's the gold standard for preventing memory leaks. But when it comes to HttpClient, that instinct will actually break your application in production.
The "Dispose Everything" Instinct
I see this constantly in code reviews. A developer wants to fetch some data from an external API, so they write something like this:
public async Task<string> GetLatestPriceAsync(string symbol)
{
using (var client = new HttpClient())
{
return await client.GetStringAsync($"https://api.exchangerate.host/latest?base={symbol}");
}
}
On the surface, this looks perfect. It's clean, it's safe, and it disposes of the client as soon as the method finishes. But here is the catch: HttpClient is a bit of a liar. When you dispose of the client, the underlying socket isn't actually closed immediately. It lingers in a state called TIME_WAIT.
If your app is low-traffic, you'll never notice. But if you're building a service that hits an API hundreds of times a minute, you'll run out of available sockets. Your app will start throwing SocketException errors, and you'll be left scratching your head wondering why a "disposed" object is crashing your server. This is what we call socket exhaustion.
Letting the Factory Manage the Handlers
You might think the solution is to just make the HttpClient a static singleton. That fixes the socket issue, but it introduces a new one: DNS changes. If the API you're calling updates its IP address, a singleton client will keep trying to hit the old IP until you restart the entire process. Not ideal for a high-availability system.
This is why we use the IHttpClientFactory pattern. Instead of you managing the lifetime of the client, the factory manages the lifetime of the HttpMessageHandler. It pools these handlers and rotates them out periodically, ensuring you don't exhaust your sockets while still respecting DNS updates. I personally find this to be one of those "invisible" wins in .NET—once it's configured, you just stop worrying about network plumbing.
Typed Clients: Organizing Your API Logic
While you can ask the factory for a generic client, the "pro move" is using a Typed Client. This allows you to encapsulate all the logic for a specific API into its own class, making your code much easier to test and maintain. Let's look at how we'd implement a PriceTrackerService using this pattern.
// 1. Define the typed client
public class PriceTrackerService
{
private readonly HttpClient _httpClient;
public PriceTrackerService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<decimal> GetPriceAsync(string symbol)
{
// The client is already configured via the factory
var response = await _httpClient.GetFromJsonAsync<PriceResponse>($"price/{symbol}");
return response?.Price ?? 0;
}
}
public record PriceResponse(decimal Price);
Now, the magic happens in your Program.cs (or Startup.cs). You tell the dependency injection container how to build this client:
var builder = WebApplication.CreateBuilder(args);
// This registers the PriceTrackerService and tells the factory
// to provide it with a pre-configured HttpClient.
builder.Services.AddHttpClient<PriceTrackerService>(client =>
{
client.BaseAddress = new Uri("https://api.prices.com/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.Timeout = TimeSpan.FromSeconds(5);
});
var app = builder.Build();
Now, whenever you inject PriceTrackerService into a controller or another service, .NET handles the instantiation and the handler pooling behind the scenes. You get the best of both worlds: the convenience of a short-lived object and the efficiency of a long-lived connection.
📋 Practical Task
Refactoring the WeatherMonitor for Socket Efficiency
You have inherited a legacy WeatherMonitor class that is causing intermittent SocketException crashes during peak usage. The current implementation creates a new HttpClient inside a using block for every single request.
Your Task:
- Remove the
using` block from theGetWeatherAsyncmethod. - Convert the
WeatherMonitorinto a Typed Client by accepting anHttpClientthrough its constructor. - Update the
Program.csconfiguration to register theWeatherMonitorusingbuilder.Services.AddHttpClient<WeatherMonitor>(). - Set the
BaseAddresstohttps://api.weather.com/v1/inside theAddHttpClientconfiguration lambda.
Ensure that the WeatherMonitor no longer instantiates the client itself, and that the dependency injection system is responsible for providing the managed client.
There are no comments for now.