Skip to content

Instantly share code, notes, and snippets.

@dalexsoto
Last active February 25, 2026 17:56
Show Gist options
  • Select an option

  • Save dalexsoto/9fd3c5bdbe9f61a717d47c5843384d11 to your computer and use it in GitHub Desktop.

Select an option

Save dalexsoto/9fd3c5bdbe9f61a717d47c5843384d11 to your computer and use it in GitHub Desktop.
Add Progress reporting to capabilities to HttpClient

HttpClientProgressExtensions

Add Progress reporting to capabilities to HttpClient

using HttpClientProgress;

A little routine to download a PDF file from the internet.

async Task DownloadFile ()
{
	// for the sake of the example lets add a client definition here
	var client = new HttpClient ();
	var docUrl = "https://pspdfkit.com/downloads/case-study-box.pdf";
	var filePath = Path.Combine (Path.GetTempPath (), "case-study-box.pdf");

	// Setup your progress reporter
	var progress = new Progress<float> ();
	progress.ProgressChanged += Progress_ProgressChanged;

	// Use the provided extension method
 	using (var file = new FileStream (filePath, FileMode.Create, FileAccess.Write, FileShare.None))
		await client.DownloadDataAsync (docUrl, file, progress);
}

void Progress_ProgressChanged (object sender, float progress)
{
	// Do something with your progress
	Console.WriteLine (progress);
}
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace HttpClientProgress {
public static class HttpClientProgressExtensions {
public static async Task DownloadDataAsync (this HttpClient client, string requestUrl, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default (CancellationToken))
{
using (var response = await client.GetAsync (requestUrl, HttpCompletionOption.ResponseHeadersRead)) {
var contentLength = response.Content.Headers.ContentLength;
using (var download = await response.Content.ReadAsStreamAsync ()) {
// no progress... no contentLength... very sad
if (progress is null || !contentLength.HasValue) {
await download.CopyToAsync (destination);
return;
}
// Such progress and contentLength much reporting Wow!
var progressWrapper = new Progress<long> (totalBytes => progress.Report (GetProgressPercentage (totalBytes, contentLength.Value)));
await download.CopyToAsync (destination, 81920, progressWrapper, cancellationToken);
}
}
float GetProgressPercentage (float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
}
static async Task CopyToAsync (this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default (CancellationToken))
{
if (bufferSize < 0)
throw new ArgumentOutOfRangeException (nameof (bufferSize));
if (source is null)
throw new ArgumentNullException (nameof (source));
if (!source.CanRead)
throw new InvalidOperationException ($"'{nameof (source)}' is not readable.");
if (destination == null)
throw new ArgumentNullException (nameof (destination));
if (!destination.CanWrite)
throw new InvalidOperationException ($"'{nameof (destination)}' is not writable.");
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = await source.ReadAsync (buffer, 0, buffer.Length, cancellationToken).ConfigureAwait (false)) != 0) {
await destination.WriteAsync (buffer, 0, bytesRead, cancellationToken).ConfigureAwait (false);
totalBytesRead += bytesRead;
progress?.Report (totalBytesRead);
}
}
}
}
@tephyrnex
Copy link

Alex, there isn't a specific license specified for this gist; and there is none indicated in the source. Can you advise on this? MIT license would be great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment