Reputation: 365
I finally figured out how to upload files from Angular 8 frontend to C# .Net Core 3.0 backend API Controller. But to get what I fully need, I also have a class that defines the file contents (e.g. # of header lines, column values, and so on...) set by the user that needs to be passed in as well.
On the client side I simply create a FormData object and push the files into it and then send that to the backend. This works great if I JUST send in this class and then receive it on the backend as a "IFormFileCollection". But if I put the FormData and my additional class in a Wrapper class, then I get an error (posted at bottom). Here I post the code that is not working.
Here is my client code that send:
export interface FileProvider
{
formData: FormData;
testString: string;
}
then my TypeScript code send to the back end:
async UploadFiles() {
try {
let files: File[] = this.files;
let myFormData: FormData = new FormData();
for (let i = 0; i < files.length; i++) {
let file: File = files[i];
myFormData.append('file', file, file.name);
}
/** Wrap this in a class. */
let fileProvider: FileProvider = {
formData: myFormData,
testString: "This is just a test string... but will be a class"
}
let promise = new Promise((resolve, reject) => {
this.dataService.UploadData(fileProvider).subscribe(data => resolve(data), error => reject(error))
});
let result = await promise;
alert("Successfully loaded Data");
}
catch (error) {
alert(error.message + ", Status: " + error.status + ", OK: " + error.ok + ", " + error.error);
}
}
my small DataService post method:
UploadData(fileProvider: FileProvider) {
let path: string = this.api + 'wells/formdata';
return this.http.post(path, fileProvider);
}
And my C# .Net Core 3 backend Controller code:
[Serializable]
public class FileProvider
{
public IFormCollection FormData { get; set; }
public string TestString { get; set; }
}
[HttpPost("formdata"), DisableRequestSizeLimit]
public IActionResult Upload(FileProvider fileProvider)
{
try
{
IFormFileCollection fileCollection = fileProvider.FormData.Files;
string testString = fileProvider.TestString;
foreach (IFormFile file in fileCollection)
{
/// Read at a line a time.
StringBuilder lineAtATime = new StringBuilder();
using (var reader = new StreamReader(file.OpenReadStream()))
{
while (reader.Peek() >= 0)
{
string line = reader.ReadLine();
lineAtATime.Append(line);
}
}
string textByLines = lineAtATime.ToString();
}
return Ok();
}
catch (Exception ex)
{
return StatusCode(500, "Internal server error: " + ex.Message);
}
}
The final error that gets thrown is the following:
"Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Microsoft.AspNetCore.Http.IFormCollection' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'formData', line 1, position 14."
What can I do to pass in both parameters classes?
Thanks!!
Upvotes: 4
Views: 4214
Reputation: 25360
The reason is you're sending a json-like object instead of a payload in multipart/form-data
format. Since JSON
can not represent binary bytes (files), you can never make it by that way.
Instead of uploading a custom FileProvider
(json), simply append the testString
field to FormData
, and upload the FormData
directly.
Server Side
replace the FormData
property with IList<IFormFile> Files
:
[Serializable]public class FileProvider { public string TestString { get; set; }public IFormCollection FormData { get; set; }public IList<IFormFile> Files { get; set; } } public IActionResult Upload([FromForm]FileProvider fileProvider) { var files = fileProvider.Files; var testString = fileProvider.TestString; ... }
Client Side
Change your Service
to receive a parameter of FormData
type:
UploadData(fileProvider: FileProvider) {UploadData(formdata: FormData) { let path: string = this.api+ 'wells/formdata'; return this.http.post(path, formdata); }
Finally, append extra fields to FormData
directly :
async UploadFiles() { try { let files: File[] = this.files; let myFormData: FormData = new FormData(); for (let i = 0; i < files.length; i++) { let file: File = files[i];myFormData.append('file', file, file.name);myFormData.append('files', file, file.name); // the filed name is `files` because the server side declares a `Files` property } myFormData.append("testString", "This is just a test string... but will be a class"); // add extra fields // ... add more fields as you likelet fileProvider: FileProvider = { formData: myFormData, testString: "This is just a test string... but will be a class" } let promise = new Promise((resolve, reject) => { this.dataService.UploadData(fileProvider).subscribe(data => resolve(data), error => reject(error)) });let resp = await this.dataService.UploadData(myFormData).toPromise(); alert("Successfully loaded Data"); } catch (error) { alert(error.message + ", Status: " + error.status + ", OK: " + error.ok + ", " + error.error); } }
Upvotes: 10