14

I'm writing a library with intentions to use it in desktop (.Net 4.0 and up), phone (WP 7.5 and up) and Windows Store (Windows 8 and up) apps.

The library has the capability to download files from the Internet using Portable HttpClient library, and report the progress of the download.

I search around here and the rest of the internet for documentations and code sample/guidelines on how to implement the progress reporting, and this search led me to nowhere.

Does anyone has an article, documentation, guideline, code sample or whatever to help me achieve this?

TheBlueSky
  • 5,526
  • 7
  • 35
  • 65

2 Answers2

33

I wrote the following code to implement progress reporting. The code supports all the platforms I wanted; however, you need to reference the following NuGet packages:

  • Microsoft.Net.Http
  • Microsoft.Bcl.Async

Here is the code:

public async Task DownloadFileAsync(string url, IProgress<double> progress, CancellationToken token)
{
    var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);

    if (!response.IsSuccessStatusCode)
    {
        throw new Exception(string.Format("The request returned with HTTP status code {0}", response.StatusCode));
    }

    var total = response.Content.Headers.ContentLength.HasValue ? response.Content.Headers.ContentLength.Value : -1L;
    var canReportProgress = total != -1 && progress != null;

    using (var stream = await response.Content.ReadAsStreamAsync())
    {
        var totalRead = 0L;
        var buffer = new byte[4096];
        var isMoreToRead = true;

        do
        {
            token.ThrowIfCancellationRequested();

            var read = await stream.ReadAsync(buffer, 0, buffer.Length, token);

            if (read == 0)
            {
                isMoreToRead = false;
            }
            else
            {
                var data = new byte[read];
                buffer.ToList().CopyTo(0, data, 0, read);

                // TODO: put here the code to write the file to disk

                totalRead += read;

                if (canReportProgress)
                {
                    progress.Report((totalRead * 1d) / (total * 1d) * 100);
                }
            }
        } while (isMoreToRead);
    }
}

The using it is as simple as:

var progress = new Microsoft.Progress<double>();
progress.ProgressChanged += (sender, value) => System.Console.Write("\r%{0:N0}", value);

var cancellationToken = new CancellationTokenSource();

await DownloadFileAsync("http://www.dotpdn.com/files/Paint.NET.3.5.11.Install.zip", progress, cancellationToken.Token);
TheBlueSky
  • 5,526
  • 7
  • 35
  • 65
  • I know this is kind of necro-commenting, but I'd just like to add that I used this approach and ran into timing issues, where there was so much progress output, that it lagged behind the actual progress of the download, resulting in the file being downloaded much faster than the progress reported. (I ended up using a stopwatch to only report every second or so) – Dynde Mar 13 '17 at 08:16
  • 1
    Not sure what might have caused this. I found it working well in my scenario; including using the buffer size 4096 bytes. – TheBlueSky Mar 17 '17 at 08:11
  • What is `put here the code to write the file to disk` supposed to mean? I assumed you mean `the data`, but what data, and how to write it? Do I append it to a file, or write it all at once? – ProfK Mar 30 '18 at 04:38
  • 3
    @ProfK `data` is an array of bytes that contains the exact bytes that were read. Yes, you keep appending it to a file until the last byte is read. – TheBlueSky Mar 30 '18 at 12:34
1

You can specify HttpCompletionOption.ResponseHeadersRead and then get the stream and report progress while you read from the stream. See this similar question.

Community
  • 1
  • 1
TheESJ
  • 2,357
  • 17
  • 13
  • 1
    maybe similar, but not an answer to my question as it is. But thanks for the link, it helped me finding the answer. – TheBlueSky Feb 08 '14 at 08:59