I am practicing passing value to a form in Angular, and then sending it with a file to .NET 6 Web API.
Here is what I do:
My HTML component:
<form [formGroup]="form" (ngSubmit)="submit()" enctype="multipart/form-data">
<table>
<tr>
<td>Name</td>
<td><input type="text" formControlName="name"></td>
</tr>
<tr>
<td>Price</td>
<td><input type="text" formControlName="price"></td>
</tr>
<tr>
<td>Quantity</td>
<td><input type="text" formControlName="quantity"></td>
</tr>
<tr>
<td>Description</td>
<td><textarea formControlName="description" cols="30" rows="10"></textarea></td>
</tr>
<tr>
<td>Status</td>
<td><input type="checkbox" formControlName="status"></td>
</tr>
<tr>
<td>Photo</td>
<td>
<input type="file" Name="photo" (change)="fileControl($event)">
</td>
</tr>
<tr>
<td>Category</td>
<td>
<select formControlName="categoryId">
<option value="1"> cate 1 </option>
<option value="2"> cate 2 </option>
<option value="3"> cate 3 </option>
</select>
</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Save"></td>
</tr>
</table>
</form>
And here is my ts component:
export class CreateApiComponent implements OnInit {
form: FormGroup;
file: any;
constructor(
private productApiService: ProductApiService,
private formBuilder: FormBuilder,
private datePipe : DatePipe
){}
ngOnInit() {
this.form = this.formBuilder.group({
name: '',
price: 0,
quantity: 0,
status: true,
description: '',
photo: '',
categoryId: 1
})
}
fileControl(e:any) {
this.file = e.target.files[0];
}
submit() {
let product: ProductApi = this.form.value;
product.created = this.datePipe.transform(new Date(), 'dd/MM/yyyy');
let formData = new FormData();
formData.append('data', this.file);
this.productApiService.createWithFile(product, formData).then(
res => {
this.result = do something ;
},
err => {
console.log(err);
}
)
}
}
And this is my createWithFile
function, baseUrl
is just a string containing "localhost:port/path":
async createWithFile(product: Product, file: FormData) {
return await lastValueFrom(this.httpClient.post(this.baseUrl+'create-with-file', {Product: product, Data: file}));
}
And this is my productApi
class:
export class ProductApi {
id: number;
name: string;
price: number;
quantity: number;
status: boolean;
description: string;
created: string;
photo: string;
categoryId: number;
categoryName: string;
}
Now to my ASP.NET, I'm using Entity Framework:
This is my Product
class, the Created
property got a JsonConverter
to take care of it so no worry:
public partial class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public int? Quantity { get; set; }
public string? Description { get; set; }
public double? Price { get; set; }
public bool Status { get; set; }
public string? Photo { get; set; }
public DateTime Created { get; set; }
public int CategoryId { get; set; }
}
I also create a model to catch the param from post request:
public class CreatedUpload
{
public Product Product { get; set; }
public IFormFile Data { get; set; }
}
This is my controller:
[HttpPost("create-with-file")]
[Consumes("application/json")]
[Produces("application/json")]
public IActionResult CreateWithFile([FromBody] CreatedUpload createdUpload)
{
try
{
*at this spot i put a debug to check the data in createdUpload*
return Ok();
}
catch
{
return BadRequest();
}
}
Here is what I expect, I expect the createdUpload
to catch {Product: product, Data: file}
from the POST request, but instead I got this error:
System.NotSupportedException: Deserialization of interface types is not supported. Type 'Microsoft.AspNetCore.Http.IFormFile'. Path: $.Data | LineNumber: 0 | BytePositionInLine: 144.
I also change the param of my CreateWithFile
like this, but all of them return null
(but not raise error):
[FromBody] Product product, [FromBody] IFormFile data
[FromForm] Product product, [FromForm] IFormFile data
I have read many questions in StackOverflow, but no answer can solve my problem, and I reach a dead end, please help me :(
You should send the request body with product
object and file
as FormData
to API.
product
object and add the key-value pair into formData
. Note that it is required to have the prefix "product" in order to send the product
object.submit() {
let product: ProductApi = this.form.value;
product.created = this.datePipe.transform(new Date(), 'dd/MM/yyyy');
let formData = new FormData();
formData.append('data', this.file);
// Add key-value pair into formData
let key: keyof typeof product;
for (k in product) {
formData.append(`product.${k}`, product[k]);
}
this.productApiService.createWithFile(formData).then(
res => {
// Success
},
err => {
console.log(err);
}
)
}
createWithFile
method signature with formData
parameter. Post the formData
. Angular HttpClient will post the request with content-type: multipart/form-data
.async createWithFile(formData: FormData){
return await lastValueFrom(this.httpClient.post(this.baseUrl+'create-with-file', formData));
}
CreateWithFile
action to receive the createdUpload
object with the FromForm
attribute.public IActionResult CreateWithFile([FromForm] CreatedUpload createdUpload)