C#: HttpClient, прогресс загрузки файла при загрузке нескольких файлов как MultipartFormDataContent

Я использую этот код для загрузки нескольких файлов, и он работает очень хорошо. Он использует библиотеку modernhttpclient.

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

Теперь мне нужен прогресс при загрузке файлов. Я искал в Google и обнаружил, что мне нужно использовать ProgressStreamContent

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

Поскольку ProgressStreamContent содержит конструктор, который принимает поток, я преобразовал MultipartFormDataContent в поток и использовал его в своем конструкторе. Но это не работает. Загрузка не удалась. Я думаю, потому что это поток всех файлов вместе, а это не то, чего ожидает мой бэкэнд.

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

Что я должен сделать здесь, чтобы это работало? Любая помощь приветствуется

2 ответа

Решение

У меня есть рабочая версия ProgressableStreamContent. Обратите внимание, я добавляю заголовки в конструктор, это ошибка в оригинальном ProgressStreamContent, которая не добавляет заголовки!!

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

}

Также обратите внимание, что он ожидает HttpContent, а не поток.

Вот как вы можете его использовать.

 var progressContent = new ProgressableStreamContent (
     requestContent, 
     4096,
     (sent,total) => {
        Console.WriteLine ("Uploading {0}/{1}", sent, total);
    });

Самый простой способ

Вы можете получить точный прогресс, отслеживая Position FileStream файла, который вы собираетесь загрузить.

      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

А также progressTracker() функция определяется как

      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);
    }
}
Другие вопросы по тегам