0

Hello according to : https://gist.github.com/HirbodBehnam/272aa5e4b82c2fb05583d095f2224861

I implemented this :

My call :

public async Task<string> UploadFiles(FileInfo fileInfo)
    {
        string res = null; 

        using (var client = new HttpClient())
        using (var multiForm = new MultipartFormDataContent())
        {
            client.Timeout = TimeSpan.FromMinutes(5); // You may need this if you are uploading a big file

            var file = new ProgressableStreamContent(new StreamContent(File.OpenRead(fileInfo.FullName))
                , (sent, total) => {
                    //Console.SetCursorPosition(1, 0); // Remove last line
                    Console.WriteLine("\bUploading " + ((float)sent / total) * 100f);
                });

            multiForm.Add(file, fileInfo.Name, fileInfo.Name); // Add the file

            var uploadServiceBaseAdress = "http://10.0.2.2:44560/PostFiles/";

            var response = await client.PostAsync(uploadServiceBaseAdress, multiForm);
            Console.WriteLine(response.StatusCode);
            if (response.StatusCode == HttpStatusCode.OK)
            {
                res = await response.Content.ReadAsStringAsync();
                Console.WriteLine(res);

            }

            return res;
        }

My class : Progressable stream content

internal class ProgressableStreamContent:HttpContent
{
    /// <summary>
    /// Lets keep buffer of 20kb
    /// </summary>
    private const int defaultBufferSize = 5 * 4096;

    private HttpContent content;
    private int bufferSize;
    //private bool contentConsumed;
    private Action<long, long> progress;

    public ProgressableStreamContent(HttpContent content, Action<long, long> progress) : this(content, defaultBufferSize, progress) { }

    public ProgressableStreamContent(HttpContent content, int bufferSize, Action<long, long> progress)
    {
        if (content == null)
        {
            throw new ArgumentNullException("content");
        }
        if (bufferSize <= 0)
        {
            throw new ArgumentOutOfRangeException("bufferSize");
        }

        this.content = content;
        this.bufferSize = bufferSize;
        this.progress = progress;

        foreach (var h in content.Headers)
        {
            this.Headers.Add(h.Key, h.Value);
        }
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {

        return Task.Run(async () =>
        {
            var buffer = new Byte[this.bufferSize];
            long size;
            TryComputeLength(out size);
            var uploaded = 0;


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

                    //downloader.Uploaded = uploaded += length;
                    uploaded += length;
                    progress?.Invoke(uploaded, size);

                    //System.Diagnostics.Debug.WriteLine($"Bytes sent {uploaded} of {size}");

                    stream.Write(buffer, 0, length);
                    stream.Flush();
                }
            }
            stream.Flush();
        });
    }

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

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

It bind in the constructor of ProgressableStreamContent. Then going back to my upload file method but when it come to return it crash : Operation not supported on this platform.

I think there is something that I dont understand and I didn't found any docs related to this. So could you explain me whats wrong ?

guizmo
  • 95
  • 1
  • 10

2 Answers2

1

You may try something like this :

using (var multiForm = new MultipartFormDataContent())
    {
      byte[] fileBytes = null;// your file
      var file = new ByteArrayContent(fileBytes);

       ...

      multiForm.Add(file, fileInfo.Name, fileInfo.Name); // Add the file
      var progressContent = new ProgressableStreamContent(multiForm,4096, (sent, total) => {
                //Console.SetCursorPosition(1, 0); // Remove last line
                Console.WriteLine("\bUploading " + ((float)sent / total) * 100f);
            });

       ...

      var response = await client.PostAsync(uploadServiceBaseAdress, progressContent );

       ...
    }
Leo Zhu
  • 15,726
  • 1
  • 7
  • 23
  • Same thing. I thing that I didn't understanding how this Is supposed to work.The progressable stream content Is supposed to convert the http request to a stream ? I was looking for doc or anything but nothing to explain that – guizmo Mar 24 '20 at 00:02
  • 1
    you could refer to https://stackoverflow.com/questions/41378457/c-httpclient-file-upload-progress-when-uploading-multiple-file-as-multipartfo – Leo Zhu Mar 24 '20 at 01:21
  • Thank you for your help I found the solution to my problem @Leo Zhu – guizmo Mar 24 '20 at 20:38
0

I think that this is because I didn't put a breakpoint in the SerializeToStreamAsync method. Then I couldn't see the upload loop when I was debugging.

Moreover the SerializeToStreamAsync is called by the HttpClient when it required.

"The SerializeToStream method will copy the buffered stream to the network stream when it is required by HttpClient"

Check this post for an other example, Darrel Miller is giving more information about this : Posting a Custom type with HttpClient

Here is my view :

public partial class SomeView : ContentPage
{

    public SomeView()
    {
        InitializeComponent();

        //YOUR CONSTRUCTOR CODE 

    }

    async void StartUploadHandler(object sender, System.EventArgs e)
    {
        styledProgressBar.Progress = 0;

        var fileInfo = (FileInfo)FilesList.SelectedItem;

        Progress<UploadBytesProgress> progressReporter = new Progress<UploadBytesProgress>();

        progressReporter.ProgressChanged += (s, args) => UpdateProgress((double)(100 * args.PercentComplete) / 100);

        fileEndpoint.UploadFiles(fileInfo, progressReporter);
    }

    void UpdateProgress(double obj)
    {
        styledProgressBar.Progress = obj;
    }
}

The Custom HttpContent :

internal class ProgressableStreamContent:HttpContent
{
    /// <summary>
    /// Lets keep buffer of 20kb
    /// </summary>
    private const int defaultBufferSize = 5 * 4096;

    private HttpContent content;
    private int bufferSize;
    //private bool contentConsumed;
    private Action<long, long> progress;

    public ProgressableStreamContent(HttpContent content, Action<long, long> progress) : this(content, defaultBufferSize, progress) { }

    public ProgressableStreamContent(HttpContent content, int bufferSize, Action<long, long> progress)
    {
        if (content == null)
        {
            throw new ArgumentNullException("content");
        }
        if (bufferSize <= 0)
        {
            throw new ArgumentOutOfRangeException("bufferSize");
        }

        this.content = content;
        this.bufferSize = bufferSize;
        this.progress = progress;

        foreach (var h in content.Headers)
        {
            this.Headers.Add(h.Key, h.Value);
        }
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {

        return Task.Run(async () =>
        {
            var buffer = new Byte[this.bufferSize];
            long size;
            TryComputeLength(out size);
            var uploaded = 0;


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

                    //downloader.Uploaded = uploaded += length;
                    uploaded += length;
                    progress?.Invoke(uploaded, size);

                    //System.Diagnostics.Debug.WriteLine($"Bytes sent {uploaded} of {size}");

                    stream.Write(buffer, 0, length);
                    stream.Flush();
                }
            }
            stream.Flush();
        });



    }

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

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

The Custom Upload Progress Model for the reporter :

public class UploadBytesProgress
{
    public UploadBytesProgress(string fileName, int bytesSended, int totalBytes)
    {
        Filename = fileName;
        BytesSended = bytesSended;
        TotalBytes = totalBytes;
    }

    public int TotalBytes { get; private set; }

    public int BytesSended { get; private set; }

    public float PercentComplete { get { return (float)BytesSended / TotalBytes; } }

    public string Filename { get; private set; }

    public bool IsFinished { get { return BytesSended == TotalBytes; } }
}

And The http call :

public async Task<string> UploadFiles(FileInfo fileInfo, IProgress<UploadBytesProgress> progessReporter)
    {
        string res = null;
        IAndroidFileHelper androidFileHelper = DependencyService.Get<IAndroidFileHelper>();

        using (var client = new HttpClient())
        {
            using (var multiForm = new MultipartFormDataContent())
            {
                var bytesFile = androidFileHelper.LoadLocalFile(fileInfo.FullName);
                ByteArrayContent byteArrayContent = new ByteArrayContent(bytesFile);

                multiForm.Add(byteArrayContent, fileInfo.Name, fileInfo.Name);

                var progressContent = new ProgressableStreamContent(multiForm, 4096, (sent, total) =>
                {
                    UploadBytesProgress args = new UploadBytesProgress("SERVEUR URL FOR UPLOAD", (int)sent, (int)total);
                    progessReporter.Report(args);
                });


                var uploadServiceBaseAdress = "SERVEUR URL FOR UPLOAD";

                var response = await client.PostAsync(uploadServiceBaseAdress, progressContent);
                Console.WriteLine(response.StatusCode);

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    res = await response.Content.ReadAsStringAsync();
                    Console.WriteLine(res);

                }
                else
                {
                    throw new Exception(response.ReasonPhrase);
                }

                return res;
            }
        }
    }

Howewer there is an other way to upload file with progress report :

https://learn.microsoft.com/fr-fr/dotnet/api/system.net.webclient.uploadprogresschanged?view=netframework-4.8

https://forums.xamarin.com/discussion/comment/199958/#Comment_199958

Hope it will help some people facing the same situation than me.

guizmo
  • 95
  • 1
  • 10