C#.NET HttpClient Usage Tutorial

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

Leave a Comment