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

.NET Core API FTP无缓存直传:解决磁盘缓存与控制器延迟触发问题

解决.NET Core API流式上传FTP且避免磁盘/内存缓存的问题

我明白你的痛点——在资源有限的设备上(1GB内存/存储),既要实现流式上传到FTP,又不能让文件缓存到磁盘或内存,还要避免请求体流的读取冲突。咱们来一步步解决这个问题:

问题根源分析

你遇到的System.IO.IOException: Unexpected end of Stream错误,本质是因为默认情况下ASP.NET Core的请求体流是只读一次的向前流,如果有任何中间件或组件提前读取了流(比如自动表单解析),后续MultipartReader再读取时就会碰到流末尾。而EnableBuffering虽然能解决流读取问题,但它会把请求体缓存到磁盘(当内存不足时),这正好违反了你的需求。

解决方案步骤

1. 移除自定义EnableBufferingAttribute

首先完全去掉这个特性,它会强制缓冲请求体到磁盘,不符合咱们的流式需求。

2. 调整控制器代码

保留RequestFormLimits来允许大文件上传,但不要触发任何自动表单解析逻辑:

[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = long.MaxValue)]
[HttpPost("[action]")]
public async Task<IActionResult> Upload(string path)
{
    if (!IsMultipartContentType(HttpContext.Request.ContentType))
        return BadRequest("Not multipart request");

    await _fileProvider.Upload(path);
    return Ok();
}

// 辅助方法:验证请求是否为multipart类型
private bool IsMultipartContentType(string contentType)
{
    return !string.IsNullOrEmpty(contentType)
           && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}

⚠️ 注意:path参数要通过URL查询字符串传递(比如/Upload?path=/ftp/folder),不要放在表单里,否则ASP.NET Core会自动解析表单,提前消耗请求体流。

3. 重构FTP上传逻辑

修改Upload方法,确保直接流式处理请求体,不做任何缓存:

public async Task Upload(string path)
{
    const int bufferSize = 8 * 1024; // 8KB缓冲区,平衡性能和内存占用
    var request = _httpContextAccessor.HttpContext.Request;

    // 安全获取multipart boundary(用官方辅助类避免手动解析出错)
    var mediaType = MediaTypeHeaderValue.Parse(request.ContentType);
    var boundary = MultipartRequestHelper.GetBoundary(mediaType, int.MaxValue);

    // 初始化MultipartReader,直接绑定请求体流
    var reader = new MultipartReader(boundary, request.Body, bufferSize);
    MultipartSection section;

    using (var client = await _ftpHelper.GetFtpClient())
    {
        while ((section = await reader.ReadNextSectionAsync()) != null)
        {
            // 验证当前section是否为文件部分
            if (ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition)
                && contentDisposition.DispositionType.Equals("form-data", StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrEmpty(contentDisposition.FileName.Value))
            {
                var fileName = contentDisposition.FileName.Value;
                var uploadPath = Path.Combine(path, fileName);

                // 直接打开FTP写入流,边读请求体边写入FTP
                using (var ftpStream = await client.OpenWriteAsync(uploadPath))
                {
                    await section.Body.CopyToAsync(ftpStream, bufferSize);
                    await ftpStream.FlushAsync(); // 确保数据写入FTP服务器
                }
            }

            // 及时释放section资源,避免内存泄漏
            await section.DisposeAsync();
        }
    }
}

4. 确保中间件顺序正确

在Program.cs中,不要添加任何会自动解析表单的中间件(比如app.UseFormMapping()),确保你的API控制器中间件在所有可能读取请求体的中间件之后。比如:

var builder = WebApplication.CreateBuilder(args);

// 添加服务
builder.Services.AddControllers();
builder.Services.AddHttpContextAccessor();
// 注册你的_fileProvider和_ftpHelper...

var app = builder.Build();

// 中间件顺序:先静态文件(如果有),再路由,最后控制器
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();

app.Run();

关键注意事项

  • 避免任何提前读取请求体的操作:不要用[FromForm]、IFormFile等参数,这些会触发ASP.NET Core的自动表单解析,消耗请求体流。
  • 缓冲区大小:设置8*1024(8KB)的缓冲区,既能保证流式传输的性能,又不会占用过多内存。
  • FTP客户端支持:确保你的_ftpHelper.GetFtpClient()返回的FTP客户端支持流式写入(比如FluentFTP的OpenWriteAsync返回的是可直接写入的网络流)。

这样修改后,请求一到达控制器就会开始处理,直接把请求体的文件数据流式上传到FTP,完全不会缓存到磁盘或内存,完美适配你的资源有限设备。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 12:32:49