For one of my API methods I wanted to send a file as well as object data. This is straight-forward enough when the object data consists of value types: the front end adds key-value-pairs to a FormData
object, including the File object as one of the values; and the .NET Core back-end model object includes an IFormFile. e.g.
// JavaScript client let data = new FormData(); data.append("file", file); data.append("id", "44b6..."); return this.httpClient.fetch(`...`, { method: 'post', body: data });
// C# Model public class MyObj { public Microsoft.AspNetCore.Http.IFormFile File { get; set; } public Guid Id { get; set; } } // C# Controller Method [HttpPost] public async Task<IActionResult> Post(MyObj request) { ... }
However this approach fails if the model includes objects as in the following case where Numbers
will be null.
public class MyObj { public Microsoft.AspNetCore.Http.IFormFile File { get; set; } public Guid Id { get; set; } public List<int> Numbers { get; set; } }
At this point the model deserialization in .NET Core and the serialization done in JavaScript don’t match. However I found trying to use the suggested techniques to be somewhat over-complicated. My impression is the ‘right’ approach is to use a custom Model Binder. This seemed nice enough, but then got into details of needing to create and configure value binders, when I really just wanted to use some built-in ones for handling lists.
In the end I went with a different, perhaps less flexible or DRY, but vastly simpler approach: creating objects that shadowed the real object and whose get/set did the serialization.
public class ControllerMyObj : MyObj { public new string Numbers { get { return base.Numbers == null ? null : Newtonsoft.Json.JsonConvert.SerializeObject(base.Numbers); } set { base.Numbers = Newtonsoft.Json.JsonConvert.DeserializeObject<List<int>>(Numbers); } } } // Controller Method [HttpPost] public async Task<IActionResult> Post(ControllerMyObj request) { MyObj myObj = request; ... }
And now the front-end needs to be changed to send JSON serialized objects. That can be done specifically by key or using a more generic approach as follows.
let body = new FormData(); Object.keys(data).forEach(key => { let value = data[key]; if (typeof (value) === 'object') body.append(key, JSON.stringify(value)); else body.append(key, value); }); body.append("file", file); // fetch ...