实现在 .net 中使用 HttpClient 下载文件时显示进度

原文:实现在 .net 中使用 HttpClient 下载文件时显示进度

在 .net framework 中,要实现下载文件并显示进度的话,最简单的做法是使用 WebClient 类。订阅 DownloadProgressChanged 事件就行了。

但是很可惜,WebClient 并不包含在 .net standard 当中。在 .net standard 中,要进行 http 网络请求,我们用得更多的是 HttpClient。另外还要注意的是,UWP 中也有一个 HttpClient,虽然用法差不多,但是命名空间是不一样的,而且 UWP 的是可以支持获取下载进度的,这里就不再细说。

如果要下载文件,我们会使用到 HttpClient 的 GetByteArrayAsync 这个方法。要实现下载进度,那要怎么办呢?俗话说,不行就包一层。这里我们写个扩展方法,定义如下:

public static class HttpClientExtensions
{
    public static Task<byte[]> GetByteArrayAsync(this HttpClient client, Uri requestUri, IProgress<HttpDownloadProgress> progress, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }
}

其中 HttpDownloadProgress 是我自己定义的结构体(不使用类的原因我下面再说),代码如下:

public struct HttpDownloadProgress
{
    public ulong BytesReceived { get; set; }

    public ulong? TotalBytesToReceive { get; set; }
}

BytesReceived 代表已经下载的字节数,TotalBytesToReceive 代表需要下载的字节数,因为 http 的响应头不一定会返回长度(content-length),所以这里设置为可空。

由于我们需要从 http 响应头获取到 content-length,而 HttpClient 自身的 GetByteArrayAsync 并没有办法实现,我们需要转向使用 GetAsync 这个方法。GetAsync 这个方法有一个重载 https://docs.microsoft.com/zh-cn/dotnet/api/system.net.http.httpclient.getasync#System_Net_Http_HttpClient_GetAsync_System_Uri_System_Net_Http_HttpCompletionOption_System_Threading_CancellationToken_ 它的第二个参数是一个枚举,代表是什么时候可以得到 response。按照需求,我们这里应该使用 HttpCompletionOption.ResponseHeadersRead 这个。

另外 HttpClient 的源码也可以在 Github 上看得到。https://github.com/dotnet/corefx/blob/d69d441dfb0710c2a34155c7c4745db357b14c96/src/System.Net.Http/src/System/Net/Http/HttpClient.cs 我们可以参考一下 GetByteArrayAsync 的实现。

经过思考,可以写出下面的代码:

public static class HttpClientExtensions
{
    private const int BufferSize = 8192;

    public static async Task<byte[]> GetByteArrayAsync(this HttpClient client, Uri requestUri, IProgress<HttpDownloadProgress> progress, CancellationToken cancellationToken)
    {
        if (client == null)
        {
            throw new ArgumentNullException(nameof(client));
        }

        using (var responseMessage = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
        {
            responseMessage.EnsureSuccessStatusCode();

            var content = responseMessage.Content;
            if (content == null)
            {
                return Array.Empty<byte>();
            }

            var headers = content.Headers;
            var contentLength = headers.ContentLength;
            using (var responseStream = await content.ReadAsStreamAsync().ConfigureAwait(false))
            {
                var buffer = new byte[BufferSize];
                int bytesRead;
                var bytes = new List<byte>();

                var downloadProgress = new HttpDownloadProgress();
                if (contentLength.HasValue)
                {
                    downloadProgress.TotalBytesToReceive = (ulong)contentLength.Value;
                }
                progress?.Report(downloadProgress);

                while ((bytesRead = await responseStream.ReadAsync(buffer, 0, BufferSize, cancellationToken).ConfigureAwait(false)) > 0)
                {
                    bytes.AddRange(buffer.Take(bytesRead));

                    downloadProgress.BytesReceived += (ulong)bytesRead;
                    progress?.Report(downloadProgress);
                }

                return bytes.ToArray();
            }
        }
    }
}

这里我将缓冲区设置为 8192 字节(8 KB),相当于每读取 8 KB 就汇报一次下载进度,当然各位看官也可以把这个值调小,这样效果会更好,但相对的性能就差一些。同时也因为这里 Report 的频率是比较高的,因此 HttpDownloadProgress 不适合用 class(否则 GC 会压力相当大)。

下面我自己的 Demo 的效果图:

原文地址:https://www.cnblogs.com/lonelyxmas/p/11100225.html

时间: 2024-12-13 12:39:10

实现在 .net 中使用 HttpClient 下载文件时显示进度的相关文章

在WebView中加载HTML页面时显示进度对话框的方法

webView.setWebViewClient(new WebViewClient(){            ProgressDialog prDialog;            @Override            public void onPageStarted(WebView view, String url, Bitmap favicon) {                prDialog = ProgressDialog.show(BrowseNewsActivity.t

android中使用Http下载文件并保存到本地SD卡

---恢复内容开始--- 1.AndroidMainfest.xml中设置权限 1 <uses-permission android:name="android.permission.INTERNET"></uses-permission> 2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission

正确处理下载文件时HTTP头的编码问题(Content-Disposition)

转自:https://blog.robotshell.org/2012/deal-with-http-header-encoding-for-file-download/ 最近在做项目时遇到了一个 case :需要实现一个强制下载功能(即强制弹出下载对话框,阻止浏览器尝试解析显示某些文件格式),并且文件名必须保持和用户之前上传时相同(可能包含非 ASCII 字符). 前一个需求很容易实现:使用 HTTP Header 的 Content-Disposition: attachment 即可,还可

Python HTTP下载文件并显示下载进度条

下面的Python脚本中利用request下载文件并写入到文件系统,利用progressbar模块显示下载进度条. 其中利用request模块下载文件可以直接下载,不需要使用open方法,例如: import urllib import requests.packages.urllib3 requests.packages.urllib3.disable_warnings() url = "https://raw.githubusercontent.com/racaljk/hosts/maste

【转】正确处理浏览器在下载文件时HTTP头的编码问题(Content-Disposition)

最近在做项目时遇到了一个 case :需要实现一个强制在浏览器中的下载功能(即强制让浏览器弹出下载对话框),并且文件名必须保持和用户之前上传时相同(可能包含非 ASCII 字符). 前一个需求很容易实现:使用 HTTP Header 的 Content-Disposition: attachment 即可,还可以配合 Content-Type: application/octet-stream 来确保万无一失.而后一个需求就比较蛋疼了,牵扯到 Header 的编码问题(文件名是作为 filena

[ASP.NET][C#]下载文件时中文文件名出现乱码

负责的网站在ASP.NET网页有一段下载Word/Excel文件的程序,最近使用者要求的文件名称内含繁体中文字, 同事用chrome/firefox浏览器测试下载都很正常显示,但用IE(版本11)开启时,却出现了乱码. 1.准备一个中文文件名的文件 ASP.NET MVC 2.在HomeControler中加入下载程序Download public ActionResult Download() { //文件位置 string filepath = @"E:testWebApplication1

用Struts2实现文件上传时显示进度条功能

最近在做一个资源共享的项目中,采用了Struts2.1.8+Spring2.5.6+hibernate3.32的框架整合方式进行开发.在文件上传这块,因为需要实现文件上传时显示进度条的功能,所以尝试了一下.怕以后忘记,先贴出来分享下.   要在上传文件时能显示进度条,首先需要实时的获知web服务端接收了多少字节,以及文件总大小,这里我们在页面上使用AJAX技术每一秒向服务器发送一次请求来获得需要的实时上传信息.但是当我们使用struts2后怎么在服务端获得实时的上传大小呢?这里需要用到commo

GridView中如何在无数据时显示空行和表头

#region ---- 数据绑定 ----- private void bd(int pageindex) { int rowcount = 0; int pagesize = 0; int pagecount = 0; decimal cyfzhj = 0; string Slqstring = Base_Query_Sql; mta.DT(Slqstring); DataTable rs = mta.DT(Slqstring); this.GridView1.DataSource = rs

java中如何设置下载文件

如果想要设置某一url为下载文件的方法如下 需要设置文件响应类型,使用response.setContentType,比如jpeg格式的图片.如果想要访问该页面时出现下载保存的窗口,使用response.setHeader("Content-Disposition", "attachment;filename="+filename.getName())函数.也可以是pdf文件,或者其他格式的,如果想要查找浏览器可以识别什么格式的文件,需要到服务器的配置文件中找,比如