HTTP client in C#: advanced techniques for working with requests

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

HTTP client in C#: making requests correctly — advanced level



In modern .NET development, working with HTTP requests goes far beyond simply calling WebClient or basic HttpClient. At an advanced level, you need to consider connection pool management, proper error handling, timeouts, retry policies, and asynchronous execution. In this article, we will cover best practices for working with an HTTP client in C# that will help you build reliable and high-performance applications.



1. Proper work with HttpClient and connection pool



One of the most common mistakes is creating a new instance of HttpClient for each request. This leads to socket exhaustion because each instance keeps an open TCP connection in the TIME_WAIT state.



Incorrect:

public async Task<string> GetDataAsync(string url){    using (var client = new HttpClient())    {        return await client.GetStringAsync(url);    }}


Correct: use a single static or singleton instance of HttpClient.



public class HttpService{    private static readonly HttpClient _client = new HttpClient();

public async Task<string> GetDataAsync(string url) { return await _client.GetStringAsync(url); }}


For finer control, use IHttpClientFactory from the Microsoft.Extensions.Http package. The factory manages the pool and automatically recovers connections during DNS failures.



// In Startup.cs or Program.csservices.AddHttpClient("MyClient", client =>{    client.BaseAddress = new Uri("https://api.example.com");    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");});

// In a servicepublic class MyService{ private readonly IHttpClientFactory _httpClientFactory;

public MyService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; }

public async Task<string> GetDataAsync() { var client = _httpClientFactory.CreateClient("MyClient"); return await client.GetStringAsync("/data"); }}


2. Timeouts and request cancellation



Without timeouts, your client can hang indefinitely. HttpClient has two mechanisms:



  • Timeout — a general timeout for the entire request.
  • CancellationToken — for canceling a request externally (e.g., the user closed the application).


Set Timeout reasonably. For most APIs, 10–30 seconds is suitable.



var client = new HttpClient{    Timeout = TimeSpan.FromSeconds(15)};

// Example with CancellationTokenSourceusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));try{ var response = await client.GetAsync("https://slow-api.example.com", cts.Token);}catch (TaskCanceledException){ Console.WriteLine("Request cancelled due to timeout.");}


Important: Timeout and CancellationToken work together. If the CancellationToken fires earlier, the request is cancelled faster.



3. Error handling and retry policy



Network failures and temporary server errors are common. Instead of immediately failing, implement a retry policy with exponential backoff. The Polly library is excellent for this.



using Polly;using Polly.Extensions.Http;

// Define the retry policyvar retryPolicy = HttpPolicyExtensions .HandleTransientHttpError() // 5xx, 408 .Or<HttpRequestException>() .WaitAndRetryAsync(3, retryAttempt => Tim

Blogs

Book Recommendations