C# RestSharp 库 - 加载大型 "application/octect-stream" 时报告进度

C# RestSharp Library - Report Progress when loading a large "application/octect-stream"

我知道这个问题已提交多次,但最后的答案已过时,我想知道今天是否有可用的解决方案?这不是功能请求。我宁愿寻找任何可行的解决方法。

我在与我的 API 通信的客户端上使用 RestSharp。 API 以 "application/octect-stream" 回复,下载可能需要几分钟时间。 Link 到 GitHub code here.

public void Download()
{
     if (NewerApp == null) return;

      var client = new RestClient(ServerAddress);
      var request = new RestRequest("/Apps/", Method.POST);
      request.AddParameter("id", CurrentApp.EncryptedId());
      request.AddParameter("action", "download");

      var asyncHandle = client.ExecuteAsync<App>(request, response => {
          HandleResponseToDownloadRequest(response);
      });
}

我需要向我的 UI 报告 "response" 的接收进度,以构建进度条或类似的东西。我已经知道要接收的预期数据量(通过之前的 API 响应),我只需要知道在接收和解析完整响应之前接收了多少字节。

我认为 RestSharp 目前不提供 'report progress' 类型的事件,对吧?我可以看到几种方法:

  1. 使用client.DownloadData(请求).SaveAs(路径);。可能文件是在下载时创建的。我可以读取文件的大小来报告下载进度。但我的第一印象是客户端先下载数据,然后保存文件。那样的话,那也无济于事了。
  2. 使用流加载响应。定期或每次缓冲区大小扩展时评估流的大小。
  3. 将响应类型从 API 更改为另一种(例如,在 JSON 中发送数据?)。
  4. 还有其他选择吗?

你怎么看?有人设法报告上传进度吗?

我可以在 ExecuteAsync 仍在执行时访问 'response' 的 RawBytes 吗?我应该改用 Execute(不使用 Asyn)吗?我应该在定期更新时使用流和更新观察流的大小吗?

我设法获得了进度报告,但没有使用 RestSharp。我在kievic's answer here的基础上使用了System.Net.HttpClient。正如 kievic 强调的那样,这里的关键是 aClient.SendAsync returns 一旦 HTTP headers 被接收和读取,但内容仍在加载。然后内容通过 while 循环中的流缓慢加载。 RestSharp 似乎没有启用此功能,或者我无法实现。

public async Task DownloadAsync(Action<bool> callback)
    {
        if (NewerApp == null) return;

        // Your original code.
        HttpClientHandler aHandler = new HttpClientHandler();
        aHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
        HttpClient aClient = new HttpClient(aHandler);
        aClient.DefaultRequestHeaders.ExpectContinue = false;
        HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, ServerAddress + "/Apps/");
        string content = "id=" + CurrentApp.EncryptedId() + "&action=download";
        message.Content = new StringContent(content);
        HttpResponseMessage response = await aClient.SendAsync(message,
            HttpCompletionOption.ResponseHeadersRead); // Important! ResponseHeadersRead.

        // New code.
        Stream stream = await response.Content.ReadAsStreamAsync();
        MemoryStream memStream = new MemoryStream();

        // Start reading the stream
        var res = stream.CopyToAsync(memStream);

        // While reading the stream
        while (true)
        {
            // Report progress
            this.DownloadedSize = memStream.Length;
            this.Progress = 100.0 * (double)memStream.Length / (double)NewerApp.Filesize;

            // Leave if no new data was read
            if (res.IsCompleted)
            {
                // Report progress one last time
                this.DownloadedSize = memStream.Length;
                this.Progress = 100.0 * (double)memStream.Length / (double)NewerApp.Filesize;

                break;
            }
        }

        // Get the bytes from the memory stream
        byte[] responseContent = new byte[memStream.Length];
        memStream.Position = 0;
        memStream.Read(responseContent, 0, responseContent.Length);

        // Function has ended - return whether the app was donwloaded
        // properly and verified, or not
        callback(HandleResponseToDownloadRequest(responseContent));
    }

每次 this.DownloadedSizethis.Progress 被分配一个新值时,它们都会触发一个事件,该事件可以被 UI.

抓住
        private double progress = 0;
    /// <summary>
    /// Progress of the download of the App. From 0.0 (%) to 100.0 (%)
    /// </summary>
    public double Progress
    {
        get { return progress; }
        set
        {
            // Max / Min
            double val = value;
            if (val > 100.0) val = 100;
            else if (val < 0.0) val = 0.0;

            // Assign value
            if (progress != val)
            {
                progress = val;
                OnProgressReport("Progress");
                OnPropertyChanged("Progress");
            }
        }
    }

    public long downloadedSize = 0;
    /// <summary>
    /// Quantity of bytes downloaded of the app.
    /// Note: there can be more bytes downloaded than advertized because
    /// the quantity of advertize filesize is not encrypted while the
    /// received bytes are encrypted.
    /// TODO: advertize the size of the encrypted file.
    /// </summary>
    public long DownloadedSize
    {
        get
        {
            return downloadedSize;
        }
        set
        {
            if (downloadedSize != value)
            {
                downloadedSize = value;
                OnDownloadedSizeReport("DownloadedSize");
            }
        }
    }

/// <summary>
    /// Fired when the progress of the download of the app file
    /// has updated (more bytes received).
    /// </summary>
    public event PropertyChangedEventHandler ProgressReport;
    protected void OnProgressReport(string name)
    {
        PropertyChangedEventHandler handler = ProgressReport;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    /// <summary>
    /// Fired when the progress of the download of the app file
    /// has updated (more bytes received).
    /// </summary>
    public event PropertyChangedEventHandler DownloadedSizeReport;
    protected void OnDownloadedSizeReport(string name)
    {
        PropertyChangedEventHandler handler = DownloadedSizeReport;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

我这样调用 DownloadAsync

// Start the download - await makes the execution of the method in background
// so that UI can refresh and keep responsive.
// downloaded: bool, true if file properly downloaded
// Updater_AppDownloaded: function called once download has ended (failed or succeeded, either way)
await System.Threading.Tasks.Task.Run(() => 
     Updater.DownloadAsync(downloaded =>     
         Updater_AppDownloaded(downloaded)));