Progress bar for HttpClient uploading

2019-04-28 06:20发布

问题:

I want to run asynchronous uploads with a progress bar in WPF (and preferably use PCL for code reuse in Xamarin and SL too.)

I've been trying to use System.Net.HttpClient. Unfortunately, PutAsync doesn't provide any progress notifications. (I know that it does for Windows.Web.Http.HttpClient, but that's not available for WPF, nor in the PCL).

For downloading, its fairly easy to implement your own progress bar, as described here. You just pass the ResponseHeadersRead option, which makes the stream available as soon as the headers are returned, and then you read it in chunk by chunk, incrementing your progress bar as you go. But for uploading, this technique doesn't work - you need to pass all your upload data into PutAsync in one go, so there's no chance to increment your counter.

I've also wondered about using HttpClient.SendAsync instead. I'd hoped I could just treat this like an asynchronous HttpWebRequest (in which you can increment the counter as you write to the HttpWebRequest.GetRequestStream as described here). But unfortunately HttpClient.SendAsync doesn't give you writeable stream, so that doesn't work.

So does HttpClient support uploads with a non-blocked UI and a progress bar? It seems like a modest need. Or is there another class I should be using? Thanks very much.

回答1:

Assuming that HttpClient (and underlying network stack) isn't buffering you should be able to do this by overriding HttpContent.SerializeToStreamAsync. You can do something like the following:

        const int chunkSize = 4096;
        readonly byte[] bytes;
        readonly Action<double> progress;

        protected override async Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context)
        {
            for (int i = 0; i < this.bytes.Length; i += chunkSize)
            {
                await stream.WriteAsync(this.bytes, i, Math.Min(chunkSize, this.bytes.Length - i));
                this.progress(100.0 * i / this.bytes.Length);
            }
        }

In order to avoid being buffered by HttpClient you either need to provide a content length (eg: implement HttpContent.TryComputeLength, or set the header) or enable HttpRequestHeaders.TransferEncodingChunked. This is necessary because otherwise HttpClient can't determine the content length header, so it reads in the entire content to memory first.

On the phone 8 you also need to disable AllowAutoRedirect because WP8 has a bug in the way it handles redirected posts (workaround is to issue a HEAD first, get the redirected URL, then send the post to the final URL with AllowAutoRedirect = false).