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
相关产品推荐
相关产品推荐

