Can I use HttpClient (C#) to download files?

Yes, HttpClient in C# is excellent for downloading files efficiently. It provides robust methods for making HTTP requests and handling file downloads, with support for streaming large files without consuming excessive memory.

Basic File Download

Here's a straightforward example of downloading a file:

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

public class FileDownloader
{
    private static readonly HttpClient httpClient = new HttpClient();

    public static async Task DownloadFileAsync(string fileUrl, string destinationPath)
    {
        try
        {
            using var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
            response.EnsureSuccessStatusCode();

            await using var contentStream = await response.Content.ReadAsStreamAsync();
            await using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None);

            await contentStream.CopyToAsync(fileStream);
            Console.WriteLine($"Downloaded: {destinationPath}");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine($"HTTP error: {ex.Message}");
        }
        catch (IOException ex)
        {
            Console.WriteLine($"File I/O error: {ex.Message}");
        }
    }
}

// Usage
await FileDownloader.DownloadFileAsync(
    "https://example.com/large-file.zip", 
    @"C:\Downloads\large-file.zip");

Download with Progress Tracking

For large files, you might want to track download progress:

public static async Task DownloadWithProgressAsync(string fileUrl, string destinationPath, 
    IProgress<(long bytesRead, long totalBytes)> progress = null)
{
    using var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();

    var totalBytes = response.Content.Headers.ContentLength ?? -1;
    await using var contentStream = await response.Content.ReadAsStreamAsync();
    await using var fileStream = new FileStream(destinationPath, FileMode.Create);

    var buffer = new byte[8192];
    long totalBytesRead = 0;
    int bytesRead;

    while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
        await fileStream.WriteAsync(buffer, 0, bytesRead);
        totalBytesRead += bytesRead;
        progress?.Report((totalBytesRead, totalBytes));
    }
}

// Usage with progress reporting
var progress = new Progress<(long bytesRead, long totalBytes)>(p =>
{
    if (p.totalBytes > 0)
    {
        var percentage = (double)p.bytesRead / p.totalBytes * 100;
        Console.WriteLine($"Downloaded: {percentage:F1}% ({p.bytesRead:N0}/{p.totalBytes:N0} bytes)");
    }
});

await DownloadWithProgressAsync("https://example.com/file.zip", "local-file.zip", progress);

Advanced Download with Retry Logic

For production applications, implement retry logic and timeout handling:

public static async Task<bool> DownloadWithRetryAsync(string fileUrl, string destinationPath, 
    int maxRetries = 3, TimeSpan? timeout = null)
{
    var client = new HttpClient();
    if (timeout.HasValue)
        client.Timeout = timeout.Value;

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead);
            response.EnsureSuccessStatusCode();

            await using var contentStream = await response.Content.ReadAsStreamAsync();
            await using var fileStream = new FileStream(destinationPath, FileMode.Create);

            await contentStream.CopyToAsync(fileStream);
            return true;
        }
        catch (Exception ex) when (attempt < maxRetries)
        {
            Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying...");
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
        }
    }

    return false;
}

Key Best Practices

Memory Efficiency

  • Use HttpCompletionOption.ResponseHeadersRead to start processing before the entire response is buffered
  • Stream content directly to file using CopyToAsync() instead of loading everything into memory
  • Use await using for proper async disposal of streams

Error Handling

  • Always call EnsureSuccessStatusCode() to verify the HTTP response
  • Handle specific exceptions like HttpRequestException, TaskCanceledException, and IOException
  • Implement retry logic for transient failures

Performance Considerations

  • Reuse HttpClient instances when possible (avoid creating new instances per request)
  • Set appropriate timeouts using HttpClient.Timeout
  • Use cancellation tokens for long-running downloads:
public static async Task DownloadFileAsync(string fileUrl, string destinationPath, 
    CancellationToken cancellationToken = default)
{
    using var response = await httpClient.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
    response.EnsureSuccessStatusCode();

    await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
    await using var fileStream = new FileStream(destinationPath, FileMode.Create);

    await contentStream.CopyToAsync(fileStream, cancellationToken);
}

Security Considerations

  • Validate file URLs and destination paths
  • Consider file size limits to prevent disk space exhaustion
  • Use HTTPS URLs when possible for secure downloads
  • Verify file integrity using checksums when available

HttpClient provides a robust foundation for file downloads in C#, offering excellent performance and flexibility for both simple and complex scenarios.

Related Questions

Get Started Now

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