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

Blazor WASM:无需复制数据到其他流,如何显示文件上传进度

Blazor WASM小文件Multipart上传进度实现方案

方案优劣对比

针对你提到的10个左右、单文件1-2MB的场景,两种方案的优劣势很明确:

  • ByteArrayContent:实现简单,直接将文件读入字节数组后,可通过计算已发送字节数追踪进度;但会把所有文件预加载到内存(总占用10-20MB),虽然WASM内存能承受,但扩展性差,后续文件数量/大小增加时内存开销会线性增长。
  • StreamContent:无需预加载整个文件到内存,流式上传更高效,内存占用低;缺点是原生没有进度回调,需要自行包装Stream实现。

结论:StreamContent方案更优——仅需少量额外代码,就能获得更高效的内存使用,且小文件场景下性能损失可忽略,扩展性更好。

StreamContent的进度追踪实现

核心思路是包装原始文件流,在读取数据时触发进度回调。以下是完整实现代码:

1. 自定义进度追踪流

public class ProgressStream : Stream
{
    private readonly Stream _innerStream;
    private readonly Action<long, long> _progressCallback;
    private long _bytesSent;

    public ProgressStream(Stream innerStream, Action<long, long> progressCallback)
    {
        _innerStream = innerStream;
        _progressCallback = progressCallback;
    }

    public override bool CanRead => _innerStream.CanRead;
    public override bool CanSeek => _innerStream.CanSeek;
    public override bool CanWrite => _innerStream.CanWrite;
    public override long Length => _innerStream.Length;
    public override long Position 
    { 
        get => _innerStream.Position; 
        set => _innerStream.Position = value; 
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesRead = _innerStream.Read(buffer, offset, count);
        if (bytesRead > 0)
        {
            _bytesSent += bytesRead;
            _progressCallback?.Invoke(_bytesSent, Length);
        }
        return bytesRead;
    }

    // 其余方法直接转发给内部流
    public override void Flush() => _innerStream.Flush();
    public override long Seek(long offset, SeekOrigin origin) => _innerStream.Seek(offset, origin);
    public override void SetLength(long value) => _innerStream.SetLength(value);
    public override void Write(byte[] buffer, int offset, int count) => _innerStream.Write(buffer, offset, count);
}

2. 在Blazor组件中使用

// 用于存储每个文件的上传进度(键为文件名,值为百分比)
private Dictionary<string, int> FileProgress = new();

private async Task UploadFiles(IReadOnlyList<IBrowserFile> files)
{
    using var multipartContent = new MultipartFormDataContent();

    // 添加元数据
    multipartContent.Add(new StringContent("sample-metadata"), "metadata");

    foreach (var file in files)
    {
        // 初始化当前文件进度为0
        FileProgress[file.Name] = 0;
        StateHasChanged();

        // 包装文件流并绑定进度回调
        var progressStream = new ProgressStream(file.OpenReadStream(), (sent, total) =>
        {
            // 必须通过InvokeAsync确保UI线程安全更新
            InvokeAsync(() =>
            {
                FileProgress[file.Name] = (int)((sent / (double)total) * 100);
                StateHasChanged();
            });
        });

        var streamContent = new StreamContent(progressStream);
        streamContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
        multipartContent.Add(streamContent, "files", file.Name);
    }

    // 发送上传请求
    var response = await HttpClient.PostAsync("/api/upload", multipartContent);
    response.EnsureSuccessStatusCode();
}

Blazor WASM HttpClient的底层上传逻辑

Blazor WASM的HttpClient基于浏览器的Fetch API实现:

  • 使用StreamContent时,Fetch会采用流式上传——边读取文件流边发送数据,不会将整个文件预加载到内存;
  • 使用ByteArrayContent时,会先将整个字节数组加载到内存,再一次性发送数据。

现代浏览器均支持Fetch的流式上传特性,无需担心兼容性问题。

内容的提问来源于stack exchange,提问作者Help123

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 08:23:33