Introduction
<span><span>HttpClient</span></span> is a modern <span><span>API</span></span> in <span><span>.NET</span></span> used for sending <span><span>HTTP</span></span> requests and receiving <span><span>HTTP</span></span> responses, replacing the outdated <span><span>WebClient</span></span> and <span><span>HttpWebRequest</span></span> classes.
<span><span>HttpClient</span></span> is provided in <span><span>.NET Framework 4.5</span></span> + and <span><span>.NET Core/.NET 5+</span></span>, based on a message handling pipeline (<span><span>message handler pipeline</span></span>), and is a modern <span><span>HTTP</span></span> client library.
Compared to earlier <span><span>HttpWebRequest</span></span>, it is easier to use, supports asynchronous operations, is highly extensible, and uses <span><span>SocketsHttpHandler</span></span> at the lower level in <span><span>.NET Core</span></span>, significantly improving performance and configurability.
Underlying Architecture
-
<span><span>HttpClient</span></span> - Encapsulates the logic for creating, sending requests, and handling responses, providing methods such as
<span><span>GetAsync, PostAsync, SendAsync</span></span>, etc. -
<span><span>HttpMessageHandler</span></span>pipeline -
By default, it uses
<span><span>SocketsHttpHandler (.NET Core/.NET 5+)</span></span>, and<span><span>HttpClientHandler</span></span>under<span><span>.NET Framework</span></span>. -
Custom middleware (e.g., logging, retry, authentication) can be chained by inheriting from
<span><span>DelegatingHandler</span></span>. -
Connection Pooling and Persistent Connections
<span><span>SocketsHttpHandler</span></span>has built-in connection pooling and<span><span>HTTP/2</span></span>support, reusing<span><span>TCP</span></span>connections to the same target host, reducing handshake and reconstruction costs.
Core Features of HttpClient
| Feature | Description | Advantages |
|---|---|---|
| Asynchronous Support | All methods have asynchronous versions | Avoids blocking threads and improves concurrency |
| Connection Pooling | Automatically manages HTTP connections | Reduces TCP connection overhead |
| Timeout Control | Configurable request timeout | Prevents long blocking |
| Content Handling | Built-in various content handlers | Simplifies JSON/XML handling |
| Cancellation Support | Supports CancellationToken | Gracefully terminates long-running requests |
| Extensibility | Can be extended via DelegatingHandler | Implements middleware such as logging, retry, etc. |
Common Methods
Using Singleton
public static class HttpClientSingleton
{
public static readonly HttpClient Instance = new HttpClient();
}
// Usage
await HttpClientSingleton.Instance.GetAsync(url);
-
Globally unique, avoids repeated creation of
<span><span>handler</span></span>, reuses connection pool. -
Note: Changes to global properties such as expiration (
<span><span>Timeout</span></span>) and default headers (<span><span>DefaultRequestHeaders</span></span>) will affect all calls.
Using <span><span>HttpClientFactory</span></span> Factory
services.AddHttpClient("GitHub", client =>
{
client.BaseAddress = new Uri("https://api.github.com/");
client.DefaultRequestHeaders.Add("User-Agent", "MyApp");
})
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(2)));
// Usage
var client = _httpClientFactory.CreateClient("GitHub");
-
The framework manages the lifecycle and stable reuse of
<span><span>SocketsHttpHandler</span></span>. -
Supports named clients, typed clients, configuration pipelines, and adding
<span><span>Polly</span></span>resilience policies.
Typed Clients
public class GitHubService {
private readonly HttpClient _client;
public GitHubService(HttpClient client) => _client = client;
// Encapsulate API methods
}
// Registration
services.AddHttpClient(client => { /* Configuration */ });
<span><span>GET</span></span> Request
var client = HttpClientSingleton.Instance;
var response = await client.GetAsync("https://api.example.com/items/1");
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
<span><span>POST</span></span> Request
var content = new StringContent(
JsonSerializer.Serialize(new { Name = "Test" }),
Encoding.UTF8, "application/json");
var postResp = await client.PostAsync("https://api.example.com/items", content);
postResp.EnsureSuccessStatusCode();
<span><span>SendAsync</span></span> Custom Request
var request = new HttpRequestMessage(HttpMethod.Put, $"items/{{id}}")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
request.Headers.Add("X-Custom", "Value");
using var resp = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
resp.EnsureSuccessStatusCode();
Streaming Upload/Download
// Download
using var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
using var stream = await resp.Content.ReadAsStreamAsync();
// Write stream to file ...
// Upload
using var fs = File.OpenRead(localFilePath);
var streamContent = new StreamContent(fs);
var uploadResp = await client.PostAsync(uploadUrl, streamContent);
DelegatingHandler Pipeline
public class LoggingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
Console.WriteLine($"Request: {request}");
var resp = await base.SendAsync(request, ct);
Console.WriteLine($"Response: {resp}");
return resp;
}
}
// Registration (HttpClientFactory)
services.AddHttpClient("WithLogging")
.AddHttpMessageHandler<logginghandler>();
</logginghandler>
Polly Resilience Policies
services.AddHttpClient("GitHub")
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, retry => TimeSpan.FromSeconds(1)));
-
<span><span>AddTransientHttpErrorPolicy</span></span>captures<span><span>5xx, 408, HttpRequestException</span></span>, and other<span><span>transient</span></span>errors; -
Can compose circuit breakers, timeouts, and isolation policies.
Authentication and Configuration
<span><span>Bearer Token</span></span>
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
<span><span>Client Certificates</span></span>
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(new X509Certificate2("cert.pfx", "pwd"));
var client = new HttpClient(handler);
<span><span>Proxy</span></span>
handler.Proxy = new WebProxy("http://127.0.0.1:8888");
handler.UseProxy = true;
Setting Timeouts and Request Headers
httpClient.Timeout = TimeSpan.FromSeconds(30); // Set global timeout
httpClient.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "your-token");
Handling Different Types of Responses
// Get JSON response
var jsonResponse = await httpClient.GetStringAsync("https://api.example.com/json");
// Get binary response (e.g., file download)
var bytes = await httpClient.GetByteArrayAsync("https://example.com/file.pdf");
// Get stream response (large file download, avoid memory overflow)
await using var stream = await httpClient.GetStreamAsync("https://example.com/largefile.zip");
await using var fileStream = File.Create("largefile.zip");
await stream.CopyToAsync(fileStream);
Sending Requests with Parameters
// GET request with query parameters
var queryParams = new Dictionary<string, string>
{
{ "page", "1" },
{ "size", "20" }
};
var uri = new UriBuilder("https://api.example.com/data")
{
Query = new FormUrlEncodedContent(queryParams).ReadAsStringAsync().Result
};
var response = await httpClient.GetAsync(uri.Uri);
// POST request with form data
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", "test"),
new KeyValuePair<string, string>("password", "pass")
});
await httpClient.PostAsync("https://example.com/login", formContent);
Handling HTTP Errors
try
{
var response = await httpClient.GetAsync("https://api.example.com/resource");
response.EnsureSuccessStatusCode(); // Non-200 status codes will throw an exception
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP Error: {ex.StatusCode}");
Console.WriteLine($"Message: {ex.Message}");
}
Handling Gzip/Deflate Compressed Responses
var httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var httpClient = new HttpClient(httpClientHandler);
Cancellation Operations
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // 10 seconds timeout
try {
var response = await client.GetAsync("https://slow-api.com", cts.Token);
} catch (TaskCanceledException) {
// Handle timeout
}
Please open in WeChat client