0

I am trying to get progress of an api along with the response. ResponseHeadersRead works fine to get the progress but I can't figure out why it doesn't return the response.

Download part

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

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

        string strResp = await response.Content.ReadAsStringAsync();
        Debug.WriteLine(strResp); // Doesn't print anything
    }
}

Reading Stream part

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

    }
}

The code is actually from another answer.

I am just not getting the response. If I use ResponseContentRead I get response but it defeats the purpose of progress.

EDIT

ProcessContentStream code - This part read the response as it comes bit by bit and posts the progress in TriggerProgressChanged

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

Post the progress

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

ProgressChanged is a delegate method.

Project link

Rishab
  • 1,901
  • 2
  • 18
  • 34
  • _"but I can't figure out why it doesn't return the response."_ - probably because you already read the content in `DownloadFileFromHttpResponseMessage`? You're trying to read the same content twice. If you read to the end of the response with `ReadAsStreamAsync`, what else do you expect to find by calling `ReadAsStringAsync`? I would expect nothing. – ProgrammingLlama Apr 06 '19 at 14:34
  • Well I have to read the stream in order to get the progress. Once it's done I need to pass the response or read it as string. What to do in this case? – Rishab Apr 06 '19 at 14:36
  • Can you explain what you mean by "get the progress"? What does `ProcessContentStream` do? – ProgrammingLlama Apr 06 '19 at 14:36
  • C# doesn't readily make the download progress available to user. So you need to write your own download code by grabbing the stream. I need too show download progress to the user in case the file is big. – Rishab Apr 06 '19 at 14:38
  • Then you need to return the response from your custom download method. – ProgrammingLlama Apr 06 '19 at 14:39
  • 1
    See https://stackoverflow.com/a/43169927/3480088 for `ProcessContentStream` code. – Rishab Apr 06 '19 at 14:39

1 Answers1

0

Ok so I found the solution. I needed to read the file on which I wrote the bytes as it was coming in with a StreamReader. Reading file with StreamReader has been explained here.

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

        // Added code
        char[] buffer;
        using (StreamReader sr = new StreamReader(DestinationFilePath))
        {
            buffer = new char[(int)sr.BaseStream.Length];
            await sr.ReadAsync(buffer, 0, (int)sr.BaseStream.Length);
        }
        Debug.WriteLine(new string(buffer)); // Now it prints the response
    }
}
Rishab
  • 1,901
  • 2
  • 18
  • 34
  • Can you please add the `ProcessContentStream` method code and the code where you assign the `DestinationFilePath` because as it is now looks like you are processing the stream from the response, but then you read from the filesystem and it's misleading taking into account the question. – Dimitar Apr 08 '19 at 06:29
  • That will become long but ok. Will do it. – Rishab Apr 08 '19 at 08:13
  • You're using `StreamRader.ReadAsync` incorrectly: the method can return fewer characters than requested, so you need to use a loop - but a better idea is to just use `ReadToEnd` into a `String` directly. Also, if the response is binary data then this code is very wrong because a `char` in C#/.NET is a 16-bit value, not a `byte`. C# is not C. – Dai Nov 04 '21 at 23:24