Search code examples
c#windows-runtimewindows-store-appsdotnet-httpclient

HttpClient - Size of downloading file before download


I'm implementing download of files with progress bar. I'm using IAsyncOperationWithProgress for this issue, concretely this code. It is working nice, but I'm receiving only count of bytes that were received/downloaded. But I need calculate percentual part for showing progress. That means I need to know total count of bytes at the start of downloading and I didn't find way how to do this effectively.

Following code resolves progress reporting. I tried to get stream length with responseStream.Length but error "This stream does not support seek operations." was thrown.

static async Task<byte[]> GetByteArratTaskProvider(Task<HttpResponseMessage> httpOperation, CancellationToken token, IProgress<int> progressCallback)
        {
            int offset = 0;
            int streamLength = 0;
            var result = new List<byte>();

            var responseBuffer = new byte[500];

            // Execute the http request and get the initial response
            // NOTE: We might receive a network error here
            var httpInitialResponse = await httpOperation;

            using (var responseStream = await httpInitialResponse.Content.ReadAsStreamAsync())
            {
                int read;

                do
                {
                    if (token.IsCancellationRequested)
                    {
                        token.ThrowIfCancellationRequested();
                    }

                    read = await responseStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);

                    result.AddRange(responseBuffer);

                    offset += read;
                    // here I want to send percents of downloaded data
                    // offset / (totalSize / 100)
                    progressCallback.Report(offset);

                } while (read != 0);
            }

            return result.ToArray();
        }

Do you have any idea how to deal with this issue? Or do you have some another way how to download files with progress reporting through HttpClient? I tried to use BackgroundDownloader as well but it was not sufficient for me. Thank you.


Solution

  • You can look at the value of the Content-Length header returned by the server, which is stored in your case in httpInitialResponse.Content.Headers. You'll have to find the header in the collection with the corresponding key (i.e. "Content-Length")

    You can do it for example like this:

    int length = int.Parse(httpInitialResponse.Content.Headers.First(h => h.Key.Equals("Content-Length")).Value.First());
    

    (You have to make sure first that a Content-Length header has been sent by the server, otherwise the line above will fail with an exception)

    Your code would look like:

    static async Task<byte[]> GetByteArrayTaskProvider(Task<HttpResponseMessage> httpOperation, CancellationToken token, IProgress<int> progressCallback)
    {
        int offset = 0;
        int streamLength = 0;
        var result = new List<byte>();
    
        var responseBuffer = new byte[500];
    
        // Execute the http request and get the initial response
        // NOTE: We might receive a network error here
        var httpInitialResponse = await httpOperation;
        var totalValueAsString = httpInitialResponse.Content.Headers.SingleOrDefault(h => h.Key.Equals("Content-Length"))?.Value?.First());
        int? totalValue = totalValueAsString != null ? int.Parse(totalValueAsString) : null;
    
        using (var responseStream = await httpInitialResponse.Content.ReadAsStreamAsync())
        {
           int read;
           do
           {
               if (token.IsCancellationRequested)
               {
                  token.ThrowIfCancellationRequested();
               }
    
               read = await responseStream.ReadAsync(responseBuffer, 0, responseBuffer.Length);
               result.AddRange(responseBuffer);
    
               offset += read;
               if (totalSize.HasValue)
               {
                  progressCallback.Report(offset * 100 / totalSize);
               }
               //for else you could send back the offset, but the code would become to complex in this method and outside of it. The logic for "supports progress reporting" should be somewhere else, to keep methods simple and non-multi-purpose (I would create a method for with bytes progress and another for percentage progress)
           } while (read != 0);
        }
        return result.ToArray();
    }