Blazor Server上传Azure Blob Storage进度回显方案咨询
解决Blazor Server文件上传进度显示不准确的问题
你的问题核心是:前端进度条只跟踪了浏览器到API服务器的上传进度,而真正耗时的API服务器到Azure Blob Storage的上传进度没同步到UI,导致进度条提前走完。结合你是少量用户的场景,给你两个直接可行的方案:
方案一:跳过API中转,直接在Blazor组件内上传到Azure Blob
Blazor Server的组件运行在服务器端,可以直接调用Azure Blob SDK上传,进度能直接绑定到组件状态,无需跨服务传递进度,适合轻量场景。
实现步骤:
- 注入
BlobServiceClient到组件(提前在Program.cs配置Azure Blob连接字符串) - 监听
InputFile的文件选择事件,直接处理上传逻辑 - 用
Progress<long>跟踪Blob上传进度,通过InvokeAsync(StateHasChanged())更新UI
@inject BlobServiceClient BlobServiceClient @implements IAsyncDisposable <div> <InputFile OnChange="HandleFileSelected" accept=".pdf,.docx,.zip" /> @if (isUploading) { <p>上传进度: @uploadProgress% (@uploadedBytes / 1024 KB / @totalBytes / 1024 KB)</p> <div class="progress" style="height: 20px;"> <div class="progress-bar" style="width: @uploadProgress%; height: 100%;"></div> </div> } </div> @code { private bool isUploading; private int uploadProgress; private long uploadedBytes; private long totalBytes; private CancellationTokenSource? _uploadCts; private async Task HandleFileSelected(InputFileChangeEventArgs e) { var targetFile = e.File; if (targetFile == null) return; isUploading = true; uploadedBytes = 0; totalBytes = targetFile.Size; uploadProgress = 0; _uploadCts = new CancellationTokenSource(); StateHasChanged(); try { // 获取Blob容器客户端 var containerClient = BlobServiceClient.GetBlobContainerClient("your-container-name"); await containerClient.CreateIfNotExistsAsync(); var blobClient = containerClient.GetBlobClient(targetFile.Name); // 绑定进度回调 var progressHandler = new Progress<long>(bytesTransferred => { uploadedBytes = bytesTransferred; uploadProgress = (int)((double)bytesTransferred / totalBytes * 100); // 后台线程更新UI必须调用InvokeAsync InvokeAsync(StateHasChanged); }); // 上传文件到Blob using var fileStream = targetFile.OpenReadStream(maxAllowedSize: 1024 * 1024 * 100); // 限制100MB await blobClient.UploadAsync(fileStream, new BlobUploadOptions { ProgressHandler = progressHandler, CancellationToken = _uploadCts.Token }); } catch (OperationCanceledException) { // 处理上传取消逻辑 } finally { isUploading = false; _uploadCts?.Dispose(); _uploadCts = null; InvokeAsync(StateHasChanged); } } public ValueTask DisposeAsync() { _uploadCts?.Cancel(); _uploadCts?.Dispose(); return ValueTask.CompletedTask; } }
方案二:保留API控制器,用Scoped服务传递进度
如果必须通过API中转,利用Blazor Server的Scoped服务隔离用户进度,通过事件通知同步到UI。
实现步骤:
- 创建Scoped进度服务,用于传递进度事件
- API控制器注入该服务,在Blob上传时推送进度
- Blazor组件订阅服务事件,更新UI
1. 定义Scoped进度服务
public class UploadProgressService { // 进度更新事件:参数为(已上传字节数, 总字节数) public event Action<long, long>? ProgressUpdated; public void UpdateProgress(long uploadedBytes, long totalBytes) { ProgressUpdated?.Invoke(uploadedBytes, totalBytes); } }
2. 注册服务到Program.cs
builder.Services.AddScoped<UploadProgressService>(); // 同时确保已注册BlobServiceClient和HttpClient
3. 修改API控制器推送进度
[ApiController] [Route("api/upload")] public class UploadController : ControllerBase { private readonly BlobServiceClient _blobServiceClient; private readonly UploadProgressService _progressService; public UploadController(BlobServiceClient blobServiceClient, UploadProgressService progressService) { _blobServiceClient = blobServiceClient; _progressService = progressService; } [HttpPost] public async Task<IActionResult> Upload(IFormFile file) { if (file == null || file.Length == 0) return BadRequest("未选择上传文件"); var containerClient = _blobServiceClient.GetBlobContainerClient("your-container-name"); await containerClient.CreateIfNotExistsAsync(); var blobClient = containerClient.GetBlobClient(file.FileName); var progressHandler = new Progress<long>(bytesTransferred => { // 推送进度到Scoped服务 _progressService.UpdateProgress(bytesTransferred, file.Length); }); using var stream = file.OpenReadStream(); await blobClient.UploadAsync(stream, new BlobUploadOptions { ProgressHandler = progressHandler }); return Ok("上传完成"); } }
4. Blazor组件订阅进度更新
@inject HttpClient HttpClient @inject UploadProgressService ProgressService @implements IDisposable <div> <InputFile OnChange="TriggerApiUpload" /> @if (isUploading) { <p>上传进度: @uploadProgress% (@uploadedBytes / 1024 KB / @totalBytes / 1024 KB)</p> <div class="progress" style="height: 20px;"> <div class="progress-bar" style="width: @uploadProgress%; height: 100%;"></div> </div> } </div> @code { private bool isUploading; private int uploadProgress; private long uploadedBytes; private long totalBytes; protected override void OnInitialized() { // 订阅进度事件 ProgressService.ProgressUpdated += UpdateUploadProgress; } private void UpdateUploadProgress(long uploaded, long total) { uploadedBytes = uploaded; totalBytes = total; uploadProgress = (int)((double)uploaded / total * 100); InvokeAsync(StateHasChanged); } private async Task TriggerApiUpload(InputFileChangeEventArgs e) { var targetFile = e.File; if (targetFile == null) return; isUploading = true; uploadedBytes = 0; totalBytes = targetFile.Size; uploadProgress = 0; StateHasChanged(); try { using var content = new MultipartFormDataContent(); using var fileStream = targetFile.OpenReadStream(maxAllowedSize: 1024 * 1024 * 100); content.Add(new StreamContent(fileStream), "file", targetFile.Name); var response = await HttpClient.PostAsync("api/upload", content); response.EnsureSuccessStatusCode(); } catch (Exception ex) { // 处理上传错误 } finally { isUploading = false; StateHasChanged(); } } public void Dispose() { // 取消事件订阅,避免内存泄漏 ProgressService.ProgressUpdated -= UpdateUploadProgress; } }
内容的提问来源于stack exchange,提问作者johnm
相关产品推荐
相关产品推荐

