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

ASP.NET Core控制器端点如何发送>2GB的大文件

大文件(>2GB)流式下载解决方案

当前架构中,所有环节均将完整文件加载到内存(MemoryStream或byte[]),导致大文件触发OutOfMemory异常。以下是各环节的修改方案,实现流式传输,彻底移除内存中加载完整文件的逻辑:

1. 后端API控制器:直接返回Blob流

不再将Blob内容复制到MemoryStream,而是直接返回流,由ASP.NET Core自动处理分块传输:

[HttpGet("objects/archives/{archiveName}")]
public async Task<IActionResult> GetArchive(Guid tenantId, [FromRoute] string archiveName)
{
    using (Infrastructure.Extensions.LoggerExtensions.BeginScope(
               this.logger,
               LoggingScopeField.TenantId(tenantId)))
    {
        try
        {
            var path = new BlobStorageRepoPathBuilder()
                .WithId(tenantId)
                .WithPath("archives")
                .WithId(this.userInfo.UserId)
                .WithPath($"{archiveName}.zip")
                .Build();

            var blob = await this.repo.DownloadAsync(path);
            
            // 直接返回Blob流,指定MIME类型和下载文件名
            return File(blob.Stream, "application/zip", $"{archiveName}.zip");
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Error occurred during report generation.");
            return this.BadRequest("Error occurred during report generation. Please check the logs.");
        }
    }
}

2. BFF层:流式转发后端响应

修改BFF的服务调用和控制器逻辑,避免将整个文件加载为byte[],直接转发后端的响应流:

修改ObjectsServiceClient的GetArchive方法

public async Task<Stream> GetArchive(string archiveName)
{
    var url = $@"{this.options.BackendUrl}"
                .AppendPathSegments("api", "tenants", $"{this.tenantInfo.TenantId}", "objects", "archives", archiveName)
                .SetQueryParam("api-version", "1.0")
                .ToString();

    // 仅读取响应头,不等待完整响应体下载
    var response = await _httpClient.GetAsync(new Uri(url), HttpCompletionOption.ResponseHeadersRead);
    await response.EnsureSuccessStatusCodeExtendedAsync();
    
    // 返回原始响应流
    return await response.Content.ReadAsStreamAsync();
}

修改BFF的API控制器

[HttpGet("archives/{archiveName}")]
public async Task<IActionResult> GetArchive([FromRoute] string archiveName)
{
    try
    {
        var stream = await this._objectsServiceClient.GetArchive(archiveName);
        
        // 转发流到前端,指定MIME类型和文件名
        return File(stream, "application/zip", $"{archiveName}.zip");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, $"Error while retrieving archive.");
        return BadRequest(ex.Message);
    }
}

3. Angular前端:处理Blob并触发下载

修改HTTP请求配置,接收二进制Blob数据,通过浏览器原生逻辑触发下载,避免前端加载完整文件:

public getArchive(archiveName: string): Observable<Blob> {
    return this.http.get(
        `${this.runtimeConfig.backendApiPath}api/tenants/${this.auth.profile.tid}/objects/archives/${archiveName}`,
        {
            headers: new HttpHeaders({ Authorization: `Bearer ${this.auth.accessToken}` }),
            responseType: 'blob' // 指定接收二进制Blob
        });
}

// 调用下载的方法
downloadArchive(archiveName: string): void {
    this.getArchive(archiveName).subscribe({
        next: (blob) => {
            // 创建临时下载链接
            const downloadUrl = window.URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = downloadUrl;
            link.download = `${archiveName}.zip`;
            document.body.appendChild(link);
            link.click();
            
            // 清理资源
            window.URL.revokeObjectURL(downloadUrl);
            document.body.removeChild(link);
        },
        error: (err) => {
            console.error('下载失败', err);
            // 添加自定义错误提示逻辑
        }
    });
}

核心优化点

  • 后端API:直接返回Blob存储的原始流,完全避免内存中加载文件
  • BFF层:使用HttpCompletionOption.ResponseHeadersRead实现流式请求,仅转发流不缓存
  • 前端:接收Blob数据,通过浏览器原生下载逻辑处理,不占用前端内存

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 06:32:12