Let's imagine I am running a .NET Core API on a device with 1GB storage and 1GB RAM. I want to upload a file from my website directly to a FTP server, without the file getting cached in memory or on disk. I have this working for downloading files, as I basically act as a proxy, by opening the FTP file in a stream, then streaming that directly to the HttpContext.Request.Body
.
For uploading, I want to hit the controller immediately. I can see it caches to disk now, and that's probably because of my EnableBuffering
attribute. I have a regular <form method="post" enctype="multipart/form-data">
that POSTs to my .NET Core backend. The controller looks like this:
[EnableBuffering]
[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();
}
EnableBuffering:
public class EnableBufferingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableBuffering();
}
public void OnResourceExecuted(ResourceExecutedContext context) {}
}
Upload logic:
public async Task Upload(string path)
{
const int buffer = 8 * 1024;
var request = _httpContextAccessor.HttpContext.Request;
var boundary = request.GetMultipartBoundary();
var reader = new MultipartReader(boundary, request.Body, buffer);
var section = await reader.ReadNextSectionAsync();
using (var client = await _ftpHelper.GetFtpClient())
{
while (section != null)
{
var fileSection = section.AsFileSection();
if (fileSection != null)
{
var fileName = fileSection.FileName;
var uploadPath = Path.Combine(path, fileName);
var stream = await client.OpenWriteAsync(uploadPath);
await section.Body.CopyToAsync(stream, buffer);
}
section = await reader.ReadNextSectionAsync();
}
}
}
If I don't enable buffering, I get:
System.IO.IOException: Unexpected end of Stream, the content may have already been read by another component.
However, by enabling buffering, I can see it says:
Ensure the requestBody can be read multiple times. Normally buffers request bodies in memory; writes requests larger than 30K bytes to disk.
So that part makes sense. However, how do I get around this problem? If I set a breakpoint on the first line of the Upload()
method, and then start an upload, it uploads the file first AND THEN hits the breakpoint.
Can I get around this so it hits my controller immediately and starts uploading to the FTP server, without saving to disk first?