Progress bar for HttpClient uploading
Asked Answered
R

2

8

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.

Rubierubiginous answered 15/1, 2014 at 6:17 Comment(0)
T
6

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).

Thimerosal answered 17/1, 2014 at 1:14 Comment(1)
Yes, that seems to work. (I've only tested it in WPF, .net 4.5.1 so far.) Thanks very much!Rubierubiginous
V
0

Simple way to upload a file with progress

I had the same need, and after some tries found out that you can easily get byte-accurate upload progress by tracking the Position of the FileStream of the file that you are going to upload.

Here is one way to do that...

FileStream fileToUpload = File.OpenRead(@"C:\test.mp3");

HttpContent content = new StreamContent(fileToUpload);
HttpRequestMessage msg = new HttpRequestMessage{
    Content=content,
    RequestUri = new Uri(--yourUploadURL--)
}

bool keepTracking = true; //to keep tracking thread running
new Task(new Action(() => { progressTracker(fileToUpload, ref keepTracking); })).Start();
var result = httpClient.SendAsync(msg).Result;
keepTracking = false; //to stop the tracking thread

the function progressTracker() is defined as,

void progressTracker(FileStream streamToTrack, ref bool keepTracking)
{
    int prevPos = -1;
    while (keepTracking)
    {
        int pos = (int)Math.Round(100 * (streamToTrack.Position / (double)streamToTrack.Length));
        if (pos != prevPos)
        {
            Console.WriteLine(pos + "%");

        }
        prevPos = pos;

        Thread.Sleep(100); //only update progress every 100ms
    }
}
Vessel answered 29/6, 2021 at 21:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.