11

A am creating a file upload app for Android and iOS using Xamarin PCL and i have managed to implement file upload and some sort of progress bar, but it is not working properly.

I saw some answers on stack overflow for displaying download progress, but i want to notify my users about upload progress and did not find any solution.

Here is my code:

public static async Task<string> PostFileAsync (Stream filestream, string filename, int filesize) {
        var progress = new System.Net.Http.Handlers.ProgressMessageHandler ();

        //Progress tracking
        progress.HttpSendProgress += (object sender, System.Net.Http.Handlers.HttpProgressEventArgs e) => {
            int progressPercentage = (int)(e.BytesTransferred*100/filesize);
            //Raise an event that is used to update the UI
            UploadProgressMade(sender, new System.Net.Http.Handlers.HttpProgressEventArgs(progressPercentage, null, e.BytesTransferred, null));
        };

        using (var client = HttpClientFactory.Create(progress)) {
            using (var content = new MultipartFormDataContent ("------" + DateTime.Now.Ticks.ToString ("x"))) {
                content.Add (new StreamContent (filestream), "Filedata", filename);
                using (var message = await client.PostAsync ("http://MyUrl.example", content)) {
                    var result = await message.Content.ReadAsStringAsync ();
                    System.Diagnostics.Debug.WriteLine ("Upload done");
                    return result;
                }
            }
        }
    }

Some sort of progress is displayed, but when the progress reaches 100%, the file is not uploaded yet. Message "Upload done" is also printed some time after i have received the last progress message.

Maybe the progress is displaying bytes sent out of the device and not already uploaded bytes, so when it says, that it is 100%, all of the bytes are just sent out but not yet received by the server?

Edit: Tried this solution: https://forums.xamarin.com/discussion/56716/plans-to-add-webclient-to-pcl and it works a bit better.

Artūrs Eimanis
  • 518
  • 3
  • 5
  • 22

3 Answers3

11

Try something like this:

I faced same issue. I fixed it by implementing custom HttpContent. I use this object to track percentage of upload progress, you can add an event to and listen it. You should customize SerializeToStreamAsync method.

internal class ProgressableStreamContent : HttpContent
{
    private const int defaultBufferSize = 4096;

    private Stream content;
    private int bufferSize;
    private bool contentConsumed;
    private Download downloader;

    public ProgressableStreamContent(Stream content, Download downloader) : this(content, defaultBufferSize, downloader) {}

    public ProgressableStreamContent(Stream content, int bufferSize, Download downloader)
    {
        if(content == null)
        {
            throw new ArgumentNullException("content");
        }
        if(bufferSize <= 0)
        {
            throw new ArgumentOutOfRangeException("bufferSize");
        }

        this.content = content;
        this.bufferSize = bufferSize;
        this.downloader = downloader;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        Contract.Assert(stream != null);

        PrepareContent();

        return Task.Run(() =>
        {
            var buffer = new Byte[this.bufferSize];
            var size = content.Length;
            var uploaded = 0;

            downloader.ChangeState(DownloadState.PendingUpload);

            using(content) while(true)
            {
                var length = content.Read(buffer, 0, buffer.Length);
                if(length <= 0) break;

                downloader.Uploaded = uploaded += length;

                stream.Write(buffer, 0, length);

                downloader.ChangeState(DownloadState.Uploading);
            }

            downloader.ChangeState(DownloadState.PendingResponse);
        });
    }

    protected override bool TryComputeLength(out long length)
    {
        length = content.Length;
        return true;
    }

    protected override void Dispose(bool disposing)
    {
        if(disposing)
        {
            content.Dispose();
        }
        base.Dispose(disposing);
    }


    private void PrepareContent()
    {
        if(contentConsumed)
        {
            // If the content needs to be written to a target stream a 2nd time, then the stream must support
            // seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target 
            // stream (e.g. a NetworkStream).
            if(content.CanSeek)
            {
                content.Position = 0;
            }
            else
            {
                throw new InvalidOperationException("SR.net_http_content_stream_already_read");
            }
        }

        contentConsumed = true;
    }
}

Refer :

currarpickt
  • 2,255
  • 4
  • 22
  • 36
Yksh
  • 3,899
  • 10
  • 53
  • 101
4

Simplest way to upload file with progress

You can get accurate progress by tracking the Position of the FileStream of the file that you are going to upload.

This demonstrates how 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 start and stop the tracking thread
new Task(new Action(() => { progressTracker(fileToUpload, ref keepTracking); })).Start();
var result = httpClient.SendAsync(msg).Result;
keepTracking = false; //stops the tracking thread

And progressTracker() function 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); //update every 100ms
    }
}
Abraham
  • 5,299
  • 1
  • 20
  • 39
  • +1 very simple indeed! Just one extra detail if `httpClient.SendAsync(msg).Result` throws an exception (e.g. because the upload failed due to a network error) then `keepTracking` will never be set to `false` and the `progressTracker()` function will loop forever! – Eric Mutta Feb 21 '22 at 01:05
-2

This is because you are doing the math wrong.

Change : int progressPercentage = (int)(e.BytesTransferred*100/filesize);

To : int progressPercentage = (int)(e.BytesTransferred/filesize) *100;

use this code instead:

    double bytesOut = double.Parse(e.BytesTransferred.ToString());
        double totalBytes = double.Parse(filesize.ToString());
        double percentage = bytesOut / totalBytes * 100;

or you can simply use e.ProgressPercentage

Ahmed Mujtaba
  • 1,960
  • 3
  • 29
  • 66
  • Nope, that's not the problem. It was because the data was getting buffered and only sent at the end. I ended up using `HttpWebRequest` with `SendChunked` set to true and `AllowWriteStreamBuffering` set to false, then manually writing the data in the request stream. – Artūrs Eimanis Apr 27 '16 at 10:08
  • @MaximV.Pavlov As this is an old issue, i don't remember how i solved this issue, but judging by the edit on my initial problem, i think i might have used the code from the Xamarin forum link. – Artūrs Eimanis Mar 15 '17 at 10:39