I'm gonna answer my own question, very hard to find how to do file upload using httpclient from a user app, and there is one important detail that I found that may unstuck someone.
here is my client code:
public static async Task<string> SendFile(HttpClient client, string Content, string comment) { MultipartFormDataContent multiContent = new MultipartFormDataContent(); var payload = Encoding.UTF8.GetBytes(Content); multiContent.Add(new ByteArrayContent(payload), "files", comment); // name must be "files" var response = await client.PostAsync($"{BaseUrl}/api/myFile/Upload", multiContent).ConfigureAwait(false); return await response.Content.ReadAsStringAsync().ConfigureAwait(false); }
Here is the controller
public async Task<IActionResult> Upload(List<IFormFile> files) { if (!ModelState.IsValid) return BadRequest("Invalid Input"); if (files == null || files.Count != 1 ) return BadRequest("Invalid files"); var file = files[0]; if (file == null || file.Length == 0 || file.Length > 1 * 1024 * 1024) return BadRequest("Invalid file"); string Content; using (MemoryStream ms = new MemoryStream()) { await file.CopyToAsync(ms).ConfigureAwait(false); Content = Encoding.ASCII.GetString(ms.ToArray()); } // Content is now the uploaded text file return Ok(); }
the trick that was not clearly documented was that in
multiContent.Add(new ByteArrayContent(payload), "files", comment); // name must be "files"
That field name "files" must be "files", not "file" as I originally had it. With the incorrect name, I would get to the controller, but the List<IFormFile> files would have no files (length == 0). with not specifying a List just(IFormFile file), file would be null. Odd part about it is that in both these cases, ModelState.IsValid would be true even though no file is passed in.