HttpClient
Progress Tracking
Data Transfer
Programming
C#

Progress info using HttpClient

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

HttpClient does not raise built-in download or upload progress events. To report progress, you have to measure bytes yourself while streaming the request or response body.

That sounds awkward at first, but the pattern is clean once you accept it: avoid reading the whole payload into memory, process the stream in chunks, and report how many bytes have passed through compared with the total length when that length is known.

Download Progress with Response Streaming

For downloads, request the response with ResponseHeadersRead so you can start reading the body as a stream.

csharp
1using System;
2using System.IO;
3using System.Net.Http;
4using System.Threading.Tasks;
5
6public static async Task DownloadWithProgressAsync(
7    HttpClient client,
8    string url,
9    string destination,
10    IProgress<double>? progress = null)
11{
12    using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
13    response.EnsureSuccessStatusCode();
14
15    var totalBytes = response.Content.Headers.ContentLength;
16
17    await using var input = await response.Content.ReadAsStreamAsync();
18    await using var output = File.Create(destination);
19
20    var buffer = new byte[81920];
21    long totalRead = 0;
22    int read;
23
24    while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0)
25    {
26        await output.WriteAsync(buffer, 0, read);
27        totalRead += read;
28
29        if (totalBytes.HasValue)
30        {
31            progress?.Report((double)totalRead / totalBytes.Value * 100.0);
32        }
33    }
34}

This works because you are counting bytes as they move through the stream instead of waiting for the whole payload to finish.

Using IProgress Cleanly

IProgress<T> is a good fit for UI or console updates because it separates the transport code from the display code.

csharp
1var progress = new Progress<double>(p =>
2{
3    Console.WriteLine($"Downloaded: {p:F1}%");
4});
5
6await DownloadWithProgressAsync(httpClient, url, "file.zip", progress);

This keeps the method reusable. The network code only knows how to report a number, not how the caller wants to display it.

What If Content Length Is Missing

Progress percentages need a total size. Some servers do not send Content-Length, especially for chunked responses or dynamically generated content.

In that case, you can still report bytes transferred, just not a true percentage.

csharp
1if (totalBytes.HasValue)
2{
3    progress?.Report((double)totalRead / totalBytes.Value * 100.0);
4}
5else
6{
7    Console.WriteLine($"Read {totalRead} bytes");
8}

That distinction is important because a progress bar based on an unknown total is not honest.

Upload Progress Uses the Same Idea

Uploads need a custom HttpContent wrapper or another mechanism that intercepts writes as they go out.

A simple pattern is to wrap a file stream and report after each chunk is copied into the outgoing request stream.

csharp
1using var fileStream = File.OpenRead("large.bin");
2using var content = new StreamContent(fileStream);
3content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
4
5using var response = await httpClient.PostAsync(url, content);
6response.EnsureSuccessStatusCode();

That alone does not give progress, but the underlying idea is the same: chunked transfer plus explicit byte counting. Many production codebases implement a custom ProgressableStreamContent for this reason.

UI and Cancellation Concerns

Progress reporting is usually paired with cancellation.

csharp
var cts = new CancellationTokenSource();

Then pass the token through your reads and writes. This matters in desktop and web UI code because users often want a cancel button more than they want a precise percentage.

Also avoid updating UI on every tiny chunk. Throttle or coalesce updates if the consumer is expensive.

Common Pitfalls

The biggest mistake is calling GetByteArrayAsync or ReadAsByteArrayAsync for large transfers and then trying to infer progress afterward. That defeats the point because the full body is already buffered.

Another common issue is assuming Content-Length always exists. It often does not.

People also forget to use ResponseHeadersRead, which means the content may be buffered before their code starts reading, reducing the usefulness of custom progress tracking.

Finally, upload and download progress are not built into HttpClient as events. If you need them, you must implement the measurement layer yourself.

Summary

  • 'HttpClient progress tracking is done by streaming and counting bytes.'
  • Use ResponseHeadersRead for download progress.
  • Report progress through IProgress<T> to keep the API clean.
  • Percentages require a known Content-Length.
  • Upload progress needs the same chunk-counting idea, usually via custom HttpContent.
  • Avoid fully buffering large payloads if progress reporting matters.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.