Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upload files and JSON in ASP.NET Core Web API

How can I upload a list of files (images) and json data to ASP.NET Core Web API controller using multipart upload?

I can successfully receive a list of files, uploaded with multipart/form-data content type like that:

public async Task<IActionResult> Upload(IList<IFormFile> files) 

And of course I can successfully receive HTTP request body formatted to my object using default JSON formatter like that:

public void Post([FromBody]SomeObject value) 

But how can I combine these two in a single controller action? How can I upload both images and JSON data and have them bind to my objects?

like image 432
Andrius Avatar asked Dec 28 '16 18:12

Andrius


People also ask

How do I upload a file using .NET core API?

In dotnet core controller you can use IFormFile Interface to get files, [HttpPost("upload-file")] public async Task<IActionResult> UploadFile([FromQuery] IFormFile file){ if(file.


2 Answers

Simple, less code, no wrapper model

There is simpler solution, heavily inspired by Andrius' answer. By using the ModelBinderAttribute you don't have to specify a model or binder provider. This saves a lot of code. Your controller action would look like this:

public IActionResult Upload(     [ModelBinder(BinderType = typeof(JsonModelBinder))] SomeObject value,     IList<IFormFile> files) {     // Use serialized json object 'value'     // Use uploaded 'files' } 

Implementation

Code behind JsonModelBinder (see GitHub or use NuGet package):

using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding;  public class JsonModelBinder : IModelBinder {     public Task BindModelAsync(ModelBindingContext bindingContext) {         if (bindingContext == null) {             throw new ArgumentNullException(nameof(bindingContext));         }          // Check the value sent in         var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);         if (valueProviderResult != ValueProviderResult.None) {             bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);              // Attempt to convert the input value             var valueAsString = valueProviderResult.FirstValue;             var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);             if (result != null) {                 bindingContext.Result = ModelBindingResult.Success(result);                 return Task.CompletedTask;             }         }          return Task.CompletedTask;     } } 

Example request

Here is an example of a raw http request as accepted by the controller action Upload above.

A multipart/form-data request is split into multiple parts each separated by the specified boundary=12345. Each part got a name assigned in its Content-Disposition-header. With these names default ASP.Net-Core knows which part is bound to which parameter in the controller action.

Files that are bound to IFormFile additionally need to specify a filename as in the second part of the request. Content-Type is not required.

Another thing to note is that the json parts need to be deserializable into the parameter types as defined in the controller action. So in this case the type SomeObject should have a property key of type string.

POST http://localhost:5000/home/upload HTTP/1.1 Host: localhost:5000 Content-Type: multipart/form-data; boundary=12345 Content-Length: 218  --12345 Content-Disposition: form-data; name="value"  {"key": "value"} --12345 Content-Disposition: form-data; name="files"; filename="file.txt" Content-Type: text/plain  This is a simple text file --12345-- 

Testing with Postman

Postman can be used to call the action and test your server side code. This is quite simple and mostly UI driven. Create a new request and select form-data in the Body-Tab. Now you can choose between text and file for each part of the reqeust.

enter image description here

like image 83
Bruno Zell Avatar answered Oct 17 '22 04:10

Bruno Zell


I'm working with Angular 7 on the front-end, so I make use of the FormData class, which allows you to append strings or blobs to a form. They can be pulled out of the form in the controller action using the [FromForm] attribute. I add the file to the FormData object, and then I stringify the data I wish to send together with the file, append it to the FormData object, and deserialize the string in my controller action.

Like so:

//front-end: let formData: FormData = new FormData(); formData.append('File', fileToUpload); formData.append('jsonString', JSON.stringify(myObject));  //request using a var of type HttpClient http.post(url, formData);  //controller action public Upload([FromForm] IFormFile File, [FromForm] string jsonString) {     SomeType myObj = JsonConvert.DeserializeObject<SomeType>(jsonString);      //do stuff with 'File'     //do stuff with 'myObj' } 

You now have a handle on the file and the object. Note that the name you provide in the params list of your controller action must match the name you provide when appending to the FormData object on the front-end.

like image 45
andreisrob Avatar answered Oct 17 '22 04:10

andreisrob