2

I've been stuck for three weeks with solving a problem in C#. Namely, not so long ago I noticed on the language documentation site that the WebClient class is not recommended to use, it is better to use HttpClient instead. enter image description here This is a good suggestion, but when I started using it, I noticed that it was impossible to get progress on the execution of the process, that is, if I download a file, I will not know how much was downloaded/there was also other technical information that was easily available in WebClient. So here's the question. There is such a piece of code and I can't understand why progressMessageHandler does not output information about the download process and if this is not the best way to get information about the download, what can you advise WITHOUT WebClient?..

string textDownloadProgress;
    private async void DownloadFileAsync()
    {
            string filename = "file.zip";
            var handler = new HttpClientHandler() { AllowAutoRedirect = true };
            var ph = new ProgressMessageHandler(handler);
            var hm = new HttpRequestMessage() { RequestUri = new Uri("URL") };
            var client = new HttpClient(ph) { Timeout = Timeout.InfiniteTimeSpan };

            var progressMessageHandler = new ProgressMessageHandler(new HttpClientHandler());
            progressMessageHandler.HttpReceiveProgress += (_, e) =>
            {
                textDownloadProgress += e.ProgressPercentage;
            };

            using (var filestream = new FileStream(filename, FileMode.Create))
            {
                var netstream = await client.GetStreamAsync(hm.RequestUri);
                await netstream.CopyToAsync(filestream);
            }
     }

I will be very grateful for your help, I have spent a lot of time and effort, but I have not found a decent solution…

1 Answers1

2

Your prolem is that you are creating a new ProgressMessageHandler to assign the event handler, and doing nothing with that object.

Instead assign the event handler to ph, which is the handler you are actually using.

Note that you should ideally put HttpClient in a static field to prevent socket exhaustion (but if not then at least dispose it with using).

Also hm and netstream need disposing. And you shouldn't use async void normally.

private async Task DownloadFileAsync()
{
    string filename = "file.zip";
    using var ph = new ProgressMessageHandler(new HttpClientHandler() { AllowAutoRedirect = true });
    using var client = new HttpClient(ph) { Timeout = Timeout.InfiniteTimeSpan };

    ph.HttpReceiveProgress += (_, e) =>
    {
        textDownloadProgress += e.ProgressPercentage;
    };

    using var hm = new HttpRequestMessage(HttpMethod.Get, new Uri("URL"));
    using var filestream = new FileStream(filename, FileMode.Create);
    using var netstream = await client.GetStreamAsync(hm.RequestUri);
    await netstream.CopyToAsync(filestream);
}
Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • I am grateful to you and you are right, the time is 3 o'clock in the morning and attention to the code is already quite low:( Then can I have one last help? There is a ProgressBar in My WPF program. How can these arguments from HttpReceiveProgress be correctly passed to the ProgressBar? If I directly specify (Conditionally: ProgressBar.Text += e.ProgressPercentage) then an exception is thrown on another thread, although the method is not asynchronous. It is known that the application itself seems to work in one thread, and the code in another... – Varion Drakonov Mar 19 '23 at 02:05
  • 1
    Use `Invoke` to marshal it back. Eg `ProgressBar.Invoke(() => ProgressBar.Text += e.ProgressPercentage);` – Charlieface Mar 19 '23 at 02:07
  • After sitting over Invoke and documentation for a while, I didn't quite understand how to use it correctly… Can you give a little more hints, if possible? Here the very idea is to transfer percentages (TextBlock (Well, or just text)) from this stream to a stream with a UI interface. – Varion Drakonov Mar 19 '23 at 02:51
  • 1
    `Invoke` is used to make a call to a function on the same thread that the control was created (because all access to a WPF control must be on the same thread). Sorry got the wrong syntax (was for Winforms) you need `ProgressBar.Dispatcher.Invoke((percent) => ProgressBar.Text += percent, e.ProgressPercentage);` See also https://stackoverflow.com/questions/1644079/change-wpf-controls-from-a-non-main-thread-using-dispatcher-invoke – Charlieface Mar 19 '23 at 03:48
  • Thank you so much! I have corrected the code that you have given for use with TextBlock (and not only) for future generations of residents :) Code: DownloadTextBlock.Dispatcher.Invoke(() => DownloadTextBlock.Text = "Progress: " + percentage); – Varion Drakonov Mar 19 '23 at 04:11