25

I am using Insomnia for testing an API, but the same happens with Postman.

I want to test a file upload, with the following controller:

public async Task<IActionResult> Post([FromForm]IFormFile File)

If I set the request as a multipart request:

enter image description here

it works.

However, if I set it as a binary file:

enter image description here

I don't know how to get the data. How can it be done?

Also, in the controller method's signature, if I change [FromForm] to [FromBody], I'm not getting data.

Can someone clarify this for me?

Julian
  • 33,915
  • 22
  • 119
  • 174
Thomas
  • 10,933
  • 14
  • 65
  • 136
  • Do you have any API requirements / contract for uploading file via API? I.e. use form-data, embed file into body etc. – Ignas May 08 '18 at 11:23
  • no, I mean it's not for a specific application: I'm trying to understand why I can send binary documents as multi-part, but not as a binary document. It's really about understand the differences in the requests, etc – Thomas May 08 '18 at 16:06
  • OK. Let me play with this and I will get back to you. – Ignas May 09 '18 at 21:05
  • Sorry for the delay, just posted a few examples. – Ignas May 14 '18 at 07:16

2 Answers2

74

As you've noticed already, using binary file option in Postman/Insomnia doesn't work the standard way. There are three different ways to upload file via RESTful API, and you have to choose one.

I've included code snippets that read the uploaded file contents to a string and output it -- try sending a text file, and you should get the contents of the file in the 200 response.

Form-data upload

This is the most popular/well-known upload method formatting the data you send as a set of key/value pairs. You normally need to specify Content-Type to multipart/form-data in the request, and then use [FromForm] attribute in MVC to bind values to variables. Also, you can use the built-in IFormFile class to access the file uploaded.

[HttpPost]
public async Task<IActionResult> PostFormData([FromForm] IFormFile file)
{
    using (var sr = new StreamReader(file.OpenReadStream()))
    {
        var content = await sr.ReadToEndAsync();
        return Ok(content);
    }
}

Body upload

You can send body in the format that MVC understands, e.g. JSON, and embed the file inside it. Normally, the file contents would be encoded using Base64 or other encoding to prevent character encoding/decoding issues, especially if you are sending images or binary data. E.g.

{
    "file": "MTIz"
}

And then specify [FromBody] inside your controller, and use class for model deserialization.

[HttpPost]
public IActionResult PostBody([FromBody] UploadModel uploadModel)
{
    var bytes = Convert.FromBase64String(uploadModel.File);
    var decodedString = Encoding.UTF8.GetString(bytes);
    return Ok(decodedString);
}
// ...
public class UploadModel
{
    public string File { get; set; }
}

When using large and non-text files, the JSON request becomes clunky and hard to read though.

Binary file

The key point here is that your file is the whole request. The request doesn't contain any additional info to help MVC to bind values to variables in your code. Therefore, to access the file, you need to read Body in the Request.

[HttpPost]
public async Task<IActionResult> PostBinary()
{
    using (var sr = new StreamReader(Request.Body))
    {
        var body = await sr.ReadToEndAsync();
        return Ok(body);
    }
}

Note: the example reads Body as string. You may want to use Stream or byte[] in your application to avoid file data encoding issues.

Ignas
  • 4,092
  • 17
  • 28
  • 1
    Two days, two days I spent... Thank you. – Eyal Jan 09 '19 at 20:47
  • 7
    THIS IS GOLDEN! Why this is not in official ASP.NETCORE docs ?! – Rizky Ramadhan Jan 17 '19 at 21:11
  • This is very helpful. Which method would you recommend for uploading large binary files. Is the 3rd method (binary file) limited in file size other than the maxRequestLength? – LazyGeek Jan 30 '19 at 11:24
  • @LazyGeek I would use #1 Form-data upload, since you can send more information like file category etc together in the same request, and it's a very common approach. #3 Binary file doesn't have other file size limitations than the request itself. – Ignas Jan 30 '19 at 12:26
  • 2
    you are amazing – fdfey Feb 25 '20 at 14:03
  • This is a superb answer, @Ignas! – Sean Kearon Oct 26 '20 at 06:59
  • superb answer 100/100 – Tufan Chand Oct 27 '20 at 18:51
  • Is it best practice to use the last one for a PUT method? That is, the user already POSTed a file and now they are replacing the contents of the resource (but the metadata is not changed, hence PUT and not DELETE followed by POST) – Arrow_Raider Apr 30 '21 at 15:31
  • @Arrow_Raider I don't see how the last one would be better for one or another method. You could for example use `[FromBody]` or `[FromForm]`, and leverage the automatic model binding. PUT vs POST+DELETE vs PATCH is a different question about RESTful API design :) – Ignas May 06 '21 at 20:54
  • The ReadToEndAsync() used in the Binary File example returns a string. Is that correct? Seems like it should be byte[]. I want to be able to post a file and save it without corruption. My ContentType is application/x-msdownload. – Christopher Painter Jul 21 '21 at 15:48
  • @ChristopherPainter you are right. This was just an oversimplified example to return the whole body in the response for testing. I've added a note. Thanks for pointing this out! – Ignas Jul 23 '21 at 12:14
  • 2
    I ended up using Request.Body.CopyToAsync( memorystream ) and it works great. – Christopher Painter Jul 23 '21 at 14:16
  • @ChristopherPainter The `Request.Body` stream doesn't allow accessing `Length`, so copying to a `MemoryStream` makes life much easier :) – Ignas Jul 23 '21 at 15:05
  • removing [FromBody] and/or [FromForm] attributes has solved my problem – Reza Bayat May 02 '23 at 10:38
1

In addition of the above, in case of multipart file conversion to base64String you can refer to the below:

if (File.Length> 0)
   {
       using (var ms = new MemoryStream())
       {
           File.CopyTo(ms);
           var fileBytes = ms.ToArray();
           string s = Convert.ToBase64String(fileBytes);                                    
       }
    }

Note: I am using this code for .NET CORE 2.1

Mlle 116
  • 1,149
  • 4
  • 20
  • 53