英文:
Get error: "Deserialization of interface types is not supported" while sending request (with field and file) to .NET 6 Web API
问题
我正在练习在Angular中将值传递给表单,然后将其与文件一起发送到.NET 6 Web API。
以下是我所做的事情:
我的HTML组件:
<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>
这是我的ts组件:
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);                
            }
        )
    }
}
这是我的createWithFile函数,baseUrl只是一个包含"localhost:port/path"的字符串:
async createWithFile(product: Product, file: FormData) {
    return await lastValueFrom(this.httpClient.post(this.baseUrl+'create-with-file', {Product: product, Data: file}));
}
这是我的productApi类:
export class ProductApi {
    id: number;
    name: string;
    price: number;
    quantity: number;
    status: boolean;
    description: string;
    created: string;
    photo: string;
    categoryId: number;
    categoryName: string;
}
现在到我的ASP.NET,我正在使用Entity Framework:
这是我的Product类,Created属性有一个JsonConverter来处理它,所以不用担心:
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; }
}
我还创建了一个模型来接收来自POST请求的参数:
public class CreatedUpload
{
    public Product Product { get; set; }
    public IFormFile Data { get; set; }        
}
这是我的控制器:
[HttpPost("create-with-file")]
[Consumes("application/json")]
[Produces("application/json")]
public IActionResult CreateWithFile([FromBody] CreatedUpload createdUpload)
{
    try
    {
        *在这个位置,我放了一个调试以检查createdUpload中的数据*
        return Ok();
    }
    catch
    {
        return BadRequest();
    }
}
这是我期望的,我希望createdUpload从POST请求中捕获{Product: product, Data: file},但我得到了这个错误:
System.NotSupportedException: 不支持接口类型的反序列化。类型 'Microsoft.AspNetCore.Http.IFormFile'。路径:$.Data | LineNumber: 0 | BytePositionInLine: 144。
我还更改了CreateWithFile的参数,但它们都返回null(但不会引发错误):
[FromBody] Product product, [FromBody] IFormFile data
[FromForm] Product product, [FromForm] IFormFile data
我已经阅读了StackOverflow上的许多问题,但没有答案能够解决我的问题,我陷入了困境,请帮助我 ![]()
英文:
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>&nbsp;</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 ![]()
答案1
得分: 2
- 使用 
FormData将请求主体发送包含product对象和file的内容。 
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);
  // 将键值对添加到 formData
  let key: keyof typeof product;
  for (k in product) {
    formData.append(`product.${k}`, product[k]);
  }
  this.productApiService.createWithFile(formData).then(
    res => {
      // 成功
    },
    err => {
      console.log(err);                
    }
  )
}
- 使用 
formData参数修改createWithFile方法签名。 使用 Angular HttpClient 发送请求,内容类型为multipart/form-data。 
async createWithFile(formData: FormData){
    return await lastValueFrom(this.httpClient.post(this.baseUrl+'create-with-file', formData));
}
- 在 Web API 中,使用 
FromForm特性修改CreateWithFile操作,以接收createdUpload对象。 
public IActionResult CreateWithFile([FromForm] CreatedUpload createdUpload)
英文:
You should send the request body with product object and file as FormData to API.
- Iterate the key properties of 
productobject and add the key-value pair intoformData. Note that it is required to have the prefix "product" in order to send theproductobject. 
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);                
    }
  )
}
- Modify the 
createWithFilemethod signature withformDataparameter. Post theformData. 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));
}
- In Web API, modify the 
CreateWithFileaction to receive thecreatedUploadobject with theFromFormattribute. 
public IActionResult CreateWithFile([FromForm] CreatedUpload createdUpload)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论