Table of contents

How do I handle exceptions thrown by HttpClient (C#) methods?

Exception handling is crucial when working with HttpClient in C# because network operations can fail for various reasons. This guide covers the common exceptions and proper handling strategies.

Common HttpClient Exceptions

1. HttpRequestException

Thrown for network-related errors, DNS failures, and connection issues:

using System;
using System.Net.Http;
using System.Threading.Tasks;

public async Task HandleHttpRequestException()
{
    using var client = new HttpClient();

    try
    {
        var response = await client.GetAsync("https://nonexistent-domain.com");
        var content = await response.Content.ReadAsStringAsync();
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Network error: {ex.Message}");

        // Check inner exception for more details
        if (ex.InnerException != null)
        {
            Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
        }
    }
}

2. TaskCanceledException

Thrown when requests are canceled or timeout:

public async Task HandleTimeouts()
{
    using var client = new HttpClient();
    client.Timeout = TimeSpan.FromSeconds(5); // 5-second timeout

    try
    {
        var response = await client.GetAsync("https://httpbin.org/delay/10");
    }
    catch (TaskCanceledException ex)
    {
        if (ex.CancellationToken.IsCancellationRequested)
        {
            Console.WriteLine("Request was explicitly canceled");
        }
        else
        {
            Console.WriteLine("Request timed out");
        }
    }
}

3. ArgumentException

Thrown for invalid URIs or parameters:

public async Task HandleInvalidUri()
{
    using var client = new HttpClient();

    try
    {
        var response = await client.GetAsync("invalid-uri");
    }
    catch (ArgumentException ex)
    {
        Console.WriteLine($"Invalid URI: {ex.Message}");
    }
    catch (UriFormatException ex)
    {
        Console.WriteLine($"Malformed URI: {ex.Message}");
    }
}

Comprehensive Exception Handling Pattern

Here's a robust exception handling pattern that covers all common scenarios:

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public class HttpClientService
{
    private readonly HttpClient _httpClient;

    public HttpClientService()
    {
        _httpClient = new HttpClient()
        {
            Timeout = TimeSpan.FromSeconds(30)
        };
    }

    public async Task<string> GetDataAsync(string url, CancellationToken cancellationToken = default)
    {
        try
        {
            using var response = await _httpClient.GetAsync(url, cancellationToken);

            // Handle HTTP status codes
            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }

            // Handle specific HTTP status codes
            switch (response.StatusCode)
            {
                case HttpStatusCode.NotFound:
                    throw new InvalidOperationException("Resource not found");
                case HttpStatusCode.Unauthorized:
                    throw new UnauthorizedAccessException("Authentication required");
                case HttpStatusCode.Forbidden:
                    throw new UnauthorizedAccessException("Access forbidden");
                case HttpStatusCode.InternalServerError:
                    throw new InvalidOperationException("Server error occurred");
                default:
                    throw new HttpRequestException($"HTTP error: {response.StatusCode} - {response.ReasonPhrase}");
            }
        }
        catch (HttpRequestException ex)
        {
            // Network connectivity issues, DNS failures, server unreachable
            throw new InvalidOperationException($"Network error occurred: {ex.Message}", ex);
        }
        catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
        {
            // Request timeout
            throw new TimeoutException("Request timed out", ex);
        }
        catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
        {
            // Explicit cancellation
            throw new OperationCanceledException("Request was canceled", ex, cancellationToken);
        }
        catch (ArgumentException ex)
        {
            // Invalid URI or parameters
            throw new ArgumentException($"Invalid request parameters: {ex.Message}", ex);
        }
    }

    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

Using EnsureSuccessStatusCode()

For simpler scenarios, you can use EnsureSuccessStatusCode() to automatically throw exceptions for non-success status codes:

public async Task<string> GetDataWithEnsureSuccess(string url)
{
    using var client = new HttpClient();

    try
    {
        var response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode(); // Throws HttpRequestException for non-success codes

        return await response.Content.ReadAsStringAsync();
    }
    catch (HttpRequestException ex)
    {
        // This will catch both network errors and HTTP error status codes
        Console.WriteLine($"Request failed: {ex.Message}");
        throw;
    }
}

Retry Logic with Exception Handling

Implement retry logic for transient failures:

public async Task<string> GetDataWithRetry(string url, int maxRetries = 3)
{
    using var client = new HttpClient();
    var attempt = 0;

    while (attempt < maxRetries)
    {
        try
        {
            var response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        catch (HttpRequestException ex) when (attempt < maxRetries - 1)
        {
            attempt++;
            Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying...");
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
        }
        catch (TaskCanceledException ex) when (!ex.CancellationToken.IsCancellationRequested && attempt < maxRetries - 1)
        {
            // Retry on timeout
            attempt++;
            Console.WriteLine($"Timeout on attempt {attempt}. Retrying...");
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
        }
    }

    throw new InvalidOperationException($"Failed to get data after {maxRetries} attempts");
}

Best Practices

  1. Always use async/await for HttpClient operations to avoid blocking threads
  2. Reuse HttpClient instances rather than creating new ones for each request
  3. Set appropriate timeouts to prevent hanging requests
  4. Handle specific exceptions rather than using generic catch-all blocks
  5. Implement retry logic for transient failures
  6. Log exceptions with sufficient detail for debugging
  7. Use cancellation tokens for long-running operations
  8. Dispose HttpClient properly or use dependency injection with IHttpClientFactory

Remember that proper exception handling makes your applications more robust and provides better user experience when network issues occur.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon