2013-12-18 5 views
12

У меня есть функция загрузчик файлов:Прогресс бар с HttpClient

 HttpClientHandler aHandler = new HttpClientHandler(); 
     aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; 
     HttpClient aClient = new HttpClient(aHandler); 
     aClient.DefaultRequestHeaders.ExpectContinue = false; 
     HttpResponseMessage response = await aClient.GetAsync(url); 
     InMemoryRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream(); 

     // To save downloaded image to local storage 
     var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(
     filename, CreationCollisionOption.ReplaceExisting); 
     var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); 
     DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0)); 

     writer.WriteBytes(await response.Content.ReadAsByteArrayAsync()); 

     await writer.StoreAsync(); 
     //current.image.SetSource(randomAccessStream); 
     writer.DetachStream(); 
     await fs.FlushAsync(); 

Как я могу реализовать функциональность прогресс бар? Может быть, я могу получить байты писателей до сих пор? Или что-то?

P.S. Я не могу использовать DownloadOperation (перемещение фона), потому что данные из сертификата на сервер запрашивают - и эта функция не существует в DownloadOperations.

+0

Что о 'Windows.Web.Http.HttpClient'. Это поддерживает прогресс. – kiewic

+3

Доступен ли Windows.Web.Http.HttpClient на рабочем столе? Я думал, что это только для приложений для магазина Windows. Который я никогда не видел никого в реальной жизни. – thund

+0

Можно ли использовать Windows.Web.Http.HttpClient в ASP.Net? – tatigo

ответ

21

Лучший способ пойти - использовать Windows.Web.Http.HttpClient вместо System.Net.Http.HttpClient. Первый поддерживает прогресс.

Но если по какой-то причине вы хотите придерживаться System.Net, вам нужно будет реализовать свой собственный прогресс.

Снимите DataWriter, удалите InMemoryRandomAccessStream и добавить HttpCompletionOption.ResponseHeadersRead к GetAsync вызов так он возвращается, как только будут получены заголовки, не тогда, когда весь ответ получен. Т.е .:

// Your original code. 
HttpClientHandler aHandler = new HttpClientHandler(); 
aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic; 
HttpClient aClient = new HttpClient(aHandler); 
aClient.DefaultRequestHeaders.ExpectContinue = false; 
HttpResponseMessage response = await aClient.GetAsync(
    url, 
    HttpCompletionOption.ResponseHeadersRead); // Important! ResponseHeadersRead. 

// To save downloaded image to local storage 
var imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(
    filename, 
    CreationCollisionOption.ReplaceExisting); 
var fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite); 

// New code. 
Stream stream = await response.Content.ReadAsStreamAsync(); 
IInputStream inputStream = stream.AsInputStream(); 
ulong totalBytesRead = 0; 
while (true) 
{ 
    // Read from the web. 
    IBuffer buffer = new Windows.Storage.Streams.Buffer(1024); 
    buffer = await inputStream.ReadAsync(
     buffer, 
     buffer.Capacity, 
     InputStreamOptions.None); 

    if (buffer.Length == 0) 
    { 
     // There is nothing else to read. 
     break; 
    } 

    // Report progress. 
    totalBytesRead += buffer.Length; 
    System.Diagnostics.Debug.WriteLine("Bytes read: {0}", totalBytesRead); 

    // Write to file. 
    await fs.WriteAsync(buffer); 
} 
inputStream.Dispose(); 
fs.Dispose(); 
+2

Спасибо, это будет, но есть ли способ получить общее количество байтов, которые я получу? Чтобы установить максимальный уровень ProgressBar – Cheese

+0

, у меня был файл, поступающий с сервера, поэтому мне удалось это использовать. Спасибо за ваш рабочий код, он мне очень помог – Cheese

+4

Почему бы не использовать ProgressMessageHandler http://msdn.microsoft.com/en-us/library/system.net.http.handlers.progressmessagehandler(v=vs.118).aspx? –

0

Хм, вы могли бы попросить другой поток проверить текущий размер записываемого потока (вы также передадите ему ожидаемый размер файла), а затем соответствующим образом обновите индикатор выполнения.

+0

Я был бы рад, если бы вы могли предоставить пример – Cheese

+0

К сожалению, у меня нет копии Win8, поэтому я не могу проверить вашу функцию. Тем не менее, если вы хотите сделать что-то довольно простым, вы можете сделать имя файла и файл в глобальном масштабе, иметь фонового работника с циклом и потоком, который будет проверять файловую систему на регулярной основе и обновлять индикатор выполнения. Это, однако, не очень элегантное решение. –

+0

Я думаю, что это не сработает. Ничего не записывается в поток до тех пор, пока 'ReadAsByteArrayAsync' не вернется. – kiewic

10

Вот самодостаточный класс, который будет делать загрузку, и сообщить процент прогресса, основанный на коде из TheBlueSky на this SO answer и eriksendc на this GitHub comment.

public class HttpClientDownloadWithProgress : IDisposable 
{ 
    private readonly string _downloadUrl; 
    private readonly string _destinationFilePath; 

    private HttpClient _httpClient; 

    public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); 

    public event ProgressChangedHandler ProgressChanged; 

    public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath) 
    { 
     _downloadUrl = downloadUrl; 
     _destinationFilePath = destinationFilePath; 
    } 

    public async Task StartDownload() 
    { 
     _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) }; 

     using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) 
      await DownloadFileFromHttpResponseMessage(response); 
    } 

    private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response) 
    { 
     response.EnsureSuccessStatusCode(); 

     var totalBytes = response.Content.Headers.ContentLength; 

     using (var contentStream = await response.Content.ReadAsStreamAsync()) 
      await ProcessContentStream(totalBytes, contentStream); 
    } 

    private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) 
    { 
     var totalBytesRead = 0L; 
     var readCount = 0L; 
     var buffer = new byte[8192]; 
     var isMoreToRead = true; 

     using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) 
     { 
      do 
      { 
       var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); 
       if (bytesRead == 0) 
       { 
        isMoreToRead = false; 
        TriggerProgressChanged(totalDownloadSize, totalBytesRead); 
        continue; 
       } 

       await fileStream.WriteAsync(buffer, 0, bytesRead); 

       totalBytesRead += bytesRead; 
       readCount += 1; 

       if (readCount % 100 == 0) 
        TriggerProgressChanged(totalDownloadSize, totalBytesRead); 
      } 
      while (isMoreToRead); 
     } 
    } 

    private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) 
    { 
     if (ProgressChanged == null) 
      return; 

     double? progressPercentage = null; 
     if (totalDownloadSize.HasValue) 
      progressPercentage = Math.Round((double)totalBytesRead/totalDownloadSize.Value * 100, 2); 

     ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); 
    } 

    public void Dispose() 
    { 
     _httpClient?.Dispose(); 
    } 
} 

Использование:

var downloadFileUrl = "http://example.com/file.zip"; 
var destinationFilePath = Path.GetFullPath("file.zip"); 

using (var client = new HttpClientDownloadWithProgress(downloadFileUrl, destinationFilePath)) 
{ 
    client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { 
     Console.WriteLine($"{progressPercentage}% ({totalBytesDownloaded}/{totalFileSize})"); 
    }; 

    await client.StartDownload(); 
} 

Результат:

7.81% (26722304/342028776) 
8.05% (27535016/342028776) 
8.28% (28307984/342028776) 
8.5% (29086548/342028776) 
8.74% (29898692/342028776) 
8.98% (30704184/342028776) 
9.22% (31522816/342028776) 
+0

Отличный ответ и работа! Спасибо –

+0

Пришли, чтобы удалить следующее обновление: if (readCount% 100 == 0) – aherrick

1

Следующий код показывает минимальный пример того, что должно быть сделано против HttpClient API, чтобы получить ход загрузки.

HttpClient client = //... 

// Must use ResponseHeadersRead to avoid buffering of the content 
using (var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)){ 
    // You must use as stream to have control over buffering and number of bytes read/received 
    using (var stream = await response.Content.ReadAsStreamAsync()) 
    { 
     // Read/process bytes from stream as appropriate 

     // Calculated by you based on how many bytes you have read. Likely incremented within a loop. 
     long bytesRecieved = //... 

     long? totalBytes = response.Content.Headers.ContentLength; 
     double? percentComplete = (double)bytesRecieved/totalBytes; 

     // Do what you want with `percentComplete` 
    } 
} 

выше не говорит вам, как обрабатывать поток, как сообщить процесс, или попытаться обеспечить прямое решение кода в исходный вопрос. Тем не менее, этот ответ может быть более доступным для будущих читателей, которые хотят применить прогресс в своем коде.

1

С .Net 4.5 вы можете обрабатывать отчеты о прохождении асинхронов с использованием интерфейса IProgress<T>. Затем вы можете написать метод расширения для загрузки файлов с помощью HttpClient. Обратите внимание, что это расширение зависит от другого расширения для обработки асинхронного потока с отчетами о ходе выполнения.

public static class HttpClientExtensions 
{ 
    public static async Task DownloadAsync(this HttpClient client, string requestUri, Stream destination, IProgress<float> progress = null, CancellationToken cancellationToken = default) { 
     using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead)) { 
      var contentLength = response.Content.Headers.ContentLength; 

      using (var download = await response.Content.ReadAsStreamAsync()) { 

       if (progress == null || !contentLength.HasValue) { 
        await download.CopyToAsync(destination); 
        return; 
       } 

       // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) 
       var relativeProgress = new Progress<long>(totalBytes => progress.Report((float)totalBytes/contentLength.Value)); 
       // Use extension method to report progress while downloading 
       await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken); 
       progress.Report(1); 
      } 
     } 
    } 
} 

С расширением потока для реального прогресса и отчетности:

public static class StreamExtensions 
{ 
    public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default) { 
     if (source == null) 
      throw new ArgumentNullException(nameof(source)); 
     if (!source.CanRead) 
      throw new ArgumentException("Has to be readable", nameof(source)); 
     if (destination == null) 
      throw new ArgumentNullException(nameof(destination)); 
     if (!destination.CanWrite) 
      throw new ArgumentException("Has to be writable", nameof(destination)); 
     if (bufferSize < 0) 
      throw new ArgumentOutOfRangeException(nameof(bufferSize)); 

     var buffer = new byte[bufferSize]; 
     long totalBytesRead = 0; 
     int bytesRead; 
     while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { 
      await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); 
      totalBytesRead += bytesRead; 
      progress?.Report(totalBytesRead); 
     } 
    } 
} 

Затем загрузите файл, используя этот код:

using (var client = new HttpClient()) { 
    client.Timeout = TimeSpan.FromMinutes(5); 

    using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { 
     await client.DownloadAsync(DownloadUrl, file, progress, cancellationToken); 
    } 
} 
+0

Это не позволяет добавить cerrificate – Cheese

+0

Создайте 'HttpClient' с помощью' HttpClientHandler' с правильными параметрами сертификата, применяемыми как вы в вашем вопросе –

Смежные вопросы