Search code examples
c#dotnet-httpclient

Get the value of download progress when using HttpClient in WPF


How would you get the current progress as a interger for example when downloading a file? I've been trying to implementing it into the code below with no luck at all

public static async Task HttpDownload(string url, string OutputLocation)
{
     using (HttpClient client = new HttpClient())       
     using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
     {
            var contentLength = response.Content.Headers.ContentLength;
            using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync())
            {
                   string fileToWriteTo = OutputLocation;
                   using (Stream streamToWriteTo = File.Open(fileToWriteTo, FileMode.Create))
                   {
                         await streamToReadFrom.CopyToAsync(streamToWriteTo);
                   }
            }
     }
}

I've tried things like Progress<T>, but it i could never covert it to a usage value

(Edit, I found something thats actually works for me, Progress bar with HttpClient)


Solution

  • I wrote my own method to achieve this. Its complettely stream based. So almost no load on your system memory. But feel free to test it out.

    public static async Task<T> DownloadAsync<T>(
                this HttpClient client,
                Uri requestUri,
                Func<HttpResponseMessage, T> destinationStreamFactory,
                IProgress<HttpDownloadProgress> progress = null,
                CancellationToken cancellationToken = default)
                where T : Stream
            {
                // Get the http headers first to examine the content length
                using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
                {
                    var contentLength = response.Content.Headers.ContentLength;
                    using (var download = await response.Content.ReadAsStreamAsync())
                    {
                        var destinationStream = destinationStreamFactory(response);
    
                        // Ignore progress reporting when no progress reporter was 
                        // passed or when the content length is unknown
                        if (progress == null)
                        {
                            await download.CopyToAsync(destinationStream, 81920, cancellationToken);
                            return destinationStream;
                        }
    
                        // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
                        var relativeProgress = new Progress<long>(totalBytes => progress.Report(
                            new HttpDownloadProgress()
                            {
                                ContentLength = contentLength ?? 0,
                                RelativeProgress = contentLength.HasValue ? Convert.ToInt32(100 * (float)totalBytes / contentLength.Value) : 0,
                                ReadBytes = totalBytes
                            }));
                        // Use extension method to report progress while downloading
                        await download.CopyToAsync(destinationStream, 81920, relativeProgress, cancellationToken);
                        progress.Report(new HttpDownloadProgress()
                        {
                            ContentLength = contentLength ?? 0,
                            RelativeProgress = 1,
                            ReadBytes = contentLength ?? 0 
                        });
                        
                        return destinationStream;
                    }
                }
    

    Example:

     var downloadProgress = new Progress<HttpDownloadProgress>(
                        f =>
                        {
                            var readMbs = f.ReadBytes / 1024 / 1024;
                            var contentMbs = f.ContentLength / 1024 / 1024;
                            Progress = f.RelativeProgress;
                            Message = $"Downloading {pluginVersion.PluginName} ({readMbs}MB/{contentMbs}MB)";
                        });
    
    using (var fileStream = await client.DownloadAsync(
                               serviceEndPoint,
                               response => CreateFileStream(response, outputPath),
                               downloadProgress,
                               token))
    {
        Console.WriteLine($"File name {fileStream.Name}";
    }
    

    with the FileStreamFactory, which creates the output folder and a filestream with the extracted file name of the httpreponse:

    private FileStream CreateFileStream(HttpResponseMessage response, string outputPath)
            {
                // get the actual content stream
                var fileName = response.Content.Headers.ContentDisposition?.FileName;
                if (fileName == null)
                {
                    throw new InvalidOperationException("No filename available");
                }
    
                var outputFile = Path.Combine(outputPath, fileName);
                var folder = Directory.GetParent(outputFile)?.FullName;
                if (folder != null && !Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                else if (File.Exists(outputFile))
                {
                    File.Delete(outputFile);
                }
    
                Console.WriteLine($"Downloading file to '{outputFile} ...");
                return File.Open(outputFile, FileMode.Create);
            }
    
    public class HttpDownloadProgress
    {
        public int RelativeProgress { get; set; }
        public long ContentLength { get; set; }
        public long ReadBytes { get; set; }
    }