I want to upload a file to my ASP.NET core API server with a stream, everything works until I add a querystring to my POST, in this case I don't get an exception but the file is created on the server but no bytes are streamed in. I'm using Streams because i have very large files (20mb-8gb)
ASP.NET API:
[Route("api/[controller]")]
[ApiController]
public class UploadTestController : ControllerBase
{
// POST api/<UploadTestController>
[DisableRequestSizeLimit]
[HttpPost]
public async Task<IActionResult> Post([FromQuery]string username)
{
using (Stream stream = Request.Body)
{
try
{
using (var fstream = System.IO.File.OpenWrite(@"F:\TMP\file..."))
{
await stream.CopyToAsync(fstream);
}
return Ok("test");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return BadRequest();
}
}
}
}
Client:
class Program
{
static void Main(string[] args)
{
Upload().Wait();
}
public static async Task Upload()
{
FileStream stream = new FileStream(@"C:\TEMP\file...", FileMode.Open);
ThrottledStream throttledStream = new ThrottledStream(stream,900000);
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromMinutes(10);
var content = new MultipartFormDataContent();
content.Add(new StreamContent(throttledStream), "file...");
try
{
HttpResponseMessage response =
await client.PostAsync("https://localhost:5001/api/uploadtest?username=test", content);
var cont = await response.Content.ReadAsStringAsync();
Console.WriteLine(cont);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadKey();
}
}
}
So if i delete the querys everything works fine. I can't explain that to myself because the querys shouldn't disturb that, right?
Consider that dealing with big files (lets say bigger than hundreds of MB) involves some security and usability concerns (resuming, overflows, etc.). An interesting reading in this regard is this.
Another consideration is about how .Net handles multipart requests and the different options to upload files:
Also the RFC multipart specification is very interesting because you can learn how the boundaries in a request are defined. I think the best way to handle this requirements is by using libraries that allow you to resume files or retry.
The most simple solution in your case would be to send the variables as part of the multipart content instead of hardcoded in the url request as well as giving a name to the stream content so you let the "magic" behind the WebApi maps this properly:
content.Add(new StreamContent(stream), name: "files", "file...");
var userName = new StringContent("test");
content.Add(userName, "username");
Next step is to tell the controller to use the content with a parameter named the same way [FromForm] IFormFileCollection files:
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files)
Then you can read all the other parameters like this:
IFormCollection form = await HttpContext.Request.ReadFormAsync();
form.TryGetValue("username", out StringValues user);
That said, this is just one approach (neither the best nor the worst) but just a quick fix to this situation. The better would be to consider to have DTO that represents the whole form (data included in the request) and take care of the security concerns explained the the mentioned Microsoft blog post, among them: