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

如何通过Ajax向.NET Core MVC控制器传递含对象数组与文件数组的Product对象

问题场景

尝试通过Ajax将包含文件数组和对象数组的Product对象传递到.NET Core MVC控制器,提交后Product的其他属性及文件数据均正常接收,但Variants列表始终为空。

涉及模型代码

public class Product
{
    public int Id { get; set; }
    public required string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public int SubcategoryId { get; set; }
    public virtual Subcategory? Subcategory { get; set; }
    public virtual List<ProductImage> Images { get; set; } = new List<ProductImage>();
    public virtual IEnumerable<ProductVariant> Variants { get; set; } = new List<ProductVariant>();
    [NotMapped]
    public IEnumerable<IFormFile> FormFiles { get; set; }
}

public class ProductVariant
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public required int Quantity { get; set; }
    public int MinQuantity { get; set; } = 1;
    public int? MaxQuantity { get; set; }
    public double? Width { get; set; }
    public double? Length { get; set; }
    public double? Height { get; set; }
    public double? Weight { get; set; }
    public required double Price { get; set; }
    public double Discount { get; set; } = 0;
    public ProductSize? Size { get; set; } // enum
    public Gender? Gender { get; set; } // enum
    public string? Color { get; set; } = string.Empty;
    public string? Material { get; set; } = string.Empty;
    public virtual Product? Product { get; set; }
}

问题原因

  1. FormData无法直接绑定数组对象:直接将variants数组append到FormData中,.NET模型绑定无法识别这种格式,需要按照键名[索引].属性名的格式逐个添加属性。
  2. 数据类型不匹配:输入框获取的值是字符串类型,未转换为模型对应的数字、枚举类型,导致绑定失败。
  3. 模型集合类型问题:Product类中Variants定义为IEnumerable<ProductVariant>,这是只读集合,模型绑定需要可写的集合类型(如List)。

解决方案

1. 修改前端JavaScript代码

调整addProduct函数中FormData的构建逻辑,遍历Variants数组,按模型绑定要求的格式添加每个属性,并转换数据类型:

function addProduct() {
    let product = {
        name: document.getElementById("Name").value,
        description: document.getElementById("Description").value,
        subcategoryId: parseInt(document.getElementById("SubcategoryId").value),
    };
    var files = Array.from($('#inputFiles')[0].files);
    var formData = new FormData();

    // 添加Product基础属性
    for (var key in product) {
        formData.append(key, product[key]);
    }

    // 添加Variants数组,按.NET模型绑定格式拼接键名
    Variants.forEach((variant, index) => {
        // 转换数字类型
        formData.append(`Variants[${index}].Quantity`, parseInt(variant.quantity) || 0);
        formData.append(`Variants[${index}].MinQuantity`, parseInt(variant.minQuantity) || 1);
        formData.append(`Variants[${index}].MaxQuantity`, variant.maxQuantity ? parseInt(variant.maxQuantity) : null);
        formData.append(`Variants[${index}].Width`, variant.width ? parseFloat(variant.width) : null);
        formData.append(`Variants[${index}].Length`, variant.length ? parseFloat(variant.length) : null);
        formData.append(`Variants[${index}].Height`, variant.height ? parseFloat(variant.height) : null);
        formData.append(`Variants[${index}].Weight`, variant.weight ? parseFloat(variant.weight) : null);
        formData.append(`Variants[${index}].Price`, parseFloat(variant.price) || 0);
        formData.append(`Variants[${index}].Discount`, parseFloat(variant.discount) || 0);
        
        // 枚举类型直接传递字符串,.NET会自动转换
        formData.append(`Variants[${index}].Size`, variant.size);
        formData.append(`Variants[${index}].Gender`, variant.gender);
        
        formData.append(`Variants[${index}].Color`, variant.color);
        formData.append(`Variants[${index}].Material`, variant.material);
    });

    // 添加文件
    for (var key in files) {
        formData.append("formFiles", files[key]);
    }

    $.ajax({
        url: "/Administration/Product/Create",
        type: 'POST',
        data: formData,
        dataType: "json",
        processData: false,
        contentType: false,
        success: function (data) {
            console.log(data);
        }
    });
}

同时,在addProductVariant函数中添加输入框清空逻辑,避免重复添加:

function addProductVariant() {
    let variant = 
    {
        quantity: productQuantityInput.value,
        minQuantity: productMinuantityInput.value,
        maxQuantity: productMaxuantityInput.value,
        width: productWidthInput.value,
        length: productLengthInput.value,
        height: productHeightInput.value,
        weight: productWeightInput.value,
        price: productPriceInput.value,
        discount: productDiscountInput.value,
        color: productColorInput.value,
        material: productMaterialInput.value,
        size: productSize.value,
        gender: productGender.value
    };
    Variants.push(variant);

    // 清空输入框
    productQuantityInput.value = '';
    productMinuantityInput.value = '1'; // 对应模型默认值
    productMaxuantityInput.value = '';
    productWidthInput.value = '';
    productLengthInput.value = '';
    productHeightInput.value = '';
    productWeightInput.value = '';
    productPriceInput.value = '';
    productDiscountInput.value = '0'; // 对应模型默认值
    productColorInput.value = '';
    productMaterialInput.value = '';
    productSize.selectedIndex = 0;
    productGender.selectedIndex = 0;
}

2. 修改后端Product模型

将Variants的类型从IEnumerable<ProductVariant>改为List<ProductVariant>,确保模型绑定可以写入:

public class Product
{
    // ... 其他属性不变
    public virtual List<ProductVariant> Variants { get; set; } = new List<ProductVariant>();
    // ... 其他属性不变
}

3. 控制器调试优化(可选)

添加模型状态校验,输出绑定错误信息便于调试:

public async Task<JsonResult> Create(Product product)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage));
        return Json(new { status = "Fail", errors = errors });
    }
    
    UnitOfWork.ProductRepo.Add(product);
    await UnitOfWork.SaveAsync();
    return Json("Success");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 02:34:52