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

4
  • You have to use MultiPartContent, not ByteArrayContent Commented Dec 29, 2016 at 11:32
  • I've the files as byte array. Commented Dec 29, 2016 at 12:01
  • Server does not accept byte array, http server mostly requires Multi Part form encoded content, it is how server expects the data, not how you are sending Commented Dec 29, 2016 at 13:04
  • I'm adding each ByteArrayContent to the MultipartFormDataContent. Please see the code, and it is working as I said in the beginning of my post. Commented Dec 30, 2016 at 6:00

1 Answer 1

19

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);
    });
Sign up to request clarification or add additional context in comments.

11 Comments

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.
It could be but in underlying http client implementation, which implementation are you using?
Sorry, I was on vacation. I'm using Xamarin Forms (Mono) System.Net.Http
I am using OkHttp based HttpClient on Xamarin Android and it does work well.
Ok. What about iOS?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.