I'm attempting to build an ASP.NET Core 1.1 Controller method to handle an HTTP Request that looks like the following:
POST https://localhost/api/data/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=--------------------------625450203542273177701444
Host: localhost
Content-Length: 474
----------------------------625450203542273177701444
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain
<< Contents of my file >>
----------------------------625450203542273177701444
Content-Disposition: form-data; name="text"
Content-Type: application/json
{"md5":"595f44fec1e92a71d3e9e77456ba80d0","sessionIds":["123","abc"]}
----------------------------625450203542273177701444--
It's a multipart/form-data request with one part being a (small) file and the other part a json blob that is based on a provided specification.
Ideally, I'd love my controller method to look like:
[HttpPost]
public async Task Post(UploadPayload payload)
{
// TODO
}
public class UploadPayload
{
public IFormFile File { get; set; }
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
But alas, that doesn't Just Work {TM}. When I have it like this, the IFormFile does get populated, but the json string doesn't get deserialized to the other properties.
I've also tried adding a Text property to UploadPayload that has all the properties other than the IFormFile and that also doesn't receive the data. E.g.
public class UploadPayload
{
public IFormFile File { get; set; }
public UploadPayloadMetadata Text { get; set; }
}
public class UploadPayloadMetadata
{
[Required]
[StringLength(32)]
public string Md5 { get; set; }
public List<string> SessionIds { get; set; }
}
A workaround that I have is to avoid model binding and use MultipartReader along the lines of:
[HttpPost]
public async Task Post()
{
...
var reader = new MultipartReader(Request.GetMultipartBoundary(), HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
var filePart = section.AsFileSection();
// Do stuff & things with the file
section = await reader.ReadNextSectionAsync();
var jsonPart = section.AsFormDataSection();
var jsonString = await jsonPart.GetValueAsync();
// Use $JsonLibrary to manually deserailize into the model
// Do stuff & things with the metadata
...
}
Doing the above bypasses model validation features, etc. Also, I thought maybe I could take that jsonString and then somehow get it into a state that I could then call await TryUpdateModelAsync(payloadModel, ...) but couldn't figure out how to get there either - and that didn't seem all that clean either.
Is it possible to get to my desired state of "transparent" model binding like my first attempt? If so, how would one get to that?