HttpClient

Standard HTTP client built into .NET framework. Supports dependency injection and lifecycle management through HttpClientFactory. Provides async communication, HTTP/2 support, automatic compression, and detailed timeout configuration. Recommended standard implementation for enterprise applications.

HTTP ClientC#.NETAsyncIHttpClientFactory

GitHub Overview

dotnet/runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

Stars17,028
Watchers458
Forks5,203
Created:September 24, 2019
Language:C#
License:MIT License

Topics

dotnethacktoberfesthelp-wanted

Star History

dotnet/runtime Star History
Data as of: 10/22/2025, 10:03 AM

Library

HttpClient

Overview

HttpClient is the standard HTTP client library for .NET. Available from .NET Framework 4.5 onwards and .NET Core/.NET 5+, it provides full support for asynchronous programming (async/await) and achieves high performance and resource efficiency through integration with IHttpClientFactory. In 2025, it remains the most recommended HTTP client solution for .NET developers.

Details

HttpClient was developed as a successor to WebRequest from the early versions of .NET Framework, addressing the needs of modern HTTP communication. Particularly from .NET Core/.NET 5 onwards, integration with IHttpClientFactory has resolved socket exhaustion and DNS change issues, making it safe for use in enterprise-level applications. Integration with System.Text.Json and coordination with Polly enable robust HTTP communication.

Key Features

  • Asynchronous Processing: Full support for async/await patterns
  • IHttpClientFactory: Dependency injection and lifecycle management
  • High Performance: Connection pooling and socket reuse
  • JSON Integration: Efficient JSON processing with System.Text.Json
  • Authentication Support: Bearer Token, JWT, and custom authentication support
  • Error Handling: Comprehensive exception handling capabilities
  • HTTP/2 Support: Latest protocol support
  • Retry Policies: Integration with Polly library
  • Timeout Control: Detailed timeout configuration
  • Header Management: Custom headers and user agent configuration

Pros and Cons

Pros

  • .NET standard library with no additional dependencies required
  • Optimized resource management through IHttpClientFactory
  • Excellent asynchronous processing with async/await support
  • Excellent integration with dependency injection (DI)
  • Reliability and continuous updates through Microsoft official support
  • High-speed JSON processing through System.Text.Json integration

Cons

  • Not available in older versions (.NET Framework 4.x and earlier)
  • Requires some boilerplate code even for basic usage
  • External libraries needed for advanced features (retry, circuit breaker, etc.)
  • Synchronous methods are deprecated (to avoid deadlocks)
  • Mocking for unit tests is somewhat complex

Reference Pages

Code Examples

Basic Setup (IHttpClientFactory)

// Program.cs (.NET 6+)
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

// Register IHttpClientFactory
builder.Services.AddHttpClient();

// Register named HttpClient
builder.Services.AddHttpClient("ApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// Register typed HttpClient
builder.Services.AddHttpClient<ApiService>();

var app = builder.Build();

// Service class example
public class ApiService
{
    private readonly HttpClient _httpClient;
    
    public ApiService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _httpClient.BaseAddress = new Uri("https://api.example.com/");
    }
}

Basic Requests

public class HttpClientService
{
    private readonly HttpClient _httpClient;
    
    public HttpClientService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    // GET request
    public async Task<string> GetDataAsync()
    {
        try
        {
            var response = await _httpClient.GetAsync("https://api.example.com/data");
            response.EnsureSuccessStatusCode();
            
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"Request error: {ex.Message}");
            throw;
        }
    }
    
    // POST request
    public async Task<string> PostDataAsync(object data)
    {
        var json = JsonSerializer.Serialize(data);
        var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
        
        var response = await _httpClient.PostAsync("https://api.example.com/users", content);
        response.EnsureSuccessStatusCode();
        
        return await response.Content.ReadAsStringAsync();
    }
    
    // PUT request
    public async Task<bool> UpdateDataAsync(int id, object data)
    {
        var json = JsonSerializer.Serialize(data);
        var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
        
        var response = await _httpClient.PutAsync($"https://api.example.com/users/{id}", content);
        return response.IsSuccessStatusCode;
    }
    
    // DELETE request
    public async Task<bool> DeleteDataAsync(int id)
    {
        var response = await _httpClient.DeleteAsync($"https://api.example.com/users/{id}");
        return response.IsSuccessStatusCode;
    }
}

Authentication Processing

public class AuthenticatedHttpService
{
    private readonly HttpClient _httpClient;
    
    public AuthenticatedHttpService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    
    // Bearer Token authentication
    public void SetBearerToken(string token)
    {
        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
    }
    
    // Basic authentication
    public void SetBasicAuth(string username, string password)
    {
        var credentials = Convert.ToBase64String(
            System.Text.Encoding.ASCII.GetBytes($"{username}:{password}"));
        
        _httpClient.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
    }
    
    // JWT Token acquisition and setting
    public async Task<string> LoginAndGetTokenAsync(string username, string password)
    {
        var loginData = new { Username = username, Password = password };
        var json = JsonSerializer.Serialize(loginData);
        var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
        
        var response = await _httpClient.PostAsync("/auth/login", content);
        response.EnsureSuccessStatusCode();
        
        var responseContent = await response.Content.ReadAsStringAsync();
        var tokenResponse = JsonSerializer.Deserialize<TokenResponse>(responseContent);
        
        // Set token in header
        SetBearerToken(tokenResponse.AccessToken);
        
        return tokenResponse.AccessToken;
    }
    
    // Custom authentication header
    public async Task<string> GetWithCustomAuthAsync(string apiKey)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, "/api/secure-data");
        request.Headers.Add("X-API-Key", apiKey);
        request.Headers.Add("X-Client-Version", "1.0");
        
        var response = await _httpClient.SendAsync(request);
        response.EnsureSuccessStatusCode();
        
        return await response.Content.ReadAsStringAsync();
    }
}

public class TokenResponse
{
    public string AccessToken { get; set; }
    public string RefreshToken { get; set; }
    public int ExpiresIn { get; set; }
}

JSON Processing (System.Text.Json Integration)

public class JsonHttpService
{
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    
    public JsonHttpService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true,
            PropertyNameCaseInsensitive = true
        };
    }
    
    // Send object
    public async Task<TResponse> PostJsonAsync<TRequest, TResponse>(string endpoint, TRequest data)
    {
        var json = JsonSerializer.Serialize(data, _jsonOptions);
        var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
        
        var response = await _httpClient.PostAsync(endpoint, content);
        response.EnsureSuccessStatusCode();
        
        var responseJson = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<TResponse>(responseJson, _jsonOptions);
    }
    
    // Get JSON response
    public async Task<T> GetJsonAsync<T>(string endpoint)
    {
        var response = await _httpClient.GetAsync(endpoint);
        response.EnsureSuccessStatusCode();
        
        var json = await response.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<T>(json, _jsonOptions);
    }
    
    // Streaming JSON processing (for large responses)
    public async Task<T> GetJsonStreamAsync<T>(string endpoint)
    {
        var response = await _httpClient.GetAsync(endpoint);
        response.EnsureSuccessStatusCode();
        
        using var stream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync<T>(stream, _jsonOptions);
    }
    
    // Practical example
    public async Task<User> CreateUserAsync(CreateUserRequest request)
    {
        return await PostJsonAsync<CreateUserRequest, User>("/api/users", request);
    }
    
    public async Task<List<User>> GetUsersAsync()
    {
        return await GetJsonAsync<List<User>>("/api/users");
    }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class CreateUserRequest
{
    public string Name { get; set; }
    public string Email { get; set; }
}

Error Handling

public class RobustHttpService
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<RobustHttpService> _logger;
    
    public RobustHttpService(HttpClient httpClient, ILogger<RobustHttpService> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }
    
    public async Task<ApiResult<T>> SafeGetAsync<T>(string endpoint)
    {
        try
        {
            using var response = await _httpClient.GetAsync(endpoint);
            
            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                var data = JsonSerializer.Deserialize<T>(json);
                
                return ApiResult<T>.Success(data);
            }
            
            // Process by HTTP status code
            return response.StatusCode switch
            {
                HttpStatusCode.NotFound => ApiResult<T>.Error("Resource not found"),
                HttpStatusCode.Unauthorized => ApiResult<T>.Error("Authentication required"),
                HttpStatusCode.Forbidden => ApiResult<T>.Error("Access denied"),
                HttpStatusCode.BadRequest => ApiResult<T>.Error("Invalid request"),
                HttpStatusCode.InternalServerError => ApiResult<T>.Error("Server error occurred"),
                _ => ApiResult<T>.Error($"Unexpected error: {response.StatusCode}")
            };
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "HTTP request failed for endpoint: {Endpoint}", endpoint);
            return ApiResult<T>.Error($"Network error: {ex.Message}");
        }
        catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
        {
            _logger.LogWarning("Request timeout for endpoint: {Endpoint}", endpoint);
            return ApiResult<T>.Error("Request timed out");
        }
        catch (TaskCanceledException ex)
        {
            _logger.LogWarning("Request cancelled for endpoint: {Endpoint}", endpoint);
            return ApiResult<T>.Error("Request was cancelled");
        }
        catch (JsonException ex)
        {
            _logger.LogError(ex, "JSON deserialization failed for endpoint: {Endpoint}", endpoint);
            return ApiResult<T>.Error("Failed to parse response");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error for endpoint: {Endpoint}", endpoint);
            return ApiResult<T>.Error("An unexpected error occurred");
        }
    }
    
    // Request with retry processing
    public async Task<T> GetWithRetryAsync<T>(string endpoint, int maxRetries = 3)
    {
        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                var response = await _httpClient.GetAsync(endpoint);
                response.EnsureSuccessStatusCode();
                
                var json = await response.Content.ReadAsStringAsync();
                return JsonSerializer.Deserialize<T>(json);
            }
            catch (HttpRequestException ex) when (attempt < maxRetries)
            {
                _logger.LogWarning("Attempt {Attempt} failed, retrying... Error: {Error}", 
                    attempt, ex.Message);
                
                await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
            }
        }
        
        throw new Exception($"Failed after {maxRetries} attempts");
    }
}

public class ApiResult<T>
{
    public bool IsSuccess { get; set; }
    public T Data { get; set; }
    public string ErrorMessage { get; set; }
    
    public static ApiResult<T> Success(T data) => new() { IsSuccess = true, Data = data };
    public static ApiResult<T> Error(string message) => new() { IsSuccess = false, ErrorMessage = message };
}

Asynchronous Processing and Performance Optimization

public class PerformantHttpService
{
    private readonly HttpClient _httpClient;
    private readonly SemaphoreSlim _semaphore;
    
    public PerformantHttpService(HttpClient httpClient)
    {
        _httpClient = httpClient;
        _semaphore = new SemaphoreSlim(10, 10); // Limit concurrent execution
    }
    
    // Concurrent fetching of multiple URLs
    public async Task<Dictionary<string, string>> GetMultipleUrlsAsync(IEnumerable<string> urls)
    {
        var tasks = urls.Select(async url =>
        {
            await _semaphore.WaitAsync();
            try
            {
                var response = await _httpClient.GetStringAsync(url);
                return new KeyValuePair<string, string>(url, response);
            }
            finally
            {
                _semaphore.Release();
            }
        });
        
        var results = await Task.WhenAll(tasks);
        return results.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
    }
    
    // Using CancellationToken
    public async Task<string> GetWithCancellationAsync(string endpoint, CancellationToken cancellationToken = default)
    {
        using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
            cancellationToken, timeoutCts.Token);
        
        try
        {
            var response = await _httpClient.GetAsync(endpoint, combinedCts.Token);
            response.EnsureSuccessStatusCode();
            
            return await response.Content.ReadAsStringAsync();
        }
        catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
        {
            throw new TimeoutException("Request timed out after 30 seconds");
        }
    }
    
    // Streaming download
    public async Task DownloadLargeFileAsync(string url, string filePath, 
        IProgress<long> progress = null, CancellationToken cancellationToken = default)
    {
        using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        response.EnsureSuccessStatusCode();
        
        var contentLength = response.Content.Headers.ContentLength;
        
        using var stream = await response.Content.ReadAsStreamAsync();
        using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
        
        var buffer = new byte[8192];
        long totalBytesRead = 0;
        int bytesRead;
        
        while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
        {
            await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
            totalBytesRead += bytesRead;
            
            progress?.Report(totalBytesRead);
        }
    }
    
    // Batch processing
    public async Task<List<T>> ProcessBatchAsync<T>(IEnumerable<string> endpoints, int batchSize = 5)
    {
        var results = new List<T>();
        var batches = endpoints.Chunk(batchSize);
        
        foreach (var batch in batches)
        {
            var batchTasks = batch.Select(async endpoint =>
            {
                var response = await _httpClient.GetStringAsync(endpoint);
                return JsonSerializer.Deserialize<T>(response);
            });
            
            var batchResults = await Task.WhenAll(batchTasks);
            results.AddRange(batchResults);
            
            // Wait between batches (rate limiting protection)
            await Task.Delay(100);
        }
        
        return results;
    }
    
    // Concurrent requests with HTTP/2 support
    public async Task<List<ApiResponse>> GetConcurrentHttp2Async(List<string> endpoints)
    {
        // HTTP/2 can process multiple requests concurrently on the same connection
        var tasks = endpoints.Select(async endpoint =>
        {
            try
            {
                var response = await _httpClient.GetAsync(endpoint);
                return new ApiResponse
                {
                    Endpoint = endpoint,
                    StatusCode = response.StatusCode,
                    Content = await response.Content.ReadAsStringAsync(),
                    Headers = response.Headers.ToDictionary(h => h.Key, h => h.Value.FirstOrDefault())
                };
            }
            catch (Exception ex)
            {
                return new ApiResponse
                {
                    Endpoint = endpoint,
                    Error = ex.Message
                };
            }
        });
        
        return (await Task.WhenAll(tasks)).ToList();
    }
}

public class ApiResponse
{
    public string Endpoint { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public string Content { get; set; }
    public Dictionary<string, string> Headers { get; set; }
    public string Error { get; set; }
}

// Extension method (Chunk available in .NET 6+, for earlier versions)
public static class EnumerableExtensions
{
    public static IEnumerable<T[]> Chunk<T>(this IEnumerable<T> source, int size)
    {
        var chunk = new List<T>(size);
        foreach (var item in source)
        {
            chunk.Add(item);
            if (chunk.Count == size)
            {
                yield return chunk.ToArray();
                chunk.Clear();
            }
        }
        if (chunk.Count > 0)
            yield return chunk.ToArray();
    }
}