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

