Я хотел бы иметь возможность обновлять процентный индикатор выполнения, чтобы показать, насколько далеко продвинулся мой контроллер в чтении файлов.
Это мой контроллер для скачивания:
[HttpGet("multiple/{*pathsInSharedDrive}")]
public async Task<IActionResult> DownloadFiles(string pathsInSharedDrive)
{
string[] paths = pathsInSharedDrive.Split(',');
var sharedDrivePath = _configuration.GetSection("PathStrings")["SecureFileShare"];
var tempZipPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".zip");
try
{
using (var zipArchive = ZipFile.Open(tempZipPath, ZipArchiveMode.Create))
{
foreach (var path in paths)
{
if (System.IO.File.Exists(path))
{
var relativePath = path.Substring(path.LastIndexOf('/') + 1);
zipArchive.CreateEntryFromFile(path, relativePath);
}
else if (Directory.Exists(path))
{
var files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var relativePath = file.Substring(path.LastIndexOf('/') + 1);
zipArchive.CreateEntryFromFile(file, relativePath);
}
}
}
}
var zipBytes = await System.IO.File.ReadAllBytesAsync(tempZipPath);
return File(zipBytes, "application/zip", "BH-files.zip");
}
finally
{
if (System.IO.File.Exists(tempZipPath))
{
System.IO.File.Delete(tempZipPath);
}
}
}
Я использовал навигацию, чтобы добраться до контроллера
NavManager.NavigateTo($@"api/download/multiple/{controllerParameter}", forceLoad: true);
Могу ли я отправить обратно информацию из контроллера на страницу, которая сообщает ей x файла из y файлов?
Как и в случае с foreach, я просто помещаю переменную I, которая возвращается на мою страницу на каждой итерации, чтобы медленно обновлять индикатор выполнения на странице.
Я попробовал что-то подобное с помощью Javascript:
[HttpGet("multiple/{*pathsInSharedDrive}")]
public async Task DownloadFiles(string pathsInSharedDrive)
{
string[] paths = pathsInSharedDrive.Split(',');
var sharedDrivePath = _configuration.GetSection("PathStrings")["SecureFileShare"];
Response.ContentType = "application/zip";
Response.Headers.Add("Content-Disposition", "attachment; filename=BH-files.zip");
using (var archive = new ZipArchive(Response.BodyWriter.AsStream(), ZipArchiveMode.Create))
{
var progressInfo = new ProgressInfo();
// Calculate total size
foreach (var path in paths)
{
var fullPath = Path.Combine(sharedDrivePath, path);
if (System.IO.File.Exists(fullPath))
{
progressInfo.TotalSize += new FileInfo(fullPath).Length;
}
else if (Directory.Exists(fullPath))
{
progressInfo.TotalSize += Directory.GetFiles(fullPath, "*", SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length);
}
}
Response.Headers.Add("X-Total-Size", progressInfo.TotalSize.ToString());
foreach (var path in paths)
{
var fullPath = Path.Combine(sharedDrivePath, path);
if (System.IO.File.Exists(fullPath))
{
var relativePath = Path.GetFileName(fullPath);
await AddFileToArchive(archive, fullPath, relativePath, progressInfo);
}
else if (Directory.Exists(fullPath))
{
var files = Directory.GetFiles(fullPath, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var relativePath = file.Substring(fullPath.Length + 1);
await AddFileToArchive(archive, file, relativePath, progressInfo);
}
}
}
}
}
private async Task AddFileToArchive(ZipArchive archive, string filePath, string entryName, ProgressInfo progressInfo)
{
var entry = archive.CreateEntry(entryName);
using (var entryStream = entry.Open())
using (var fileStream = System.IO.File.OpenRead(filePath))
{
await fileStream.CopyToAsync(entryStream);
progressInfo.ProcessedSize += fileStream.Length;
Response.Headers["X-Processed-Size"] = progressInfo.ProcessedSize.ToString();
await Response.Body.FlushAsync();
}
}
js
window.downloadWithProgress = function (url, filename) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = function (event) {
var totalSize = xhr.getResponseHeader('X-Total-Size');
var processedSize = xhr.getResponseHeader('X-Processed-Size');
if (totalSize && processedSize) {
var percentComplete = (processedSize / totalSize) * 100;
DotNet.invokeMethodAsync('SecureFileShare', 'UpdateProgress', percentComplete);
}
};
xhr.onload = function () {
if (this.status === 200) {
var blob = new Blob([this.response], { type: 'application/zip' });
var downloadUrl = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
resolve();
} else {
reject(new Error('Download failed'));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
};
На странице я вызываю Javascript:
await JSRuntime.InvokeVoidAsync("downloadWithProgress", url, "BH-files.zip");
Это сработало для загрузки, но
var processedSize = xhr.getResponseHeader('X-Processed-Size');
не обновлялся.
Там должен быть лучший путь. Дайте мне знать, что вы думаете.
Вы можете использовать технологию связи в реальном времени, такую как SignalR, чтобы сервер мог передавать информацию клиенту, удовлетворяя тем самым потребность в обновлениях прогресса в реальном времени и, таким образом, обновляя индикатор выполнения в реальном времени во время процесса загрузки файла. . В этом процессе концентратор SignalR используется для получения и трансляции сообщений об обновлении хода выполнения, а внешний интерфейс подключается к концентратору через клиент SignalR для получения этих сообщений и обновления пользовательского интерфейса. Ниже приведен пример одного пути для справки:
Контроллер:
[ApiController]
[Route("api/[controller]")]
public class HomeController : Controller
{
private readonly IHubContext<MyChatHub> _hubContext;
private readonly IConfiguration _configuration;
public HomeController(IHubContext<MyChatHub> hubContext, IConfiguration configuration)
{
_hubContext = hubContext;
_configuration = configuration;
}
[HttpGet]
public async Task<IActionResult> DownloadFiles(string pathsInSharedDrive)
{
string[] paths = pathsInSharedDrive.Split(',');
var sharedDrivePath = _configuration.GetSection("PathStrings")["SecureFileShare"];
var tempZipPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".zip");
try
{
using (var zipArchive = ZipFile.Open(tempZipPath, ZipArchiveMode.Create))
{
var totalSize = CalculateTotalSize(paths);
foreach (var path in paths)
{
var fullPath = Path.Combine( path);
if (System.IO.File.Exists(fullPath))
{
await AddFileToArchive(zipArchive, fullPath, Path.GetFileName(fullPath), totalSize);
}
else if (Directory.Exists(fullPath))
{
var files = Directory.GetFiles(fullPath, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
await AddFileToArchive(zipArchive, file, file.Substring(fullPath.Length + 1), totalSize);
}
}
}
}
await _hubContext.Clients.All.SendAsync("ReceiveProgress", 100);
var zipBytes = await System.IO.File.ReadAllBytesAsync(tempZipPath);
return File(zipBytes, "application/zip", "BH-files.zip");
}
finally
{
if (System.IO.File.Exists(tempZipPath))
{
System.IO.File.Delete(tempZipPath);
}
}
}
private long CalculateTotalSize(string[] paths)
{
long totalSize = 0;
foreach (var path in paths)
{
var fullPath = Path.Combine(path);
if (System.IO.File.Exists(fullPath))
{
totalSize += new FileInfo(fullPath).Length;
}
else if (Directory.Exists(fullPath))
{
totalSize += Directory.GetFiles(fullPath, "*", SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length);
}
}
return totalSize;
}
private async Task AddFileToArchive(ZipArchive archive, string filePath, string entryName, long totalSize)
{
var entry = archive.CreateEntry(entryName);
using (var entryStream = entry.Open())
using (var fileStream = System.IO.File.OpenRead(filePath))
{
var buffer = new byte[4096];
int bytesRead;
long bytesWritten = 0;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await entryStream.WriteAsync(buffer, 0, bytesRead);
bytesWritten += bytesRead;
int percentComplete = (int)((double)bytesWritten / totalSize * 100);
await _hubContext.Clients.All.SendAsync("ReceiveProgress", percentComplete);
}
}
}
}
Джс:
window.downloadWithProgress = function (url, filename) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = function (event) {
var totalSize = xhr.getResponseHeader('X-Total-Size');
var processedSize = xhr.getResponseHeader('X-Processed-Size');
if (totalSize && processedSize) {
var percentComplete = (processedSize / totalSize) * 100;
DotNet.invokeMethodAsync('BlazorApp3', 'UpdateProgress', percentComplete);
}
};
xhr.onload = function () {
if (this.status === 200) {
var blob = new Blob([this.response], { type: 'application/zip' });
var downloadUrl = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
resolve();
} else {
reject(new Error('Download failed'));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
};
И я смоделировал локальный путь:
@page "/download"
@inject NavigationManager Navigation
@inject IJSRuntime JSRuntime
@using Microsoft.AspNetCore.SignalR.Client
@rendermode InteractiveServer
<h3>Download Progress</h3>
<div>
<button @onclick = "StartDownload">Start Download</button>
</div>
<div>
<progress id = "progressBar" max = "100" value = "@progress"></progress>
<span>@progress %</span>
</div>
@code {
private HubConnection _hubConnection;
private int progress;
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/MyChatHub"))
.Build();
_hubConnection.On<int>("ReceiveProgress", p =>
{
progress = p;
InvokeAsync(StateHasChanged);
});
await _hubConnection.StartAsync();
}
private async Task StartDownload()
{
try
{
var directoryPath = "C:/Users/v-yuningduan/Pictures/Screenshots";
var encodedPath = System.Net.WebUtility.UrlEncode(directoryPath);
var url = $"api/Home?pathsInSharedDrive = {encodedPath}";
await JSRuntime.InvokeVoidAsync("downloadWithProgress", url, "Screenshots.zip");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public async ValueTask DisposeAsync()
{
if (_hubConnection != null)
{
await _hubConnection.DisposeAsync();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./JS/Downloadmethod.js");
}
}
}
После завершения:
Для всех, кто в будущем, я использовал аутентификацию Windows, поэтому мне пришлось добавить _hubConnection = new HubConnectionBuilder() .WithUrl(Navigation.ToAbsoluteUri("/chathub"), options => { options.UseDefaultCredentials = true; }) .Build() ; И когда я использую метод байтового буфера, скорость загрузки действительно увеличивается еще в 10 раз. Поэтому я просто использую zipArchive.CreateEntryFromFile, и это кажется намного быстрее. Если есть какая-либо причина не использовать этот метод, дайте мне знать. Но основная идея как подключиться к хабу отличная!! О да, я изменил js UpdateProgress на Recieve