You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server上传Azure Blob Storage进度回显方案咨询

解决Blazor Server文件上传进度显示不准确的问题

你的问题核心是:前端进度条只跟踪了浏览器到API服务器的上传进度,而真正耗时的API服务器到Azure Blob Storage的上传进度没同步到UI,导致进度条提前走完。结合你是少量用户的场景,给你两个直接可行的方案:

方案一:跳过API中转,直接在Blazor组件内上传到Azure Blob

Blazor Server的组件运行在服务器端,可以直接调用Azure Blob SDK上传,进度能直接绑定到组件状态,无需跨服务传递进度,适合轻量场景。

实现步骤:

  1. 注入BlobServiceClient到组件(提前在Program.cs配置Azure Blob连接字符串)
  2. 监听InputFile的文件选择事件,直接处理上传逻辑
  3. 用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。

实现步骤:

  1. 创建Scoped进度服务,用于传递进度事件
  2. API控制器注入该服务,在Blob上传时推送进度
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:15:31