You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 5响应式表单嵌套FormGroup文件上传至ASP.NET Core Web API问题

解决Angular嵌套表单文件上传到ASP.NET Core API的问题

问题的核心在于你当前用JSON格式提交表单数据,但文件无法通过JSON传输,必须使用FormData(multipart/form-data格式)来处理文件上传。下面是完整的解决方案,分步骤实现:

1. 修改Angular组件的提交逻辑,改用FormData传递数据

首先,我们需要把表单中的所有字段(包括文件)打包成FormData。推荐通过监听文件选择事件把文件绑定到FormControl中,这样还能支持表单验证。

1.1 绑定文件选择事件到FormControl

先修改HTML模板,给文件输入添加change事件:

<!-- Nominee 1 文件输入 -->
<input type="file" #nomineePicture1 formControlName="nomineePicture" class="form-control" (change)="onFileSelected($event, 'employeeNominee1')">

<!-- Nominee 2 文件输入 -->
<input type="file" #nomineePicture2 formControlName="nomineePicture" class="form-control" (change)="onFileSelected($event, 'employeeNominee2')">

然后在组件中添加事件处理方法,将选中的文件设置到对应的FormControl:

onFileSelected(event: Event, groupName: string) {
  const fileInput = event.target as HTMLInputElement;
  if (fileInput.files.length > 0) {
    const file = fileInput.files[0];
    // 将文件设置到对应的FormControl
    this.updateEmployeeNomineeForm.get(groupName).get('nomineePicture').setValue(file);
  }
}

1.2 构建FormData并提交

修改updateEmployeeNominees方法,把表单数据转换为FormData格式:

updateEmployeeNominees(): void {
  const formData = new FormData();

  // 处理Nominee 1的所有字段
  const nominee1Group = this.updateEmployeeNomineeForm.get('employeeNominee1') as FormGroup;
  Object.keys(nominee1Group.controls).forEach(key => {
    const value = nominee1Group.get(key).value;
    if (key === 'nomineePicture' && value) {
      // 添加文件到FormData,指定字段名和文件名
      formData.append('EmployeeNominee1.NomineePicture', value, value.name);
    } else if (value !== null && value !== undefined) {
      // 添加普通字段
      formData.append(`EmployeeNominee1.${key}`, value);
    }
  });

  // 处理Nominee 2的所有字段
  const nominee2Group = this.updateEmployeeNomineeForm.get('employeeNominee2') as FormGroup;
  Object.keys(nominee2Group.controls).forEach(key => {
    const value = nominee2Group.get(key).value;
    if (key === 'nomineePicture' && value) {
      formData.append('EmployeeNominee2.NomineePicture', value, value.name);
    } else if (value !== null && value !== undefined) {
      formData.append(`EmployeeNominee2.${key}`, value);
    }
  });

  // 调用服务提交FormData
  this.employeeService.updateEmployeeNominees(formData).subscribe((updateStatus) => {
    if (updateStatus) {
      this.resetUpdateEmployeeNominees();
      this.updateSuccessMessage = "Employee Nominees updated successfully!";
      setTimeout(() => {
        this.updateSuccessMessage = null;
      }, 4000);
    }
  }, (error) => {
    this.serverErrorMessage = this.errorMessageService.getServerErrorMessageText();
  });
}

2. 修改Angular服务,适配FormData提交

FormData不需要手动序列化,Angular会自动设置正确的Content-Type(包含boundary),所以修改EmployeeService的方法:

updateEmployeeNominees(formData: FormData): Observable<any> {
  // 移除JSON序列化和手动设置的Content-Type
  return this.http.put<any>(`${this.baseUrl}UpdateEmployeeNominees/${employeeId}`, formData)
    .pipe(catchError(this.handleError));
}

3. 调整ASP.NET Core API的ViewModel和控制器

3.1 更新ViewModel,将文件字段改为IFormFile

public class EmployeeNomineeViewModel
{
    public int EmployeeId { get; set; }
    public string NomineeName { get; set; }
    public string NomineeRelation { get; set; }
    public string NomineeContactNo { get; set; }
    public string NomineeNationalId { get; set; }
    // 将文件类型改为IFormFile,用于接收上传的文件
    public IFormFile NomineePicture { get; set; }
    public string AdditionalInfo { get; set; }
}

public class EmployeeNomineesViewModel
{
    public EmployeeNomineeViewModel EmployeeNominee1 { get; set; }
    public EmployeeNomineeViewModel EmployeeNominee2 { get; set; }
}

3.2 修改控制器方法,使用[FromForm]接收FormData

[HttpPut("{id}")]
public async Task<IActionResult> UpdateEmployeeNominees([FromRoute] int id, [FromForm] EmployeeNomineesViewModel employeeNominees)
{
    // 处理Nominee 1的文件(示例:保存到服务器目录)
    if (employeeNominees.EmployeeNominee1?.NomineePicture != null)
    {
        // 确保上传目录存在
        var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads");
        if (!Directory.Exists(uploadDir))
        {
            Directory.CreateDirectory(uploadDir);
        }

        var filePath = Path.Combine(uploadDir, employeeNominees.EmployeeNominee1.NomineePicture.FileName);
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await employeeNominees.EmployeeNominee1.NomineePicture.CopyToAsync(stream);
        }
    }

    // 同理处理Nominee 2的文件
    if (employeeNominees.EmployeeNominee2?.NomineePicture != null)
    {
        var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads");
        var filePath = Path.Combine(uploadDir, employeeNominees.EmployeeNominee2.NomineePicture.FileName);
        using (var stream = new FileStream(filePath, FileMode.Create))
        {
            await employeeNominees.EmployeeNominee2.NomineePicture.CopyToAsync(stream);
        }
    }

    // 执行你的业务逻辑(更新数据库等)
    return Ok(true);
}

4. 可选:添加文件字段的表单验证

如果需要强制用户选择文件,可以在创建FormGroup时给nomineePicture添加必填验证:

employeeNominee1: this.formBuilder.group({
  // ...其他字段
  nomineePicture: [null, Validators.required]
}),

然后在模板中显示错误提示:

<div *ngIf="(updateEmployeeNomineeForm.get('employeeNominee1.nomineePicture') as FormControl).invalid && 
            (updateEmployeeNomineeForm.get('employeeNominee1.nomineePicture') as FormControl).touched">
  <span class="text-danger">Please select a nominee picture.</span>
</div>

这样就能完整实现嵌套表单中的文件上传功能了!

内容的提问来源于stack exchange,提问作者TanvirArjel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:41:52