6.21Downloading Network Resources
One of the most common application scenarios for HTTP remote requests is downloading network resources and saving them to the local disk, including downloading web page content, images, compressed packages, installation software, and so on. There are multiple ways to download network resources. The most common is to send an HTTP request, receive the returned Stream, and then write it to the local disk and save it as the corresponding file.
Downloading in the Conventional Way by Receiving the Stream
// Get the response Streamvar stream = await httpRemoteService.GetAsStreamAsync("https://furion.net/img/furionlogo.png");// Create a file stream and write to itusing var fileStream = new FileStream(@"C:\Workspaces\furionlogo.png", FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);await contentStream.CopyToAsync(fileStream);However, this way of downloading network resources is not flexible enough when faced with various complex scenarios — for example, it cannot track download progress in real time, properly handle the case where the file already exists, or implement chunked downloads. In addition, it may require developers to write more additional code. Therefore, the framework integrates features specifically designed for downloading network resources to address these issues.
Downloading with the framework's built-in dedicated download functionality
The following example shows how to use the framework's built-in download functionality to download the ASP.NET Core runtime:
// Downloads the ASP.NET Core runtime from the specified URL and saves it to the C:\Workspaces\ directory// If no file name is specified, the framework automatically resolves the file name from the download URL, for example: 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\"); // To specify a file name, set it to C:\Workspaces\aspnetcore-runtime.exeAfter the file download completes, the framework returns a FileTransferResult object containing the following properties:
IsSuccess: whether the transfer completed successfully (booltype). Note: skipping because the file exists is also considered a success.RequestUri: the file transferURL(stringtype).FilePath: the path of the file (stringtype).FileSize: the size of the file (alongtype, in bytes).ElapsedMilliseconds: the transfer duration (alongtype, in milliseconds).StatusCode: the response status (HttpStatusCodetype).
If the local file already exists, an InvalidOperationException is thrown: System.InvalidOperationException: The destination path 'C:\Workspaces\aspnetcore-runtime-8.0.10-win-x64.exe' already exists.. In this case, you can use the fileExistsBehavior parameter to specify the behavior when the file exists:
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); // Overwrite the file if it existsThe FileExistsBehavior enum contains the following options:
CreateNew(default): if the file already exists, an exception is thrown; otherwise, a new file is created.Overwrite: overwrites the existing file.Skip: keeps the existing file and skips the download operation.
While downloading a file, you can also obtain real-time download progress. The following example shows how to print the download progress:
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()); // Outputs a brief progress string } , fileExistsBehavior: FileExistsBehavior.Overwrite);Example console output of download progress (using progress.ToSummaryString()):
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.To display file download progress in the console in real time, it is recommended to use the UpdateConsoleProgressAsync() method. The example is as follows:
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() // Updates the file transfer progress bar in the console , fileExistsBehavior: FileExistsBehavior.Overwrite);// ✅ Or use the DownloadFileWithConsoleProgressAsync method (with console progress printing) 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);After execution, the console displays the following progress information:
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.If you use progress.ToString(), the console output will contain more detailed progress information:
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.00The progress parameter is of type FileTransferProgress and contains the following properties and methods:
-
Properties:
FilePath: the path of the file (stringtype).FileName: the name of the file (stringtype).FileSize: the size of the file (alongtype, in bytes).Transferred: the amount of data transferred (alongtype, in bytes).PercentageComplete: the percentage of the transfer completed (doubletype).TransferRate: the current transfer rate (adoubletype, in bytes per second).TimeElapsed: the duration from the start of the transfer to now (TimeSpantype).EstimatedTimeRemaining: the estimated remaining transfer time (TimeSpantype).
-
Methods:
ToString(): outputs a detailed, indented progress string.ToStringAsync(): outputs a detailed, indented progress string.ToSummaryString(): outputs a brief progress string.ToSummaryStringAsync(): outputs a brief progress string.UpdateConsoleProgress(): updates (prints) the file transfer progress bar in the console.UpdateConsoleProgressAsync(): updates (prints) the file transfer progress bar in the console.
Downloading multiple files in parallel
The framework natively supports downloading multiple files in parallel. With the ParallelUtility.ForEachAsync utility method, you can easily achieve concurrent downloads and automatically enable multi-line progress bar mode — each file occupies two lines (the file header and the progress bar), and all progress bars refresh in real time on the same screen without interfering with each other:
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\";// Downloads in parallel; the default maximum concurrency is 4await ParallelUtility.ForEachAsync(urls, async (url, token) => { await _httpRemoteService.DownloadFileWithConsoleProgressAsync(url, saveDir, FileExistsBehavior.Overwrite, cancellationToken: token); });After execution, the console displays the download progress of all files at the same time, and each file's progress bar refreshes independently:
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.After all files finish downloading, the progress bars display the Done! status one by one:
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!You can also control the maximum concurrency via the maxDegreeOfParallelism parameter:
await ParallelUtility.ForEachAsync(urls, async (url, token) => { await _httpRemoteService.DownloadFileWithConsoleProgressAsync(url, saveDir, FileExistsBehavior.Overwrite, cancellationToken: token); }, maxDegreeOfParallelism: 2); // Download at most 2 files at the same time