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
, andIOException
- 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.