6.9下载网络资源

HTTP 远程请求最常见的应用场景之一是下载网络资源并将其保存到本地磁盘,这包括下载网页内容、图片、压缩包以及安装软件等。下载网络资源有多种方式,其中最常见的是发送 HTTP 请求,接收返回的 Stream 流,然后将其写入本地磁盘并保存为相应文件。

下载网络资源#

HTTP 远程请求最常见的应用场景之一是下载网络资源并将其保存到本地磁盘,这包括下载网页内容、图片、压缩包以及安装软件等。下载网络资源有多种方式,其中最常见的是发送 HTTP 请求,接收返回的 Stream 流,然后将其写入本地磁盘并保存为相应文件。

使用常规接收 Stream 流方式进行下载#

cs
// 获取响应 Stream 流var stream = await httpRemoteService.GetAsStreamAsync("https://furion.net/img/furionlogo.png");// 创建文件流并写入using var fileStream = new FileStream(@"C:\Workspaces\furionlogo.png", FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);await contentStream.CopyToAsync(fileStream);

然而,这种下载网络资源的方式在面对多种复杂场景时显得不够灵活,例如无法实时追踪下载进度、妥善处理文件已存在的情况,以及实现分片下载等。此外,它还可能需要开发者编写更多的额外代码。因此,框架中集成了专门设计用于下载网络资源的功能,以应对这些问题。

利用框架内置的专用下载功能进行下载#

以下示例展示了如何利用框架内置的下载功能来下载 ASP.NET Core 运行时:

cs
// 从指定 URL 下载 ASP.NET Core 运行时,并保存到 C:\Workspaces\ 目录中// 如果未指定文件名,框架将自动从下载地址中解析出文件名,例如:aspnetcore-runtime-8.0.10-win-x64.exevar fileTransferResult = await httpRemoteService.DownloadFileAsync("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe"    , @"C:\Workspaces\");   // 如需指定文件名可设置为 C:\Workspaces\aspnetcore-runtime.exe

文件下载完成后,框架将返回一个 FileTransferResult 对象,包含以下属性:

  • IsSuccess:传输是否成功完成(bool 类型)。注意:因文件存在而跳过也被视为成功。
  • RequestUri:文件传输 URLstring 类型)。
  • FilePath:文件的路径(string 类型)。
  • FileSize:文件的大小(以字节为单位的 long 类型)。
  • ElapsedMilliseconds:传输耗时(以毫秒为单位的 long` 类型)。
  • StatusCode:响应状态(HttpStatusCode 类型)。

若本地文件已存在,将会抛出 InvalidOperationException 异常,System.InvalidOperationException: The destination path 'C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe' already exists.。此时,您可以通过 fileExistsBehavior 参数来指定文件存在时的行为:

cs
var fileTransferResult = await httpRemoteService.DownloadFileAsync("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe"    , @"C:\Workspaces\"    , fileExistsBehavior: FileExistsBehavior.Overwrite);    // 若文件存在时则覆盖

FileExistsBehavior 枚举包含以下选项:

  • CreateNew(默认值):若文件已存在,则抛出异常;否则,创建新文件。
  • Overwrite:覆盖现有文件。
  • Skip:保留现有文件,并跳过下载操作。

在下载文件时,您还可以获取实时的下载进度。以下示例展示了如何打印下载进度:

cs
var fileTransferResult = await httpRemoteService.DownloadFileAsync("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe"     , @"C:\Workspaces\"     , async progress =>     {         Console.WriteLine(await progress.ToSummaryStringAsync()); // 输出简要进度字符串     }     , fileExistsBehavior: FileExistsBehavior.Overwrite);

下载进度的控制台输出示例(使用 progress.ToSummaryString()):

bash
Transferred 0.26MB of 10.09MB (2.63% complete, Speed: 3.86MB/s, Time: 0.07s, ETA: 2.55s), File: aspnetcore-runtime-8.0.10-win-x64.exe, Path: C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe.Transferred 10.09MB of 10.09MB (100.00% complete, Speed: 9.99MB/s, Time: 1.01s, ETA: 0.00s), File: aspnetcore-runtime-8.0.10-win-x64.exe, Path: C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe.

若需在控制台中实时显示文件下载进度,推荐使用 UpdateConsoleProgressAsync() 方法。示例如下:

cs
 var fileTransferResult = await httpRemoteService.DownloadFileAsync("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe"     , @"C:\Workspaces\"     , progress => progress.UpdateConsoleProgressAsync() // 在控制台中更新文件传输进度条     , fileExistsBehavior: FileExistsBehavior.Overwrite);// ✅ 或使用 DownloadFileWithConsoleProgressAsync 方法(带控制台进度打印) var fileTransferResult = await httpRemoteService.DownloadFileWithConsoleProgressAsync("https://download.visualstudio.microsoft.com/download/pr/a17b907f-8457-45a8-90db-53f2665ee49e/49bccd33593ebceb2847674fe5fd768e/aspnetcore-runtime-8.0.10-win-x64.exe"     , @"C:\Workspaces\"     , fileExistsBehavior: FileExistsBehavior.Overwrite);

执行后,控制台将显示如下进度信息:

bash
File: aspnetcore-runtime-8.0.10-win-x64.exe, Path: C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe[##############################                    ] 61.35% (6.19MB/10.09MB) Speed: 5.81MB/s, Time: 1.07s, ETA: 0.67s.

若使用 progress.ToString(),则控制台输出将包含更详细的进度信息:

bash
Transfer Progress:        File Name:                        aspnetcore-runtime-8.0.10-win-x64.exe        File Path:                        C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe        File Size:                        10.09MB        Transferred:                      0.12MB        Percentage Complete:              1.23%        Transfer Rate:                    2.20MB/s        Time Elapsed (s):                 0.06        Estimated Time Remaining (s):     4.52Transfer Progress:        File Name:                        aspnetcore-runtime-8.0.10-win-x64.exe        File Path:                        C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe        File Size:                        10.09MB        Transferred:                      10.09MB        Percentage Complete:              100.00%        Transfer Rate:                    9.77MB/s        Time Elapsed (s):                 1.03        Estimated Time Remaining (s):     0.00

progress 参数的类型为 FileTransferProgress,包含以下属性和方法:

  • 属性

    • FilePath:文件的路径(string 类型)。
    • FileName:文件的名称(string 类型)。
    • FileSize:文件的大小(以字节为单位的 long 类型)。
    • Transferred:已传输的数据量(以字节为单位的 long 类型)。
    • PercentageComplete:已完成的传输百分比(double 类型)。
    • TransferRate:当前的传输速率(以字节/秒为单位的 double 类型)。
    • TimeElapsed:从开始传输到现在的持续时间(TimeSpan 类型)。
    • EstimatedTimeRemaining:预估的剩余传输时间(TimeSpan 类型)。
  • 方法

    • ToString():输出带缩进的详细进度字符串。
    • ToStringAsync():输出带缩进的详细进度字符串。
    • ToSummaryString():输出简要的进度字符串。
    • ToSummaryStringAsync():输出简要的进度字符串。
    • UpdateConsoleProgress():在控制台中更新(打印)文件传输进度条。
    • UpdateConsoleProgressAsync():在控制台中更新(打印)文件传输进度条。

并行下载多个文件#

框架原生支持并行下载多个文件。借助 ParallelUtility.ForEachAsync 工具方法,可以轻松实现并发下载,并自动启用多行进度条模式——每个文件独占两行(文件头和进度条),所有进度条同屏实时刷新,互不干扰:

cs
var urls = new[]{    "https://img-s.msn.cn/tenant/amp/entityid/AA296jTM.img?w=640&h=1068&m=6",    "https://img-s.msn.cn/tenant/amp/entityid/AA297bnQ.img?w=640&h=1240&m=6&x=236&y=233&s=64&d=64",    "https://img-s.msn.cn/tenant/amp/entityid/AA296Rr4.img?w=640&h=821&m=6"};const string saveDir = @"C:\Workspaces\";// 并行下载,默认最大并发数为 4await ParallelUtility.ForEachAsync(urls, async (url, token) =>    {        await _httpRemoteService.DownloadFileWithConsoleProgressAsync(url, saveDir, FileExistsBehavior.Overwrite, cancellationToken: token);    });

执行后,控制台将同时显示所有文件的下载进度,每个文件的进度条独立刷新:

bash
File: AA296jTM.img, Path: C:\Workspaces\AA296jTM.img[########............] 40.12% (0.05MB/0.12MB) Speed: 1.20MB/s, Time: 42ms, ETA: 58ms.File: AA297bnQ.img, Path: C:\Workspaces\AA297bnQ.img[######..............] 30.05% (0.04MB/0.12MB) Speed: 0.95MB/s, Time: 38ms, ETA: 84ms.File: AA296Rr4.img, Path: C:\Workspaces\AA296Rr4.img[##########..........] 50.33% (0.06MB/0.12MB) Speed: 1.55MB/s, Time: 45ms, ETA: 39ms.

所有文件下载完成后,进度条将逐一显示 Done! 状态:

bash
File: AA296jTM.img, Path: C:\Workspaces\AA296jTM.img[####################] 100.00% (0.12MB/0.12MB) Speed: 2.61MB/s, Time: 167ms. Done!File: AA297bnQ.img, Path: C:\Workspaces\AA297bnQ.img[####################] 100.00% (0.12MB/0.12MB) Speed: 1.81MB/s, Time: 203ms. Done!File: AA296Rr4.img, Path: C:\Workspaces\AA296Rr4.img[####################] 100.00% (0.12MB/0.12MB) Speed: 1.61MB/s, Time: 204ms. Done!

您还可以通过 maxDegreeOfParallelism 参数控制最大并发数:

cs
await ParallelUtility.ForEachAsync(urls, async (url, token) =>    {        await _httpRemoteService.DownloadFileWithConsoleProgressAsync(url, saveDir, FileExistsBehavior.Overwrite, cancellationToken: token);    },    maxDegreeOfParallelism: 2);   // 最多同时下载 2 个文件

HttpFileDownloadBuilder 构建器#

除了上述方法,您还可以使用 HttpFileDownloadBuilder 构建器来配置下载网络资源所需的各项设置。

cs
var fileTransferResult = await httpRemoteService.SendAsync(HttpRequestBuilder.DownloadFile("https://furion.net/img/furionlogo.png", @"C:\Workspaces\"));

HttpFileDownloadBuilder 构建器是框架提供专门用来下载网络资源所需的各项设置。HttpFileDownloadBuilder 的构造函数是私有的,因此无法直接使用 new 关键字进行实例化,不过,框架提供了 HttpRequestBuilder.DownloadFile 的多个静态重载方法创建 HttpFileDownloadBuilder 的实例。

cs
HttpRequestBuilder.DownloadFile(httpMethod, requestUri, destinationPath, onProgressChanged, fileExistsBehavior, configure);HttpRequestBuilder.DownloadFile(requestUri, destinationPath, onProgressChanged, fileExistsBehavior, configure); // 默认 GET 请求

此外,HttpFileDownloadBuilder 包含以下配置功能:

cs
// 默认为 GET 请求。不指定保存的文件名将自动解析文件名,如最终下载路径为 C:\Workspaces\furionlogo.pngHttpRequestBuilder.DownloadFile("https://furion.net/img/furionlogo.png", @"C:\Workspaces\")    // 设置用于传输操作的缓冲区大小,以字节为单位,默认值为 80 KB    .SetBufferSize(80 * 1024)    // 设置文件保存的目标路径,可设置为 null,若为 null 时将获取 HttpRemoteOptions 的 DefaultFileDownloadDirectory 属性或应用程序执行目录进行设置    .SetDestinationPath(@"C:\Workspaces\")    // 设置当目标文件已存在时的行为    .SetFileExistsBehavior(FileExistsBehavior.Overwrite)    // 设置文件传输进度(通知)的间隔时间    .SetProgressInterval(TimeSpan.FromSeconds(1))    // 设置在文件开始传输时的操作    .SetOnTransferStarted(() => {})    // 设置用于传输进度发生变化时执行的委托    .SetOnProgressChanged(async progress => { })    // 设置在文件传输完成时的操作,委托参数为文件传输总花费时间(毫秒)    .SetOnTransferCompleted(duration => {})    // 设置在文件传输发生异常时的操作    .SetOnTransferFailed(exception => {})    // 设置在文件存在且配置为跳过时的操作    .SetOnFileExistAndSkip(() => {})    // 设置 HTTP 文件传输事件处理程序,CustomFileTransferEventHandler 为实现 IHttpFileTransferEventHandler 接口的类型    .SetEventHandler<CustomFileTransferEventHandler>()    .SetEventHandler(typeof(CustomFileTransferEventHandler))    // 设置 HttpRequestBuilder 实例    .With(builder => {})    // 支持扩展更多    // 设置下载最大线程数    .SetMaxThreads(4)    // 设置单次读取数据的最大空闲等待时间(滑动窗口超时)    .SetChunkTimeout(TimeSpan.FromSeconds(100))    // 设置多线程分块下载的最大重试次数    .SetChunkMaxRetries(3)    // 开启高速下载模式    .EnableHighSpeedMode(); // 支持传入下载最大线程数

在通过 HttpRequestBuilder.DownloadFile 方法成功构建 HttpFileDownloadBuilder 实例后,您可以利用 Send 方法或异步的 SendAsync 方法来执行发送操作。

cs
var fileTransferResult = httpRemoteService.Send(httpFileDownloadBuilder, cancellationToken);var fileTransferResult = await httpRemoteService.SendAsync(httpFileDownloadBuilder, cancellationToken);

文件传输事件处理程序#

IHttpFileTransferEventHandler 接口允许您定义下载或上传文件的预处理操作。通过实现该接口,您可以创建自定义的文件传输事件处理程序,例如 CustomFileTransferEventHandler 类:

cs
public class CustomFileTransferEventHandler : IHttpFileTransferEventHandler{    // 在文件开始传输时的操作    public void OnTransferStarted() {}    // 传输进度发生变化时的操作    public Task OnProgressChangedAsync(FileTransferProgress fileTransferProgress) {}    // 在文件传输完成时的操作    public void OnTransferCompleted(long duration) {}    // 在文件传输发生异常时的操作    public void OnTransferFailed(Exception exception) {}}

要在应用程序中启用此处理程序,请在 Startup.csProgram.cs 文件中注册 CustomFileTransferEventHandler 服务:

cs
services.TryAddSingleton<CustomFileTransferEventHandler>();

接下来,您可以在构建 HTTP 请求时指定此处理程序:

cs
HttpRequestBuilder.DownloadFile("https://furion.net/img/furionlogo.png", @"C:\Workspaces\")    .SetEventHandler<CustomFileTransferEventHandler>();HttpRequestBuilder.DownloadFile("https://furion.net/img/furionlogo.png", @"C:\Workspaces\")    .SetEventHandler(typeof(CustomFileTransferEventHandler));    // 使用类型方式设置