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

如何避免HttpClient.PostAsync上传大文件时占用过多内存?

Blazor WebAssembly大文件上传内存溢出问题解决方案

问题原因分析

你遇到的内存溢出,核心原因是Blazor WebAssembly中HttpClient的底层实现BrowserHttpHandler在处理MultipartFormDataContent时,会先将整个多部分内容(包括大文件流)完整序列化到内存缓冲区,再传递给浏览器的fetch API。即使你设置了TransferEncodingChunked = true,也无法改变这个缓冲行为,最终导致大文件占用过多客户端内存引发OOM。

解决方案

方案1:直接上传原始文件流(无需Multipart包装)

如果你的REST服务器支持接收原始文件流(而非multipart/form-data格式),可以直接用StreamContent替代MultipartFormDataContent,绕过内存缓冲问题:

private async Task<FileModel?> UploadSingleFile(IBrowserFile file)
{
    // 注意:OpenReadStream默认有大小限制,传入long.MaxValue解除限制
    using Stream fileStream = file.OpenReadStream(long.MaxValue);
    
    var request = new HttpRequestMessage(HttpMethod.Post, $"File/{_selectedDataset.ID}");
    request.Content = new StreamContent(fileStream);
    
    // 设置文件类型和文件名(如果服务器需要)
    request.Content.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
    request.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
    {
        FileName = file.Name
    };

    // 使用ResponseHeadersRead选项,避免提前加载响应内容到内存
    var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

    if (response.IsSuccessStatusCode)
    {
        var addedFile = (await response.Content.ReadFromJsonAsync<IEnumerable<FileModel>>())?.FirstOrDefault();
        return addedFile;
    }

    return null;
}

方案2:使用JS互操作调用浏览器原生FormData(兼容Multipart格式)

如果服务器必须要求multipart/form-data格式,直接利用浏览器原生API处理流式上传是最可靠的方式——浏览器会自动分块上传文件,不会将整个文件加载到内存。

步骤1:添加JS上传函数

在wwwroot/js/fileUpload.js中添加以下代码:

window.uploadFile = async function (uploadUrl, file, authToken) {
    const formData = new FormData();
    formData.append("file", file, file.name); // 这里的键名要和服务器端点的参数名匹配

    const fetchOptions = {
        method: "POST",
        body: formData
    };

    // 如果需要认证,添加Authorization头
    if (authToken) {
        fetchOptions.headers = {
            "Authorization": `Bearer ${authToken}`
        };
    }

    const response = await fetch(uploadUrl, fetchOptions);

    if (!response.ok) {
        throw new Error(`上传失败:${response.status} ${response.statusText}`);
    }

    return await response.json();
};

步骤2:在Blazor组件中调用JS函数

注入IJSRuntime并修改上传逻辑:

@inject IJSRuntime _jsRuntime
// 如果使用Blazor WASM认证,注入AccessTokenProvider
@inject IAccessTokenProvider _tokenProvider

private async Task<FileModel?> UploadSingleFile(IBrowserFile file)
{
    try
    {
        // 获取认证令牌(如果需要)
        string? authToken = null;
        var tokenResult = await _tokenProvider.RequestAccessToken();
        if (tokenResult.TryGetToken(out var token))
        {
            authToken = token.Value;
        }

        // 调用JS上传函数
        var uploadedFiles = await _jsRuntime.InvokeAsync<IEnumerable<FileModel>>(
            "uploadFile",
            $"File/{_selectedDataset.ID}",
            file,
            authToken);

        return uploadedFiles?.FirstOrDefault();
    }
    catch (Exception ex)
    {
        // 处理上传异常(如网络错误、服务器拒绝等)
        Console.WriteLine($"上传失败:{ex.Message}");
        return null;
    }
}

关键注意事项

  • 解除IBrowserFile的流大小限制:默认情况下OpenReadStream()会限制最大读取大小(通常为50MB),上传大文件时必须传入long.MaxValue解除限制。
  • 优先选择原生API方案:如果服务器支持,方案1更简洁;如果必须用Multipart格式,方案2是避免内存溢出的最优解,因为完全利用浏览器的流式上传能力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:25:51