C# - Monitor Progress Httpclient once GetByteArrayAsync is called? [duplicate] - c#
i have a file downloader function:
HttpClientHandler aHandler = new HttpClientHandler();
aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
HttpClient aClient = new HttpClient(aHandler);
aClient.DefaultRequestHeaders.ExpectContinue = false;
HttpResponseMessage response = await aClient.GetAsync(url);
InMemoryRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream();
// To save downloaded image to local storage
var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(
filename, CreationCollisionOption.ReplaceExisting);
var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite);
DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0));
writer.WriteBytes(await response.Content.ReadAsByteArrayAsync());
await writer.StoreAsync();
//current.image.SetSource(randomAccessStream);
writer.DetachStream();
await fs.FlushAsync();
How can i realize progress bar functionality?
Maybe i can get the writers bytes written so far? Or something?
P.S. I cant use DownloadOperation(Background transferring) because data from server requests certificate - and this functionality doesn't exist in DownloadOperations.
From .Net 4.5 onwards: Use IProgress<T>
Since .Net 4.5 you can handle asynchronous progress reporting with the IProgress<T> interface. You can write an extension method for downloading files using the HttpClient that can be called like this where progress is the implementation of IProgress<float> for your progress bar or other UI stuff:
// Seting up the http client used to download the data
using (var client = new HttpClient()) {
client.Timeout = TimeSpan.FromMinutes(5);
// Create a file stream to store the downloaded data.
// This really can be any type of writeable stream.
using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) {
// Use the custom extension method below to download the data.
// The passed progress-instance will receive the download status updates.
await client.DownloadAsync(DownloadUrl, file, progress, cancellationToken);
}
}
Implementation
The code for this extension method looks like this. Note that this extension depends on another extension for handling asynchronous stream copying with progress reporting.
public static class HttpClientExtensions
{
public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default) {
// Get the http headers first to examine the content length
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) {
var contentLength = response.Content.Headers.ContentLength;
using (var download = await response.Content.ReadAsStreamAsync()) {
// Ignore progress reporting when no progress reporter was
// passed or when the content length is unknown
if (progress == null || !contentLength.HasValue) {
await download.CopyToAsync(destination);
return;
}
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value));
// Use extension method to report progress while downloading
await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
progress.Report(1);
}
}
}
}
With stream extension for the real progress reporting:
public static class StreamExtensions
{
public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default) {
if (source == null)
throw new ArgumentNullException(nameof(source));
if (!source.CanRead)
throw new ArgumentException("Has to be readable", nameof(source));
if (destination == null)
throw new ArgumentNullException(nameof(destination));
if (!destination.CanWrite)
throw new ArgumentException("Has to be writable", nameof(destination));
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
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);
}
}
}
Here's a self-contained class that'll do the download, and report back the progress percentage, based on code from TheBlueSky on this SO answer, and eriksendc on this GitHub comment.
public class HttpClientDownloadWithProgress : IDisposable
{
private readonly string _downloadUrl;
private readonly string _destinationFilePath;
private HttpClient _httpClient;
public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
public event ProgressChangedHandler ProgressChanged;
public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath)
{
_downloadUrl = downloadUrl;
_destinationFilePath = destinationFilePath;
}
public async Task StartDownload()
{
_httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };
using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead))
await DownloadFileFromHttpResponseMessage(response);
}
private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength;
using (var contentStream = await response.Content.ReadAsStreamAsync())
await ProcessContentStream(totalBytes, contentStream);
}
private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
{
var totalBytesRead = 0L;
var readCount = 0L;
var buffer = new byte[8192];
var isMoreToRead = true;
using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
do
{
var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
isMoreToRead = false;
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
readCount += 1;
if (readCount % 100 == 0)
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
}
while (isMoreToRead);
}
}
private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
{
if (ProgressChanged == null)
return;
double? progressPercentage = null;
if (totalDownloadSize.HasValue)
progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
Usage:
var downloadFileUrl = "http://example.com/file.zip";
var destinationFilePath = Path.GetFullPath("file.zip");
using (var client = new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath))
{
client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => {
Console.WriteLine($"{progressPercentage}% ({totalBytesDownloaded}/{totalFileSize})");
};
await client.StartDownload();
}
Result:
7.81% (26722304/342028776)
8.05% (27535016/342028776)
8.28% (28307984/342028776)
8.5% (29086548/342028776)
8.74% (29898692/342028776)
8.98% (30704184/342028776)
9.22% (31522816/342028776)
The best way to go is using Windows.Web.Http.HttpClient instead of System.Net.Http.HttpClient. The first one supports progress.
But if for some reason you want to stick to the System.Net one, you will need to implement your own progress.
Remove the DataWriter, remove the InMemoryRandomAccessStream and add HttpCompletionOption.ResponseHeadersRead to GetAsync call so it returns as soon as headers are received, not when the whole response is received. I.e.:
// Your original code.
HttpClientHandler aHandler = new HttpClientHandler();
aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
HttpClient aClient = new HttpClient(aHandler);
aClient.DefaultRequestHeaders.ExpectContinue = false;
HttpResponseMessage response = await aClient.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead); // Important! ResponseHeadersRead.
// To save downloaded image to local storage
var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(
filename,
CreationCollisionOption.ReplaceExisting);
var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite);
// New code.
Stream stream = await response.Content.ReadAsStreamAsync();
IInputStream inputStream = stream.AsInputStream();
ulong totalBytesRead = 0;
while (true)
{
// Read from the web.
IBuffer buffer = new Windows.Storage.Streams.Buffer(1024);
buffer = await inputStream.ReadAsync(
buffer,
buffer.Capacity,
InputStreamOptions.None);
if (buffer.Length == 0)
{
// There is nothing else to read.
break;
}
// Report progress.
totalBytesRead += buffer.Length;
System.Diagnostics.Debug.WriteLine("Bytes read: {0}", totalBytesRead);
// Write to file.
await fs.WriteAsync(buffer);
}
inputStream.Dispose();
fs.Dispose();
The simplest way to implement progress tracking for both uploading and downloading is to use ProgressMessageHandler from the Microsoft.AspNet.WebApi.Client nuget package.
Note: this library was originally named System.Net.Http.Formatting, and was renamed to Microsoft.AspNet.WebApi.Client. However, this library is not related to ASP.Net and can be used by any project looking for official Microsoft extensions to HttpClient. The source code is available here.
Example:
var handler = new HttpClientHandler() { AllowAutoRedirect = true };
var ph = new ProgressMessageHandler(handler);
ph.HttpSendProgress += (_, args) =>
{
Console.WriteLine($"upload progress: {(double)args.BytesTransferred / args.TotalBytes}");
};
ph.HttpReceiveProgress += (_, args) =>
{
Console.WriteLine($"download progress: {(double)args.BytesTransferred / args.TotalBytes}");
};
var client = new HttpClient(ph);
await client.SendAsync(...);
Note that this will not report progress if uploading a byte array. The request message content must be a stream.
The following code shows a minimal example of what must be done against the HttpClient api to get download progress.
HttpClient client = //...
// Must use ResponseHeadersRead to avoid buffering of the content
using (var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)){
// You must use as stream to have control over buffering and number of bytes read/received
using (var stream = await response.Content.ReadAsStreamAsync())
{
// Read/process bytes from stream as appropriate
// Calculated by you based on how many bytes you have read. Likely incremented within a loop.
long bytesRecieved = //...
long? totalBytes = response.Content.Headers.ContentLength;
double? percentComplete = (double)bytesRecieved / totalBytes;
// Do what you want with `percentComplete`
}
}
The above does not tell you how to process the stream, how to report the process, or try to provide a direct solution to the code in the original question. However, this answer may be more accessible to future readers who wish to apply having progress to in their code.
same as #René Sackers solution above, but added the ability to cancel the download
class HttpClientDownloadWithProgress : IDisposable
{
private readonly string _downloadUrl;
private readonly string _destinationFilePath;
private readonly CancellationToken? _cancellationToken;
private HttpClient _httpClient;
public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
public event ProgressChangedHandler ProgressChanged;
public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath, CancellationToken? cancellationToken = null)
{
_downloadUrl = downloadUrl;
_destinationFilePath = destinationFilePath;
_cancellationToken = cancellationToken;
}
public async Task StartDownload()
{
_httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };
using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead))
await DownloadFileFromHttpResponseMessage(response);
}
private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength;
using (var contentStream = await response.Content.ReadAsStreamAsync())
await ProcessContentStream(totalBytes, contentStream);
}
private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
{
var totalBytesRead = 0L;
var readCount = 0L;
var buffer = new byte[8192];
var isMoreToRead = true;
using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
do
{
int bytesRead;
if (_cancellationToken.HasValue)
{
bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cancellationToken.Value);
}
else
{
bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
}
if (bytesRead == 0)
{
isMoreToRead = false;
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
readCount += 1;
if (readCount % 10 == 0)
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
}
while (isMoreToRead);
}
//the last progress trigger should occur after the file handle has been released or you may get file locked error
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
}
private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
{
if (ProgressChanged == null)
return;
double? progressPercentage = null;
if (totalDownloadSize.HasValue)
progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
René Sackers version is excellent but it could be better. Specifically, it has a subtle race condition caused by TriggerProgressChanged firing before the stream closes. The fix is to fire the event after the stream is explicitly disposed. The version below includes the above change, inherits from HttpClient and adds support for cancellation tokens.
public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
public class HttpClientWithProgress : HttpClient
{
private readonly string _DownloadUrl;
private readonly string _DestinationFilePath;
public event ProgressChangedHandler ProgressChanged;
public HttpClientWithProgress(string downloadUrl, string destinationFilePath)
{
_DownloadUrl = downloadUrl;
_DestinationFilePath = destinationFilePath;
}
public async Task StartDownload()
{
using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead))
await DownloadFileFromHttpResponseMessage(response);
}
public async Task StartDownload(CancellationToken cancellationToken)
{
using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
await DownloadFileFromHttpResponseMessage(response);
}
private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
long? totalBytes = response.Content.Headers.ContentLength;
using (var contentStream = await response.Content.ReadAsStreamAsync())
await ProcessContentStream(totalBytes, contentStream);
}
private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
{
long totalBytesRead = 0L;
long readCount = 0L;
byte[] buffer = new byte[8192];
bool isMoreToRead = true;
using (FileStream fileStream = new FileStream(_DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
do
{
int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
isMoreToRead = false;
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
readCount += 1;
if (readCount % 10 == 0)
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
}
while (isMoreToRead);
}
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
}
private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
{
if (ProgressChanged == null)
return;
double? progressPercentage = null;
if (totalDownloadSize.HasValue)
progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
}
}
This is my variation on the answer of René Sackers. Main differences:
A more functional style.
Only one method instead of a whole object.
Can cancel the download
public async static Task Download(
string downloadUrl,
string destinationFilePath,
Func<long?, long, double?, bool> progressChanged)
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };
using var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength;
using var contentStream = await response.Content.ReadAsStreamAsync();
var totalBytesRead = 0L;
var readCount = 0L;
var buffer = new byte[8192];
var isMoreToRead = true;
static double? calculatePercentage(long? totalDownloadSize, long totalBytesRead) => totalDownloadSize.HasValue ? Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2) : null;
using var fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
do
{
var bytesRead = await contentStream.ReadAsync(buffer);
if (bytesRead == 0)
{
isMoreToRead = false;
if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead)))
{
throw new OperationCanceledException();
}
continue;
}
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
totalBytesRead += bytesRead;
readCount++;
if (readCount % 100 == 0)
{
if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead)))
{
throw new OperationCanceledException();
}
}
}
while (isMoreToRead);
}
It can be called this way:
// Change this variable to stop the download
// You can use a global variable or some kind of state management
var mustStop = false;
var downloadProgress = (long? _, long __, double? progressPercentage) =>
{
if (progressPercentage.HasValue)
progressBar.Value = progressPercentage.Value;
// In this example only the variable is checked
// You could write other code that evaluates other conditions
return mustStop;
};
SomeClass.Download("https://example.com/bigfile.zip", "c:\downloads\file.zip", downloadProgress);
Hm, you could have another thread check the current size of the stream being written (you'd also pass the expected file size to it) and then update the progress bar accordingly.
Im not really sure how to measure how the completion logic, but for now this seems to do it.
public event ProgressChangedHandler ProgressChanged;
public event ProgressCompleteHandler DownloadComplete;
...
TriggerProgressChanged(totalDownloadSize, totalBytesRead);
TriggerDownloadComplete(totalBytesRead == totalDownloadSize);
private void TriggerDownloadComplete(bool status)
{
DownloadComplete(status);
}
client.DownloadComplete += (status) =>
{
if (status)
{
// success
}
};
This is a modified version of René Sackers answer with the following functional changes:
http client not disposed (because it should not be disposed)
better progress handling
callback to create httpRequest (custom header support)
utilizes ArrayPool to reduce memory footprint
automatic event subscribe+unsubscribe to prevent memory leaks by event handlers
You can also use this nuget package https://www.nuget.org/packages/Amusoft.Toolkit.Http to gain all benefits. Since it supports net462 and above that is probably the easiest way.
Usage:
await DownloadWithProgress.ExecuteAsync(HttpClients.General, assetUrl, downloadFilePath, progressHandler, () =>
{
var requestMessage = new HttpRequestMessage(HttpMethod.Get, assetUrl);
requestMessage.Headers.Accept.TryParseAdd("application/octet-stream");
return requestMessage;
});
I guess i am not the only one who needs custom headers so i figured i would share this rewrite
Implementation:
public delegate void DownloadProgressHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);
public static class DownloadWithProgress
{
public static async Task ExecuteAsync(HttpClient httpClient, string downloadPath, string destinationPath, DownloadProgressHandler progress, Func<HttpRequestMessage> requestMessageBuilder = null)
{
requestMessageBuilder ??= GetDefaultRequestBuilder(downloadPath);
var download = new HttpClientDownloadWithProgress(httpClient, destinationPath, requestMessageBuilder);
download.ProgressChanged += progress;
await download.StartDownload();
download.ProgressChanged -= progress;
}
private static Func<HttpRequestMessage> GetDefaultRequestBuilder(string downloadPath)
{
return () => new HttpRequestMessage(HttpMethod.Get, downloadPath);
}
}
internal class HttpClientDownloadWithProgress
{
private readonly HttpClient _httpClient;
private readonly string _destinationFilePath;
private readonly Func<HttpRequestMessage> _requestMessageBuilder;
private int _bufferSize = 8192;
public event DownloadProgressHandler ProgressChanged;
public HttpClientDownloadWithProgress(HttpClient httpClient, string destinationFilePath, Func<HttpRequestMessage> requestMessageBuilder)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_destinationFilePath = destinationFilePath ?? throw new ArgumentNullException(nameof(destinationFilePath));
_requestMessageBuilder = requestMessageBuilder ?? throw new ArgumentNullException(nameof(requestMessageBuilder));
}
public async Task StartDownload()
{
using var requestMessage = _requestMessageBuilder.Invoke();
using var response = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
await DownloadAsync(response);
}
private async Task DownloadAsync(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
var totalBytes = response.Content.Headers.ContentLength;
using (var contentStream = await response.Content.ReadAsStreamAsync())
await ProcessContentStream(totalBytes, contentStream);
}
private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
{
var totalBytesRead = 0L;
var readCount = 0L;
var buffer = ArrayPool<byte>.Shared.Rent(_bufferSize);
var isMoreToRead = true;
using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, _bufferSize, true))
{
do
{
var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
isMoreToRead = false;
ReportProgress(totalDownloadSize, totalBytesRead);
continue;
}
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
readCount += 1;
if (readCount % 100 == 0)
ReportProgress(totalDownloadSize, totalBytesRead);
}
while (isMoreToRead);
}
ArrayPool<byte>.Shared.Return(buffer);
}
private void ReportProgress(long? totalDownloadSize, long totalBytesRead)
{
double? progressPercentage = null;
if (totalDownloadSize.HasValue)
progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);
ProgressChanged?.Invoke(totalDownloadSize, totalBytesRead, progressPercentage);
}
}
Related
Httpclient Get realtime download speed while downloading [duplicate]
i have a file downloader function: HttpClientHandler aHandler = new HttpClientHandler(); aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; HttpClient aClient = new HttpClient(aHandler); aClient.DefaultRequestHeaders.ExpectContinue = false; HttpResponseMessage response = await aClient.GetAsync(url); InMemoryRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream(); // To save downloaded image to local storage var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync( filename, CreationCollisionOption.ReplaceExisting); var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0)); writer.WriteBytes(await response.Content.ReadAsByteArrayAsync()); await writer.StoreAsync(); //current.image.SetSource(randomAccessStream); writer.DetachStream(); await fs.FlushAsync(); How can i realize progress bar functionality? Maybe i can get the writers bytes written so far? Or something? P.S. I cant use DownloadOperation(Background transferring) because data from server requests certificate - and this functionality doesn't exist in DownloadOperations.
From .Net 4.5 onwards: Use IProgress<T> Since .Net 4.5 you can handle asynchronous progress reporting with the IProgress<T> interface. You can write an extension method for downloading files using the HttpClient that can be called like this where progress is the implementation of IProgress<float> for your progress bar or other UI stuff: // Seting up the http client used to download the data using (var client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(5); // Create a file stream to store the downloaded data. // This really can be any type of writeable stream. using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { // Use the custom extension method below to download the data. // The passed progress-instance will receive the download status updates. await client.DownloadAsync(DownloadUrl, file, progress, cancellationToken); } } Implementation The code for this extension method looks like this. Note that this extension depends on another extension for handling asynchronous stream copying with progress reporting. public static class HttpClientExtensions { public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default) { // Get the http headers first to examine the content length using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) { var contentLength = response.Content.Headers.ContentLength; using (var download = await response.Content.ReadAsStreamAsync()) { // Ignore progress reporting when no progress reporter was // passed or when the content length is unknown if (progress == null || !contentLength.HasValue) { await download.CopyToAsync(destination); return; } // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes / contentLength.Value)); // Use extension method to report progress while downloading await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); progress.Report(1); } } } } With stream extension for the real progress reporting: public static class StreamExtensions { public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default) { if (source == null) throw new ArgumentNullException(nameof(source)); if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source)); if (destination == null) throw new ArgumentNullException(nameof(destination)); if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination)); if (bufferSize < 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); 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); } } }
Here's a self-contained class that'll do the download, and report back the progress percentage, based on code from TheBlueSky on this SO answer, and eriksendc on this GitHub comment. public class HttpClientDownloadWithProgress : IDisposable { private readonly string _downloadUrl; private readonly string _destinationFilePath; private HttpClient _httpClient; public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public event ProgressChangedHandler ProgressChanged; public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; } public async Task StartDownload() { _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) await DownloadFileFromHttpResponseMessage(response); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) await ProcessContentStream(totalBytes, contentStream); } private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) { var totalBytesRead = 0L; var readCount = 0L; var buffer = new byte[8192]; var isMoreToRead = true; using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { do { var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { isMoreToRead = false; TriggerProgressChanged(totalDownloadSize, totalBytesRead); continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 100 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } public void Dispose() { _httpClient?.Dispose(); } } Usage: var downloadFileUrl = "http://example.com/file.zip"; var destinationFilePath = Path.GetFullPath("file.zip"); using (var client = new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath)) { client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { Console.WriteLine($"{progressPercentage}% ({totalBytesDownloaded}/{totalFileSize})"); }; await client.StartDownload(); } Result: 7.81% (26722304/342028776) 8.05% (27535016/342028776) 8.28% (28307984/342028776) 8.5% (29086548/342028776) 8.74% (29898692/342028776) 8.98% (30704184/342028776) 9.22% (31522816/342028776)
The best way to go is using Windows.Web.Http.HttpClient instead of System.Net.Http.HttpClient. The first one supports progress. But if for some reason you want to stick to the System.Net one, you will need to implement your own progress. Remove the DataWriter, remove the InMemoryRandomAccessStream and add HttpCompletionOption.ResponseHeadersRead to GetAsync call so it returns as soon as headers are received, not when the whole response is received. I.e.: // Your original code. HttpClientHandler aHandler = new HttpClientHandler(); aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; HttpClient aClient = new HttpClient(aHandler); aClient.DefaultRequestHeaders.ExpectContinue = false; HttpResponseMessage response = await aClient.GetAsync( url, HttpCompletionOption.ResponseHeadersRead); // Important! ResponseHeadersRead. // To save downloaded image to local storage var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync( filename, CreationCollisionOption.ReplaceExisting); var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); // New code. Stream stream = await response.Content.ReadAsStreamAsync(); IInputStream inputStream = stream.AsInputStream(); ulong totalBytesRead = 0; while (true) { // Read from the web. IBuffer buffer = new Windows.Storage.Streams.Buffer(1024); buffer = await inputStream.ReadAsync( buffer, buffer.Capacity, InputStreamOptions.None); if (buffer.Length == 0) { // There is nothing else to read. break; } // Report progress. totalBytesRead += buffer.Length; System.Diagnostics.Debug.WriteLine("Bytes read: {0}", totalBytesRead); // Write to file. await fs.WriteAsync(buffer); } inputStream.Dispose(); fs.Dispose();
The simplest way to implement progress tracking for both uploading and downloading is to use ProgressMessageHandler from the Microsoft.AspNet.WebApi.Client nuget package. Note: this library was originally named System.Net.Http.Formatting, and was renamed to Microsoft.AspNet.WebApi.Client. However, this library is not related to ASP.Net and can be used by any project looking for official Microsoft extensions to HttpClient. The source code is available here. Example: var handler = new HttpClientHandler() { AllowAutoRedirect = true }; var ph = new ProgressMessageHandler(handler); ph.HttpSendProgress += (_, args) => { Console.WriteLine($"upload progress: {(double)args.BytesTransferred / args.TotalBytes}"); }; ph.HttpReceiveProgress += (_, args) => { Console.WriteLine($"download progress: {(double)args.BytesTransferred / args.TotalBytes}"); }; var client = new HttpClient(ph); await client.SendAsync(...); Note that this will not report progress if uploading a byte array. The request message content must be a stream.
The following code shows a minimal example of what must be done against the HttpClient api to get download progress. HttpClient client = //... // Must use ResponseHeadersRead to avoid buffering of the content using (var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)){ // You must use as stream to have control over buffering and number of bytes read/received using (var stream = await response.Content.ReadAsStreamAsync()) { // Read/process bytes from stream as appropriate // Calculated by you based on how many bytes you have read. Likely incremented within a loop. long bytesRecieved = //... long? totalBytes = response.Content.Headers.ContentLength; double? percentComplete = (double)bytesRecieved / totalBytes; // Do what you want with `percentComplete` } } The above does not tell you how to process the stream, how to report the process, or try to provide a direct solution to the code in the original question. However, this answer may be more accessible to future readers who wish to apply having progress to in their code.
same as #René Sackers solution above, but added the ability to cancel the download class HttpClientDownloadWithProgress : IDisposable { private readonly string _downloadUrl; private readonly string _destinationFilePath; private readonly CancellationToken? _cancellationToken; private HttpClient _httpClient; public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public event ProgressChangedHandler ProgressChanged; public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath, CancellationToken? cancellationToken = null) { _downloadUrl = downloadUrl; _destinationFilePath = destinationFilePath; _cancellationToken = cancellationToken; } public async Task StartDownload() { _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) await DownloadFileFromHttpResponseMessage(response); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) await ProcessContentStream(totalBytes, contentStream); } private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) { var totalBytesRead = 0L; var readCount = 0L; var buffer = new byte[8192]; var isMoreToRead = true; using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { do { int bytesRead; if (_cancellationToken.HasValue) { bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, _cancellationToken.Value); } else { bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); } if (bytesRead == 0) { isMoreToRead = false; continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 10 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } //the last progress trigger should occur after the file handle has been released or you may get file locked error TriggerProgressChanged(totalDownloadSize, totalBytesRead); } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } public void Dispose() { _httpClient?.Dispose(); } }
René Sackers version is excellent but it could be better. Specifically, it has a subtle race condition caused by TriggerProgressChanged firing before the stream closes. The fix is to fire the event after the stream is explicitly disposed. The version below includes the above change, inherits from HttpClient and adds support for cancellation tokens. public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public class HttpClientWithProgress : HttpClient { private readonly string _DownloadUrl; private readonly string _DestinationFilePath; public event ProgressChangedHandler ProgressChanged; public HttpClientWithProgress(string downloadUrl, string destinationFilePath) { _DownloadUrl = downloadUrl; _DestinationFilePath = destinationFilePath; } public async Task StartDownload() { using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead)) await DownloadFileFromHttpResponseMessage(response); } public async Task StartDownload(CancellationToken cancellationToken) { using (var response = await GetAsync(_DownloadUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) await DownloadFileFromHttpResponseMessage(response); } private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); long? totalBytes = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) await ProcessContentStream(totalBytes, contentStream); } private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) { long totalBytesRead = 0L; long readCount = 0L; byte[] buffer = new byte[8192]; bool isMoreToRead = true; using (FileStream fileStream = new FileStream(_DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) { do { int bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { isMoreToRead = false; continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 10 == 0) TriggerProgressChanged(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } TriggerProgressChanged(totalDownloadSize, totalBytesRead); } private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) { if (ProgressChanged == null) return; double? progressPercentage = null; if (totalDownloadSize.HasValue) progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); } }
This is my variation on the answer of René Sackers. Main differences: A more functional style. Only one method instead of a whole object. Can cancel the download public async static Task Download( string downloadUrl, string destinationFilePath, Func<long?, long, double?, bool> progressChanged) { using var httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; using var response = await httpClient.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength; using var contentStream = await response.Content.ReadAsStreamAsync(); var totalBytesRead = 0L; var readCount = 0L; var buffer = new byte[8192]; var isMoreToRead = true; static double? calculatePercentage(long? totalDownloadSize, long totalBytesRead) => totalDownloadSize.HasValue ? Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2) : null; using var fileStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); do { var bytesRead = await contentStream.ReadAsync(buffer); if (bytesRead == 0) { isMoreToRead = false; if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead))) { throw new OperationCanceledException(); } continue; } await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)); totalBytesRead += bytesRead; readCount++; if (readCount % 100 == 0) { if (progressChanged(totalBytes, totalBytesRead, calculatePercentage(totalBytes, totalBytesRead))) { throw new OperationCanceledException(); } } } while (isMoreToRead); } It can be called this way: // Change this variable to stop the download // You can use a global variable or some kind of state management var mustStop = false; var downloadProgress = (long? _, long __, double? progressPercentage) => { if (progressPercentage.HasValue) progressBar.Value = progressPercentage.Value; // In this example only the variable is checked // You could write other code that evaluates other conditions return mustStop; }; SomeClass.Download("https://example.com/bigfile.zip", "c:\downloads\file.zip", downloadProgress);
Hm, you could have another thread check the current size of the stream being written (you'd also pass the expected file size to it) and then update the progress bar accordingly.
Im not really sure how to measure how the completion logic, but for now this seems to do it. public event ProgressChangedHandler ProgressChanged; public event ProgressCompleteHandler DownloadComplete; ... TriggerProgressChanged(totalDownloadSize, totalBytesRead); TriggerDownloadComplete(totalBytesRead == totalDownloadSize); private void TriggerDownloadComplete(bool status) { DownloadComplete(status); } client.DownloadComplete += (status) => { if (status) { // success } };
This is a modified version of René Sackers answer with the following functional changes: http client not disposed (because it should not be disposed) better progress handling callback to create httpRequest (custom header support) utilizes ArrayPool to reduce memory footprint automatic event subscribe+unsubscribe to prevent memory leaks by event handlers You can also use this nuget package https://www.nuget.org/packages/Amusoft.Toolkit.Http to gain all benefits. Since it supports net462 and above that is probably the easiest way. Usage: await DownloadWithProgress.ExecuteAsync(HttpClients.General, assetUrl, downloadFilePath, progressHandler, () => { var requestMessage = new HttpRequestMessage(HttpMethod.Get, assetUrl); requestMessage.Headers.Accept.TryParseAdd("application/octet-stream"); return requestMessage; }); I guess i am not the only one who needs custom headers so i figured i would share this rewrite Implementation: public delegate void DownloadProgressHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); public static class DownloadWithProgress { public static async Task ExecuteAsync(HttpClient httpClient, string downloadPath, string destinationPath, DownloadProgressHandler progress, Func<HttpRequestMessage> requestMessageBuilder = null) { requestMessageBuilder ??= GetDefaultRequestBuilder(downloadPath); var download = new HttpClientDownloadWithProgress(httpClient, destinationPath, requestMessageBuilder); download.ProgressChanged += progress; await download.StartDownload(); download.ProgressChanged -= progress; } private static Func<HttpRequestMessage> GetDefaultRequestBuilder(string downloadPath) { return () => new HttpRequestMessage(HttpMethod.Get, downloadPath); } } internal class HttpClientDownloadWithProgress { private readonly HttpClient _httpClient; private readonly string _destinationFilePath; private readonly Func<HttpRequestMessage> _requestMessageBuilder; private int _bufferSize = 8192; public event DownloadProgressHandler ProgressChanged; public HttpClientDownloadWithProgress(HttpClient httpClient, string destinationFilePath, Func<HttpRequestMessage> requestMessageBuilder) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); _destinationFilePath = destinationFilePath ?? throw new ArgumentNullException(nameof(destinationFilePath)); _requestMessageBuilder = requestMessageBuilder ?? throw new ArgumentNullException(nameof(requestMessageBuilder)); } public async Task StartDownload() { using var requestMessage = _requestMessageBuilder.Invoke(); using var response = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead); await DownloadAsync(response); } private async Task DownloadAsync(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength; using (var contentStream = await response.Content.ReadAsStreamAsync()) await ProcessContentStream(totalBytes, contentStream); } private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) { var totalBytesRead = 0L; var readCount = 0L; var buffer = ArrayPool<byte>.Shared.Rent(_bufferSize); var isMoreToRead = true; using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, _bufferSize, true)) { do { var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { isMoreToRead = false; ReportProgress(totalDownloadSize, totalBytesRead); continue; } await fileStream.WriteAsync(buffer, 0, bytesRead); totalBytesRead += bytesRead; readCount += 1; if (readCount % 100 == 0) ReportProgress(totalDownloadSize, totalBytesRead); } while (isMoreToRead); } ArrayPool<byte>.Shared.Return(buffer); } private void ReportProgress(long? totalDownloadSize, long totalBytesRead) { double? progressPercentage = null; if (totalDownloadSize.HasValue) progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); ProgressChanged?.Invoke(totalDownloadSize, totalBytesRead, progressPercentage); } }
FtpWebRequest ProtocolViolationException
Trying to create an ftp download async function with IProgress and cancelation token, but I'm getting a "ProtocolViolationException: Operation is not valid due to the current state of the object" on GetRequestStreamAsync() function I was using this as an inspiration upload async with IProgress and cancelation token public async Task DownloadWithProgressAsync(string remoteFilepath, string localFilepath, IProgress<decimal> progress, CancellationToken token) { const int bufferSize = 128 * 1024; // 128kb buffer progress.Report(0m); var remoteUri = new Uri(_baseUrl + remoteFilepath); Debug.Log($"Download\nuri: {remoteUri}"); var request = (FtpWebRequest)WebRequest.CreateDefault(remoteUri); request.Method = WebRequestMethods.Ftp.DownloadFile; request.Credentials = new NetworkCredential(_username, _password); token.ThrowIfCancellationRequested(); using (var fileStream = new FileStream(localFilepath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write, bufferSize, true)) { using (var ftpStream = await request.GetRequestStreamAsync()) { var buffer = new byte[bufferSize]; int read; while ((read = await ftpStream.ReadAsync(buffer, 0, buffer.Length, token) ) > 0) { await fileStream.WriteAsync(buffer, 0, read, token); var percent = (decimal)ftpStream.Position / ftpStream.Length; progress.Report(percent); } } } var response = (FtpWebResponse)await request.GetResponseAsync(); var success = (int)response.StatusCode >= 200 && (int)response.StatusCode < 300; response.Close(); if (!success) throw new Exception(response.StatusDescription); }
C#: HttpClient, File upload progress when uploading multiple file as MultipartFormDataContent
I'm using this code to upload multiple files and it working very well. It uses modernhttpclient library. public async Task<string> PostImages (int platform, string url, List<byte []> imageList) { try { int count = 1; var requestContent = new MultipartFormDataContent (); foreach (var image in imageList) { var imageContent = new ByteArrayContent (image); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse ("image/jpeg"); requestContent.Add (imageContent, "image" + count, "image.jpg"); count++; } var cookieHandler = new NativeCookieHandler (); var messageHandler = new NativeMessageHandler (false, false, cookieHandler); cookieHandler.SetCookies (cookies); using (var client = new HttpClient (messageHandler)) { client.DefaultRequestHeaders.TryAddWithoutValidation ("User-Agent", GetUserAgent (platform)); using (var r = await client.PostAsync (url, requestContent)) { string result = await r.Content.ReadAsStringAsync (); System.Diagnostics.Debug.WriteLine ("PostAsync: " + result); return result; } } } catch (Exception e) { System.Diagnostics.Debug.WriteLine (e.Message); return null; } } Now I need the progress when uploading the files. I searched in google and found I need to use ProgressStreamContent https://github.com/paulcbetts/ModernHttpClient/issues/80 Since ProgressStreamContent contains a constructor that takes a stream, I converted the MultipartFormDataContent to stream and used it in its constructor. But, its not working. Upload fails. I think its because it is a stream of all the files together which is not what my back end is expecting. public async Task<string> PostImages (int platform, string url, List<byte []> imageList) { try { int count = 1; var requestContent = new MultipartFormDataContent (); // here you can specify boundary if you need---^ foreach (var image in imageList) { var imageContent = new ByteArrayContent (image); imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse ("image/jpeg"); requestContent.Add (imageContent, "image" + count, "image.jpg"); count++; } var cookieHandler = new NativeCookieHandler (); var messageHandler = new NativeMessageHandler (false, false, cookieHandler); cookieHandler.SetCookies (RestApiPaths.cookies); var stream = await requestContent.ReadAsStreamAsync (); var client = new HttpClient (messageHandler); client.DefaultRequestHeaders.TryAddWithoutValidation ("User-Agent", RestApiPaths.GetUserAgent (platform)); var request = new HttpRequestMessage (HttpMethod.Post, url); var progressContent = new ProgressStreamContent (stream, 4096); progressContent.Progress = (bytes, totalBytes, totalBytesExpected) => { Console.WriteLine ("Uploading {0}/{1}", totalBytes, totalBytesExpected); }; request.Content = progressContent; var response = await client.SendAsync (request); string result = await response.Content.ReadAsStringAsync (); System.Diagnostics.Debug.WriteLine ("PostAsync: " + result); return result; } catch (Exception e) { System.Diagnostics.Debug.WriteLine (e.Message); return null; } } What should I do here to get this working? Any help is appreciated
I have a working version of ProgressableStreamContent. Please note, I am adding headers in the constructor, this is a bug in original ProgressStreamContent that it does not add headers !! internal class ProgressableStreamContent : HttpContent { /// <summary> /// Lets keep buffer of 20kb /// </summary> private const int defaultBufferSize = 5*4096; private HttpContent content; private int bufferSize; //private bool contentConsumed; private Action<long,long> progress; public ProgressableStreamContent(HttpContent content, Action<long,long> progress) : this(content, defaultBufferSize, progress) { } public ProgressableStreamContent(HttpContent content, int bufferSize, Action<long,long> progress) { if (content == null) { throw new ArgumentNullException("content"); } if (bufferSize <= 0) { throw new ArgumentOutOfRangeException("bufferSize"); } this.content = content; this.bufferSize = bufferSize; this.progress = progress; foreach (var h in content.Headers) { this.Headers.Add(h.Key,h.Value); } } protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { return Task.Run(async () => { var buffer = new Byte[this.bufferSize]; long size; TryComputeLength(out size); var uploaded = 0; using (var sinput = await content.ReadAsStreamAsync()) { while (true) { var length = sinput.Read(buffer, 0, buffer.Length); if (length <= 0) break; //downloader.Uploaded = uploaded += length; uploaded += length; progress?.Invoke(uploaded, size); //System.Diagnostics.Debug.WriteLine($"Bytes sent {uploaded} of {size}"); stream.Write(buffer, 0, length); stream.Flush(); } } stream.Flush(); }); } protected override bool TryComputeLength(out long length) { length = content.Headers.ContentLength.GetValueOrDefault(); return true; } protected override void Dispose(bool disposing) { if (disposing) { content.Dispose(); } base.Dispose(disposing); } } Also note, it expects HttpContent, not stream. This is how you can use it. var progressContent = new ProgressableStreamContent ( requestContent, 4096, (sent,total) => { Console.WriteLine ("Uploading {0}/{1}", sent, total); });
Create progress bar for download zip
I want to create a progress bar in a backgroud progress when the users download zip's or same. This is my code: private void DoSincroFit() { HttpWebRequest request = HttpWebRequest.CreateHttp(url); //Add headers to request request.Headers["Type"] = "sincrofit"; request.Headers["Device"] = "1"; request.Headers["Version"] = "0.000"; request.Headers["Os"] = "WindowsPhone"; request.BeginGetResponse(new AsyncCallback(playResponseAsync), request); } public async void playResponseAsync(IAsyncResult asyncResult) { //Declaration of variables HttpWebRequest webRequest = (HttpWebRequest)asyncResult.AsyncState; try { string fileName = "sincrofit.rar"; using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asyncResult)) { byte[] buffer = new byte[1024]; var newZipFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); using (var writeStream = await newZipFile.OpenAsync(FileAccessMode.ReadWrite)) { using (var outputStream = writeStream.GetOutputStreamAt(0)) { using (var dataWriter = new DataWriter(outputStream)) { using (Stream input = webResponse.GetResponseStream()) { var totalSize = 0; for (int size = input.Read(buffer, 0, buffer.Length); size > 0; size = input.Read(buffer, 0, buffer.Length)) { dataWriter.WriteBytes(buffer); totalSize += size; //get the progress of download //I think the progress bar going here! } await dataWriter.StoreAsync(); await outputStream.FlushAsync(); dataWriter.DetachStream(); } } } } } } catch { CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher; dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { //Declaration of variables SMethods.Message_Dialog("Download has stopped!", "Error"); }); } } And this is method that call background method public async Task<string> doSync(ProgressBar bar) { //Declaration of variables string response = await DoRequest("CHECK", "1", "0.000", "WindowsPhone"); pBar = bar; //When is 1, the checkConnection will connect if (response == "1") { response = response + "," + await DoRequest("SIZEFIT", "1", "0.000", "WindowsPhone"); DoSincroFit(); response += "," + await DoRequest("DELSINC", "1", "0.000", "WindowsPhone"); return response; } return "0,0,0"; } How can I create progress bar when this is a external class? Exactly, doSync and DoSincroFit belong a Sync.cs and my UI is MyPage.Xaml.cs. Thanks in advance!
If you are using MVVM you might create properties that notify when they are changed. Then, your external code which shouldn't really know anything about UI representation simply updates such properties. Then, in your UI code you bind controls to those properties. Without knowing more about your application, I cannot offer anything concrete here.
Finally i can solve this! My progress bar do work now! private void DoSincroFit() { HttpWebRequest request = HttpWebRequest.CreateHttp(url); //Add headers to request request.Headers["Type"] = "sincrofit"; request.Headers["Device"] = "1"; request.Headers["Version"] = "0.000"; request.Headers["Os"] = "WindowsPhone"; //Windows Cache Problems request.Headers["Cache-Control"] = "no-cache"; request.Headers["Pragma"] = "no-cache"; dispatcher = CoreWindow.GetForCurrentThread().Dispatcher; request.BeginGetResponse(new AsyncCallback(playResponseAsync), request); } public async void playResponseAsync(IAsyncResult asyncResult) { //Declaration of variables HttpWebRequest webRequest = (HttpWebRequest)asyncResult.AsyncState; try { //For download file with stream string fileName = "sincrofit.rar"; using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asyncResult)) { byte[] buffer = new byte[1]; //For acces Local folder of phone device var newZipFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); //Progress bar and their update using (var writeStream = await newZipFile.OpenAsync(FileAccessMode.ReadWrite)) { using (var outputStream = writeStream.GetOutputStreamAt(0)) { using (var dataWriter = new DataWriter(outputStream)) { using (Stream input = webResponse.GetResponseStream()) { var totalSize = 0; int read; uint zeroUint = Convert.ToUInt32(0); uint readUint; while ((read = input.Read(buffer, 0, buffer.Length)) > 0) { totalSize += read; await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { //Declaration of variables pBar.Value = totalSize * 100 / sizeFit; }); readUint = Convert.ToUInt32(read); IBuffer ibuffer = buffer.AsBuffer(); dataWriter.WriteBuffer(ibuffer, zeroUint, readUint); } await dataWriter.StoreAsync(); await outputStream.FlushAsync(); dataWriter.DetachStream(); } } } } } } catch { //For control errors stopped progress bar dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { //Declaration of variables SMethods.Message_Dialog("Download has stopped!", "Error"); }); } } This is a test progress bar but if u need download a big zip's then use buffer with 1024 or same... Because with 1 byte can be terrible! Thanks for all! :)
Adding pause and continue ability in my downloader
I am creating a downloader in C#. I am using WebClient class. To pause downloading on a button click I could just think of using Thread. So when I created thread and attached it with my file downloading as below WebClient web = new WebLCient(); Thread dwnd_thread = new Thread(Program.web.DownloadFileAsync(new Uri(Program.src), Program.dest)); it is giving me following errors: "The best overloaded method match for 'System.Threading.Thread.Thread(System.Threading.ThreadStart)' has some invalid arguments" and "Argument '1': cannot convert from 'void' to 'System.Threading.ThreadStart'". Then I thought if I pause my system main thread then it could block my whole process for that I used below line of code System.Threading.Thread.Sleep(100); but it is doing nothing at all. Can somebody tell me what could be a better approach for pause/downloading and how to use thread to pause my downloading process.
As there's no standard way of pause/resume a download request, you'll have to implement your own mechanism. Below is a block of code, containing an example of how such a mechanism could look. The class FileDownload takes 3 parameters: source - url to the file, to download. destination - where to save the file. chunkSize - how many bytes to read, before checking whether to pause or continue the download. public class FileDownload { private volatile bool _allowedToRun; private string _source; private string _destination; private int _chunkSize; private Lazy<int> _contentLength; public int BytesWritten { get; private set; } public int ContentLength { get { return _contentLength.Value; } } public bool Done { get { return ContentLength == BytesWritten; } } public FileDownload(string source, string destination, int chunkSize) { _allowedToRun = true; _source = source; _destination = destination; _chunkSize = chunkSize; _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength())); BytesWritten = 0; } private long GetContentLength() { var request = (HttpWebRequest)WebRequest.Create(_source); request.Method = "HEAD"; using (var response = request.GetResponse()) return response.ContentLength; } private async Task Start(int range) { if (!_allowedToRun) throw new InvalidOperationException(); var request = (HttpWebRequest)WebRequest.Create(_source); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; request.AddRange(range); using (var response = await request.GetResponseAsync()) { using (var responseStream = response.GetResponseStream()) { using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; } await fs.FlushAsync(); } } } } public Task Start() { _allowedToRun = true; return Start(BytesWritten); } public void Pause() { _allowedToRun = false; } } Usage: static void Main(string[] args) { var fw = new FileDownload("http://download.microsoft.com/download/E/E/2/EE2D29A1-2D5C-463C-B7F1-40E4170F5E2C/KinectSDK-v1.0-Setup.exe", #"D:\KinetSDK.exe", 5120); // Display progress... Task.Factory.StartNew(() => { while (!fw.Done) { Console.SetCursorPosition(0, Console.CursorTop); Console.Write(string.Format("ContentLength: {0} | BytesWritten: {1}", fw.ContentLength, fw.BytesWritten)); } }); // Start the download... fw.Start(); // Simulate pause... Thread.Sleep(500); fw.Pause(); Thread.Sleep(2000); // Start the download from where we left, and when done print to console. fw.Start().ContinueWith(t => Console.WriteLine("Done")); Console.ReadKey(); }
I've taken #ebb's solution (which works great!) and adapted it a bit. It now: Can accept a stream as an input Can report progress via IProgress Some other minor tweaks public class FileDownload { private volatile bool _allowedToRun; private Stream _sourceStream; private string _sourceUrl; private string _destination; private bool _disposeOnCompletion; private int _chunkSize; private IProgress<double> _progress; private Lazy<long> _contentLength; public long BytesWritten { get; private set; } public long ContentLength { get { return _contentLength.Value; } } public bool Done { get { return ContentLength == BytesWritten; } } public FileDownload(Stream source, string destination, bool disposeOnCompletion = true, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null) { _allowedToRun = true; _sourceStream = source; _destination = destination; _disposeOnCompletion = disposeOnCompletion; _chunkSize = chunkSizeInBytes; _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength())); _progress = progress; BytesWritten = 0; } public FileDownload(string source, string destination, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null) { _allowedToRun = true; _sourceUrl = source; _destination = destination; _chunkSize = chunkSizeInBytes; _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength())); _progress = progress; BytesWritten = 0; } private long GetContentLength() { if (_sourceStream != null) return _sourceStream.Length; else { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "HEAD"; using (var response = request.GetResponse()) return response.ContentLength; } } private async Task Start(int range) { if (!_allowedToRun) throw new InvalidOperationException(); if (_sourceStream != null) { using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await _sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; _progress?.Report((double)BytesWritten / ContentLength); } await fs.FlushAsync(); } //Control whether the stream should be disposed here or outside of this class if (BytesWritten == ContentLength && _disposeOnCompletion) _sourceStream?.Dispose(); } else { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; request.AddRange(range); using (var response = await request.GetResponseAsync()) { using (var responseStream = response.GetResponseStream()) { using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; _progress?.Report((double)BytesWritten / ContentLength); } await fs.FlushAsync(); } } } } } public Task Start() { _allowedToRun = true; return Start(BytesWritten); } public void Pause() { _allowedToRun = false; } }
I have adapted it in order to deal with files > 2GB and deals with broken download, so that for any reasons the download was stopped and you need to try again, at next download attempt, instead of starting again from scratch it will continue where it was stopped. All above solutions from #derekantrican, #ebb and myself will only work if server supports Range requests. You might need to test first if server accepts range requests before to proceed with this solution. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests public class FileDownload { private volatile bool _allowedToRun; private readonly string _sourceUrl; private readonly string _destination; private readonly int _chunkSize; private readonly IProgress<double> _progress; private readonly Lazy<long> _contentLength; public long BytesWritten { get; private set; } public long ContentLength => _contentLength.Value; public bool Done => ContentLength == BytesWritten; public FileDownload(string source, string destination, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null) { if(string.IsNullOrEmpty(source)) throw new ArgumentNullException("source is empty"); if (string.IsNullOrEmpty(destination)) throw new ArgumentNullException("destination is empty"); _allowedToRun = true; _sourceUrl = source; _destination = destination; _chunkSize = chunkSizeInBytes; _contentLength = new Lazy<long>(GetContentLength); _progress = progress; if (!File.Exists(destination)) BytesWritten = 0; else { try { BytesWritten = new FileInfo(destination).Length; } catch { BytesWritten = 0; } } } private long GetContentLength() { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "HEAD"; using (var response = request.GetResponse()) return response.ContentLength; } private async Task Start(long range) { if (!_allowedToRun) throw new InvalidOperationException(); if(Done) //file has been found in folder destination and is already fully downloaded return; var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; request.AddRange(range); using (var response = await request.GetResponseAsync()) { using (var responseStream = response.GetResponseStream()) { using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; _progress?.Report((double)BytesWritten / ContentLength); } await fs.FlushAsync(); } } } } public Task Start() { _allowedToRun = true; return Start(BytesWritten); } public void Pause() { _allowedToRun = false; } }
unfortunately WebClient has no ways to pause downloading. So, You must use WebRequest on background thread and pause getting response stream with flag. Here is sample code. But make sure that you can't pause unlimited, because TCP connection will be closed if there has nothing transfer for a while. So if resume downloading fail, you must restart downloading again. public class DownloadJob { public delegate void DownloadCompletedDelegate(Stream stream); //completion download event public event DownloadCompletedDelegate OnDownloadCompleted; //sync object private object _lock = new object(); //pause flag private bool _bPause = false; //Pause download public void Pause() { lock (_lock) { _bPause = true; } } //Resume download public void Resume() { lock (_lock) { _bPause = false; } } //Begin download with URI public void BeginDowload(Uri uri) { //Create Background thread Thread downLoadThread = new Thread( delegate() { WebRequest pWebReq = WebRequest.Create(uri); WebResponse pWebRes = pWebReq.GetResponse(); using (MemoryStream pResultStream = new MemoryStream()) using (Stream pWebStream = pWebRes.GetResponseStream()) { byte[] buffer = new byte[256]; int readCount = 1; while (readCount > 0) { //Read download stream readCount = pWebStream.Read(buffer, 0, buffer.Length); //Write to result MemoryStream pResultStream.Write(buffer, 0, readCount); //Waiting 100msec while _bPause is true while (true) { lock (_lock) { if (_bPause == true) { Thread.Sleep(100); } else { break; } } } pResultStream.Flush(); } //Fire Completion event if (OnDownloadCompleted != null) { OnDownloadCompleted(pResultStream); } } } ); //Start background thread job downLoadThread.Start(); } }