HTTP client in C#: making requests correctly from A to Z

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 a proper HTTP client in C# is important



In modern C# (.NET) development, working with HTTP requests is a basic necessity. Whether it's calling a REST API, downloading data from a web server, or integrating with cloud services — a reliable HTTP client is essential. However, many developers, especially beginners, make typical mistakes: improper connection management, ignoring timeouts, or inefficient response handling. In this article, we will break down creating an HTTP client in C# from the basics to advanced techniques.



We will look at the HttpClient class — the primary tool for working with HTTP in .NET, as well as alternative approaches. You will learn how to properly send GET and POST requests, handle JSON, manage headers, and avoid common performance issues.



Basics: creating and configuring HttpClient



Proper initialization of HttpClient



The HttpClient class is designed for reuse. Creating a new instance for each request is an anti-pattern that can lead to socket exhaustion. Instead, use a single instance for the entire application or dependency injection (DI) via IHttpClientFactory.



// Incorrect: creating for each requestpublic async Task<string> BadRequest(string url){    using (var client = new HttpClient())    {        return await client.GetStringAsync(url);    }}

// Correct: static instancepublic static readonly HttpClient SharedClient = new HttpClient();

// Or via IHttpClientFactory (recommended for ASP.NET Core)services.AddHttpClient("MyClient", client =>{ client.BaseAddress = new Uri("https://api.example.com"); client.DefaultRequestHeaders.Add("Accept", "application/json");});


Configuring timeouts and base address



Timeouts protect the application from hanging when the server is unavailable. HttpClient has its own timeout, but it can be overridden.



HttpClient client = new HttpClient{    BaseAddress = new Uri("https://jsonplaceholder.typicode.com"),    Timeout = TimeSpan.FromSeconds(10) // 10 seconds for the entire request};


It is also useful to set MaxResponseContentBufferSize to limit the size of received data.



Sending requests: GET, POST, PUT, and DELETE



GET request: retrieving data



The simplest way to get data is GetStringAsync or GetAsync with manual handling.



public async Task<string> GetUsersAsync(){    HttpResponseMessage response = await SharedClient.GetAsync("/users");    response.EnsureSuccessStatusCode(); // throws an exception on error    return await response.Content.ReadAsStringAsync();}

// Or even simpler:string json = await SharedClient.GetStringAsync("/users");


POST request: sending data



To send JSON, use PostAsJsonAsync (requires the System.Net.Http.Json package).



var newUser = new { name = "Ivan", email = "ivan@example.com" };HttpResponseMessage response = await SharedClient.PostAsJsonAsync("/users", newUser);if (response.IsSuccessStatusCode){    var createdUser = await response.Content.ReadFromJsonAsync<User>();}


To send a form, use FormUrlEncodedContent.



var formData = new Dictionary<string, string>{    { "username", "admin" },    { "password", "secret" }};var content = new FormUrlEncodedContent(formData);HttpResponseMessage response = await SharedClient.PostAsync("/login", content);


PUT and DELETE requests



Updating and deleting are done similarly:



// PUTvar updateData = new { id = 1, name = "Updated name" };HttpResponseMessage putR                

Blogs

Book Recommendations