HTTP Client in C#: Making Requests Correctly and Efficiently

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

Introduction: Why You Need a Proper HTTP Client in C#

In modern development, almost no application can function without interacting with external services. Whether it's sending data to a server, fetching currency exchange rates, or working with a cloud API — all of this requires a proper implementation of HTTP requests. In the C# (.NET) ecosystem, the main tool for this task is the HttpClient class. However, many developers, especially beginners, make typical mistakes: creating a new HttpClient instance for each request, forgetting to handle exceptions, or working incorrectly with asynchrony.

In this article, we will break down how to properly organize an HTTP client in C#, look at sending GET and POST requests, working with headers and request body, as well as best practices for error handling. After reading, you will be able to write reliable and performant code for interacting with any REST API.



Basics of Working with HttpClient in C#

The HttpClient class is part of the System.Net.Http namespace and is available in all modern versions of .NET (Core, 5, 6, 7, 8 and above). Its main advantage is support for asynchronous operations and automatic connection pool management.



Properly Creating an HttpClient

The most important rule: do not create a new HttpClient for each request. This can lead to socket exhaustion. Instead, use a single instance for the entire lifetime of the application or use IHttpClientFactory.

// ❌ Bad: new HttpClient for each requestusing (var client = new HttpClient()){    var response = await client.GetAsync("https://api.example.com");}

// ✅ Good: one HttpClient for all requestspublic class MyHttpService{ private static readonly HttpClient _client = new HttpClient(); public async Task<string> GetDataAsync(string url) { return await _client.GetStringAsync(url); }}


Basic Configuration

When initializing HttpClient, you can set a base address, timeouts, and default headers:

public class ApiClient{    private readonly HttpClient _httpClient;        public ApiClient()    {        _httpClient = new HttpClient        {            BaseAddress = new Uri("https://api.pythonlib.ru/"),            Timeout = TimeSpan.FromSeconds(10)        };                // Headers that will be sent with every request        _httpClient.DefaultRequestHeaders.Add("Accept", "application/json");        _httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");    }}


Sending GET and POST Requests

Let's look at the two most common types of HTTP requests: retrieving data (GET) and sending data (POST).



GET Request: Retrieving Data

A GET request is used to read data from the server. It should not change the state of the resource and does not contain a body.

public async Task<string> GetUserAsync(int userId){    try    {        var response = await _httpClient.GetAsync($"/api/users/{userId}");        response.EnsureSuccessStatusCode(); // Will throw an exception on error        return await response.Content.ReadAsStringAsync();    }    catch (HttpRequestException ex)    {        Console.WriteLine($"Request error: {ex.Message}");        throw;    }}

For simple cases, you can use the shorthand form:

string json = await _httpClient.GetStringAsync("/api/users/42");


POST Request: Sending Data

A POST request is used to create new resources. Data is sent in the request body, most often in JSON format.

public async Task<User> CreateUserAsync(User newUser){    // Serialize the object to JSON    var json = JsonSerializer.Serialize(newUser);                

Blogs

Book Recommendations