Yes, you can track the progress of downloads using HttpClient
in C#. This requires manually handling the response stream and reading data in chunks to monitor bytes transferred over time.
Key Implementation Steps
- Use
HttpCompletionOption.ResponseHeadersRead
to start processing immediately - Check
Content-Length
header for total download size (when available) - Read the response stream in chunks to track progress
- Implement progress reporting through callbacks or events
Basic Progress Tracking Example
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
public class DownloadProgressTracker
{
private readonly HttpClient _httpClient;
public DownloadProgressTracker()
{
_httpClient = new HttpClient();
}
public async Task DownloadFileAsync(string url, string filePath,
IProgress<DownloadProgress> progress = null)
{
using var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"Download failed: {response.StatusCode}");
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var totalBytesRead = 0L;
var buffer = new byte[8192];
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write,
FileShare.None, bufferSize: 8192, useAsync: true);
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// Report progress
progress?.Report(new DownloadProgress(totalBytesRead, totalBytes));
}
}
}
public class DownloadProgress
{
public long BytesDownloaded { get; }
public long TotalBytes { get; }
public double? ProgressPercentage => TotalBytes > 0 ?
(double)BytesDownloaded / TotalBytes * 100 : null;
public DownloadProgress(long bytesDownloaded, long totalBytes)
{
BytesDownloaded = bytesDownloaded;
TotalBytes = totalBytes;
}
}
Usage Example
class Program
{
static async Task Main(string[] args)
{
var downloader = new DownloadProgressTracker();
var progress = new Progress<DownloadProgress>(p =>
{
if (p.ProgressPercentage.HasValue)
{
Console.WriteLine($"Downloaded: {p.BytesDownloaded:N0} / {p.TotalBytes:N0} bytes " +
$"({p.ProgressPercentage:F1}%)");
}
else
{
Console.WriteLine($"Downloaded: {p.BytesDownloaded:N0} bytes (size unknown)");
}
});
try
{
await downloader.DownloadFileAsync(
"https://example.com/largefile.zip",
"download.zip",
progress);
Console.WriteLine("Download completed!");
}
catch (Exception ex)
{
Console.WriteLine($"Download failed: {ex.Message}");
}
}
}
Advanced Example with Cancellation and Speed Calculation
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class AdvancedDownloader
{
private readonly HttpClient _httpClient;
public AdvancedDownloader()
{
_httpClient = new HttpClient();
}
public async Task DownloadWithProgressAsync(string url, string filePath,
IProgress<DetailedProgress> progress = null,
CancellationToken cancellationToken = default)
{
using var response = await _httpClient.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength ?? -1L;
var totalBytesRead = 0L;
var buffer = new byte[16384]; // 16KB buffer
var stopwatch = Stopwatch.StartNew();
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write,
FileShare.None, bufferSize: 16384, useAsync: true);
int bytesRead;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length,
cancellationToken)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
totalBytesRead += bytesRead;
// Calculate speed
var elapsedTime = stopwatch.Elapsed;
var speedBytesPerSecond = elapsedTime.TotalSeconds > 0 ?
totalBytesRead / elapsedTime.TotalSeconds : 0;
progress?.Report(new DetailedProgress(
totalBytesRead,
totalBytes,
speedBytesPerSecond,
elapsedTime));
}
}
}
public class DetailedProgress
{
public long BytesDownloaded { get; }
public long TotalBytes { get; }
public double SpeedBytesPerSecond { get; }
public TimeSpan ElapsedTime { get; }
public double? ProgressPercentage => TotalBytes > 0 ?
(double)BytesDownloaded / TotalBytes * 100 : null;
public string SpeedFormatted => SpeedBytesPerSecond switch
{
>= 1_073_741_824 => $"{SpeedBytesPerSecond / 1_073_741_824:F1} GB/s",
>= 1_048_576 => $"{SpeedBytesPerSecond / 1_048_576:F1} MB/s",
>= 1024 => $"{SpeedBytesPerSecond / 1024:F1} KB/s",
_ => $"{SpeedBytesPerSecond:F0} B/s"
};
public DetailedProgress(long bytesDownloaded, long totalBytes,
double speedBytesPerSecond, TimeSpan elapsedTime)
{
BytesDownloaded = bytesDownloaded;
TotalBytes = totalBytes;
SpeedBytesPerSecond = speedBytesPerSecond;
ElapsedTime = elapsedTime;
}
}
Important Considerations
Content-Length Header
Not all servers provide the Content-Length
header. When unavailable:
- You can still track bytes downloaded
- Progress percentage cannot be calculated
- Consider implementing time-based progress indicators
Buffer Size Optimization
- Small buffers (1-4KB): More frequent progress updates, higher CPU overhead
- Large buffers (16-64KB): Better performance, less frequent updates
- Recommended: 8-16KB for balanced performance and responsiveness
Error Handling
Always implement proper error handling for: - Network timeouts - Disk space issues - HTTP error responses - Cancellation requests
Memory Management
Use using
statements or proper disposal to avoid memory leaks with streams and HttpClient responses.
This approach provides flexible, efficient download progress tracking that works well for both small and large file downloads while maintaining responsive user interfaces.