6

I'm using this code to upload multiple files and it working very well. It uses modernhttpclient library.

public async Task<string> PostImages (int platform, string url, List<byte []> imageList)
{
    try {
        int count = 1;
        var requestContent = new MultipartFormDataContent ();

        foreach (var image in imageList) {
            var imageContent = new ByteArrayContent (image);
            imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse ("image/jpeg");
            requestContent.Add (imageContent, "image" + count, "image.jpg");
            count++;
        }
        var cookieHandler = new NativeCookieHandler ();
        var messageHandler = new NativeMessageHandler (false, false, cookieHandler);
        cookieHandler.SetCookies (cookies);
        using (var client = new HttpClient (messageHandler)) {
            client.DefaultRequestHeaders.TryAddWithoutValidation ("User-Agent", GetUserAgent (platform));
            using (var r = await client.PostAsync (url, requestContent)) {
                string result = await r.Content.ReadAsStringAsync ();
                System.Diagnostics.Debug.WriteLine ("PostAsync: " + result);
                return result;
            }
        }
    } catch (Exception e) {
        System.Diagnostics.Debug.WriteLine (e.Message);
        return null;
    }
}

Now I need the progress when uploading the files. I searched in google and found I need to use ProgressStreamContent

https://github.com/paulcbetts/ModernHttpClient/issues/80

Since ProgressStreamContent contains a constructor that takes a stream, I converted the MultipartFormDataContent to stream and used it in its constructor. But, its not working. Upload fails. I think its because it is a stream of all the files together which is not what my back end is expecting.

public async Task<string> PostImages (int platform, string url, List<byte []> imageList)
{
    try {
        int count = 1;
        var requestContent = new MultipartFormDataContent ();
            //    here you can specify boundary if you need---^
        foreach (var image in imageList) {
            var imageContent = new ByteArrayContent (image);
            imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse ("image/jpeg");
            requestContent.Add (imageContent, "image" + count, "image.jpg");
            count++;
        }
        var cookieHandler = new NativeCookieHandler ();
        var messageHandler = new NativeMessageHandler (false, false, cookieHandler);
        cookieHandler.SetCookies (RestApiPaths.cookies);


        var stream = await requestContent.ReadAsStreamAsync ();

        var client = new HttpClient (messageHandler);
        client.DefaultRequestHeaders.TryAddWithoutValidation ("User-Agent", RestApiPaths.GetUserAgent (platform));

        var request = new HttpRequestMessage (HttpMethod.Post, url);

        var progressContent = new ProgressStreamContent (stream, 4096);
        progressContent.Progress = (bytes, totalBytes, totalBytesExpected) => {
            Console.WriteLine ("Uploading {0}/{1}", totalBytes, totalBytesExpected);
        };

        request.Content = progressContent;

        var response = await client.SendAsync (request);
        string result = await response.Content.ReadAsStringAsync ();

        System.Diagnostics.Debug.WriteLine ("PostAsync: " + result);

        return result;

    } catch (Exception e) {
        System.Diagnostics.Debug.WriteLine (e.Message);
        return null;
    }
}

What should I do here to get this working? Any help is appreciated

Drunken Daddy
  • 7,326
  • 14
  • 70
  • 104

1 Answers1

18

I have a working version of ProgressableStreamContent. Please note, I am adding headers in the constructor, this is a bug in original ProgressStreamContent that it does not add headers !!

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);
    }

}

Also note, it expects HttpContent, not stream.

This is how you can use it.

 var progressContent = new ProgressableStreamContent (
     requestContent, 
     4096,
     (sent,total) => {
        Console.WriteLine ("Uploading {0}/{1}", sent, total);
    });
Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • This does not work (anymore). It will go through the whole request in "SerializeToStreamAsync" and only then start using the bandwidth and actually uploading it. In Xamarin at least. – Legion Jul 15 '20 at 16:46
  • It could be but in underlying http client implementation, which implementation are you using? – Akash Kava Jul 16 '20 at 02:19
  • Sorry, I was on vacation. I'm using Xamarin Forms (Mono) System.Net.Http – Legion Aug 04 '20 at 08:07
  • I am using OkHttp based HttpClient on Xamarin Android and it does work well. – Akash Kava Aug 04 '20 at 10:12
  • Ok. What about iOS? – Legion Aug 04 '20 at 11:50
  • @AkashKava How can I use this with MemoryStream type content? – Abdullah Akçam Dec 10 '20 at 17:16
  • @Legion for iOS, it is recommended not to use HttpClient and use iOS’s background upload as it works even in background. – Akash Kava Dec 11 '20 at 02:30
  • @Nerdvan you can create StreamContent from MemoryStream and use it as content. – Akash Kava Dec 11 '20 at 02:31
  • @AkashKava I tried to use this and found a issue. ie, when uploading file larger than 2 GB, the progress is not properly updated. possibly because of line content.ReadAsStreamAsync() which is trying to read full data to memory. I am adding StreamContent to Multipartdata which is to upload directly instead of loading entire file into memory. (ie , multipartdata.add(new StreamContent(xyz),....) – user1066231 Oct 13 '21 at 14:43
  • @user1066231 First if all never upload gigabytes from mobile. Instead use upload blocks, in our applications we upload 4mb parts and then combine all parts on the server. Try to have look at Azure blob upload with committing smaller units. Also the upload works only if underlying implementation streams data. You will have to change underlying implementation to support progress without loading in memory. – Akash Kava Oct 13 '21 at 16:04
  • @AkashKava thanks for the response., In my case I am developing PC only Web application. yes, understood that this class does not support progress without loading in memory. – user1066231 Oct 13 '21 at 16:18