Как я могу рассчитать прогресс с помощью HttpClient PostAsync?

В моем приложении Магазина Windows (С#) мне нужно загрузить MultipartFormDataContent (некоторые строки и некоторые файлы) на сервер и получить в ответ огромный файл. Проблема в том, что я не могу использовать BackgroundDownloaders для этого. Я могу использовать только один запрос для этого.

Я использую метод HttpClient.PostAsync:

 using (var client = new HttpClient(httpClientHandler))
            {
                using (var content = new MultipartFormDataContent())
                {
                    content.Add(...); // prepare all strings and files content
                    try
                    {
                        using (var response = await client.PostAsync(url, content))
                        {
                            if (response.StatusCode == HttpStatusCode.OK)
                            {
                                var inputBytes = await response.Content.ReadAsByteArrayAsync();
                                // some operations with inputBytes 
                            }
                            ......
                        }
                    }
                }
            }

Мой вопрос: как я могу рассчитать ход этой операции?

Примечание. Моя цель — Windows 8. И я не могу использовать Windows.Web.Http.HttpClient (минимально поддерживаемый клиент Windows 8.1). Только System.Net.Http.HttpClient


person jimpanzer    schedule 20.03.2014    source источник


Ответы (2)


Я столкнулся с той же проблемой. Я исправил это, реализовав пользовательский HttpContent. Я использую этот объект для отслеживания процента загрузки, вы можете добавить событие и прослушать его. Вы должны настроить метод SerializeToStreamAsync.

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

Просто для справки:

public interface IDownload
{
    event EventHandler<DownloadStateEventArgs> StateChanged;
    event EventHandler<DownloadStateEventArgs> Completed;

    DownloadState State { get; }
    Guid Id { get; }
    string Uri { get; }
    long Filesize { get; }
    long Downloaded { get; }

    Task DownloadAsync();
}
person Medeni Baykal    schedule 11.12.2014
comment
Это решение работает отлично. Просто помните, что если вы используете Xamarin, нужно включить пакет nuget Microsoft.Net.Http в проект .iOS, иначе вы получите исключение TypeLoadException при инициализации этого класса. - person Davide Vosti; 27.10.2015
comment
что такое Скачать загрузчик? - person Drunken Daddy; 29.12.2016
comment
@HeisenBerg это простой объект, который я использую для сообщения о статусе загрузки и т. д. - person Medeni Baykal; 29.12.2016
comment
Спасибо. Не могли бы вы взглянуть на эту тему? stackoverflow.com/questions/41378457/ - person Drunken Daddy; 29.12.2016
comment
Это не работает (уже). Он пройдет весь запрос в SerializeToStreamAsync и только потом начнет использовать полосу пропускания и фактически загружать его. - person Legion; 15.07.2020
comment
Не удается найти DownloadState в Xamarin.tvOS - person Amir Hajiha; 30.05.2021

В nuget клиента WebAPI есть несколько классов для этого. Взгляните на ProgressMessageHandler. Это библиотека PCL, поэтому она должна нормально работать для приложения Магазина Windows.

person Darrel Miller    schedule 20.03.2014