.NET Core 2.0 MVC多文件上传至Azure Blob进度显示问题咨询
我完全理解你在实现这个功能时的头疼——官方文档在这块确实太简略了,尤其是要同时处理双阶段上传进度+多文件的场景,踩坑是难免的。下面针对你的四个问题逐一解答:
问题1:是否需要两个进度条分别对应客户端到服务器、服务器到Azure的上传?
完全正确!这是两个完全独立的传输阶段:
- 第一阶段是客户端→服务器:用户浏览器把文件传到你的.NET Core服务器,进度反映的是文件从用户本地到你服务器的传输状态
- 第二阶段是服务器→Azure Blob:服务器拿到文件后再上传至Azure存储,这又是一个独立的传输过程
因为两个阶段的速度可能差异很大(比如用户网络慢,或者服务器到Azure的带宽受限),分开展示两个进度能让用户清晰了解上传的整体状态,体验会好很多。
问题2:如何在前端展示每个文件的上传进度?
不建议用AJAX轮询服务器的List[i],更高效的方式是结合前端XHR/Fetch进度监听+服务器端实时推送:
- 客户端→服务器的进度:前端用
FormData封装多文件,通过XMLHttpRequest发送请求,监听progress事件就能实时获取每个文件的上传进度,直接更新对应文件的第一个进度条。示例代码大概是这样:
const formData = new FormData(); // 假设fileInput是你的文件选择控件 fileInput.files.forEach(file => formData.append('videoFile', file)); const xhr = new XMLHttpRequest(); xhr.open('POST', '/YourController/Upload'); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percent = (e.loaded / e.total) * 100; // 更新对应文件的客户端上传进度条 updateClientProgress(file.name, percent); } }); xhr.send(formData);
- 服务器→Azure的进度:这个阶段的进度在服务器端,前端没法直接监听,最好用SignalR(.NET Core 2.0支持)做实时推送:
- 服务器端在处理Azure上传时,把进度通过SignalR推送给前端
- 前端连接SignalR Hub,接收进度消息后更新对应文件的第二个进度条
如果暂时不想用SignalR,也可以把每个文件的Azure上传进度存在服务器的IMemoryCache里,前端定时AJAX请求接口获取进度,但这种轮询方式效率低,延迟高,体验不如实时推送。
问题3:while循环读取字节时,文件是否已在服务器缓存?
没错!当你调用videoFile[i].OpenReadStream()的时候,文件已经被ASP.NET Core框架缓存到服务器了——默认情况下,小于64KB的文件会存在内存里,大于这个值的会存在服务器的临时目录中。你代码里的while循环其实是在读取已经缓存到服务器的文件内容,而不是实时从客户端接收数据。
这也是为什么你没法通过这个循环获取客户端到服务器的上传进度——这个循环处理的是已经上传完成(或部分完成)的缓存内容,不是接收上传的过程。
问题4:如何利用Azure的IProgress实现在进度变化时返回结果?当前Console.WriteLine无法生效。
Console.WriteLine在ASP.NET Core服务器环境(比如IIS、Azure App Service)下确实没用,因为服务器的控制台输出前端根本看不到。要把进度传递给前端,得用实时通信机制,这里推荐用SignalR:
- 先给项目添加SignalR的NuGet包(.NET Core 2.0对应
Microsoft.AspNetCore.SignalR包),然后配置SignalR服务 - 创建一个Hub类,用来向前端发送进度消息:
public class UploadProgressHub : Hub { }
- 在你的Blob服务类中注入
IHubContext<UploadProgressHub>,然后修改UploadBlobAsync的进度回调:
private readonly IHubContext<UploadProgressHub> _hubContext; // 构造函数注入 public YourBlobService(IHubContext<UploadProgressHub> hubContext) { _hubContext = hubContext; } public async Task<bool> UploadBlobAsync(string blobContainer, string blobName, IFormFile file, string connectionId) { CloudBlobContainer container = await GetContainerAsync(blobContainer); CloudBlockBlob blob = container.GetBlockBlobReference(blobName); CancellationToken cancellationToken = new CancellationToken(); IProgress<StorageProgress> progressHandler = new Progress<StorageProgress>(progress => { // 计算进度百分比 var percent = (double)progress.BytesTransferred / file.Length * 100; // 通过SignalR把进度推送给指定客户端 _hubContext.Clients.Client(connectionId).SendAsync("ReceiveAzureProgress", blobName, percent); }); using (var filestream = file.OpenReadStream()) { await blob.UploadFromStreamAsync(filestream, default(AccessCondition), default(BlobRequestOptions), default(OperationContext), progressHandler, cancellationToken); } return true; }
- 前端连接SignalR Hub,监听
ReceiveAzureProgress事件,更新对应文件的Azure上传进度条:
const connection = new signalR.HubConnectionBuilder() .withUrl("/uploadProgressHub") .build(); connection.on("ReceiveAzureProgress", (fileName, percent) => { updateAzureProgress(fileName, percent); }); connection.start().catch(err => console.error(err.toString()));
如果暂时不想用SignalR,也可以把进度存在服务器的IMemoryCache里(用文件名或唯一标识当key),前端定时AJAX请求接口获取,但这种方式体验会差很多。
内容的提问来源于stack exchange,提问作者Ian Gleeson

