HTTP client in C#: making requests correctly from A to Z
\n\nIn modern development on the .NET platform, working with HTTP requests is one of the key tasks. Whether it's integration with an external REST API, sending data to a server, or downloading web pages — proper use of HttpClient in C# is critical for application performance and stability. In this article, we will cover all aspects of working with an HTTP client in C#: from basic examples to advanced techniques, error handling, and best practices.
1. Basics of working with HttpClient in C#
\n\nHttpClient is the main class in .NET for sending HTTP requests and receiving responses. It is located in the System.Net.Http namespace and is available starting from .NET Framework 4.5 and all versions of .NET Core/.NET 5+.
Important rule: HttpClient 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 static or singleton instance.
Basic example of a GET request:
\n\nusing System;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nclass Program\n{\n private static readonly HttpClient client = new HttpClient();\n\n static async Task Main(string[] args)\n {\n try\n {\n HttpResponseMessage response = await client.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n string responseBody = await response.Content.ReadAsStringAsync();\n Console.WriteLine(responseBody);\n }\n catch (HttpRequestException e)\n {\n Console.WriteLine($"Request error: {e.Message}");\n }\n }\n}\n\nIn this example, we create a static HttpClient, send a GET request, check the success of the response using EnsureSuccessStatusCode(), and read the response body as a string.
2. Types of HTTP requests and working with headers
\n\nIn addition to GET, HttpClient supports all major HTTP methods: POST, PUT, DELETE, PATCH, and others. Let's look at sending a POST request with JSON data — this is the most common scenario when working with REST APIs.
Example of a POST request with JSON:
\n\nusing System;\nusing System.Net.Http;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\n\nclass Program\n{\n private static readonly HttpClient client = new HttpClient();\n\n static async Task Main(string[] args)\n {\n var data = new { Name = "John", Age = 30 };\n string json = JsonSerializer.Serialize(data);\n var content = new StringContent(json, Encoding.UTF8, "application/json");\n\n try\n {\n HttpResponseMessage response = await client.PostAsync("https://api.example.com/users", content);\n response.EnsureSuccessStatusCode();\n string result = await response.Content.ReadAsStringAsync();\n Console.WriteLine($"User created: {result}");\n }\n catch (HttpRequestException e)\n {\n Console.WriteLine($"Error: {e.Message}");\n }\n }\n}\n\nWorking with headers: APIs often require passing authentication tokens or other headers. They can be added in two ways:
\n\n- \n
- Globally — via the
DefaultRequestHeadersproperty (for all client requests). \n - Locally — via the
Headerscollection in theHttpRequestMessageobject (for a specific request). \n
Example of adding an Authorization header:
\n\n// Globally\nclient.DefaultRequestHeaders.Authorization = \n new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "your-token");\n\n// Locally\nvar request = new HttpR