I’m trying to send Product object from view to controller using ajax, I’ve searched a lot before posting this question but most of results shows how to send array of objects or array of files but in my case my main object contains array of files and array of objects.
Here are Product and ProductVariant Models:
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; }
}
When i submit and debug Product Variants list is null while files and properies are filled with data
Here’s the View
@model DataAccessLayer.Models.Product.Product
@{
ViewData["Title"] = "Create Product";
}
<div class="dash-content">
<div class="page-header mb-3">
<div class="text-center">
<h4>Add New Product</h4>
</div>
</div>
<div class="create-category">
<div asp-action="Create" method="post" class="card" enctype="multipart/form-data" id="form">
<div class="card-body">
<div class="row ">
<div class="col-lg-6 mb-3">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" placeholder="Product Name" id="Name">
</div>
</div>
<div class="col-lg-6 col-sm-6 col-12 mb-3">
<div class="form-group">
<label asp-for="Subcategory"></label>
<select asp-for="SubcategoryId" class="form-select" id="SubcategoryId">
<option selected disabled>Select Sucategory</option>
@foreach (Category cat in ViewBag.categories)
{
<optgroup label="@cat.Name">
@foreach (var subcat in cat.Subcategories)
{
<option value="@subcat.Id">@subcat.Name</option>
}
</optgroup>
}
</select>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group">
<label asp-for="Description"></label>
<textarea asp-for="Description" class="form-control" rows="4" id="Description"></textarea>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group mb-4">
<label>Product Images</label>
<div class="image-upload">
<input name="files" type="file" multiple accept="image/*" id="inputFiles">
</div>
</div>
</div>
<output></output>
<div class="col-lg-12 my-3 d-flex justify-content-center">
<div class="page-btn">
<a class="btn btn-add Mx-2" onclick="showSection()">Add Product Variant</a>
</div>
</div>
<div class="card-body pt-2 d-none" id="createVariant">
<div class="row gy-3">
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Quantity</label>
<input type="number" class="form-control" placeholder="Quantity" id="Quantity">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Min Quantity</label>
<input type="number" class="form-control" placeholder="Min Quantity" id="Min-Quantity">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Max Quantity</label>
<input type="number" class="form-control" placeholder="Max Quantity" id="Max-Quantity">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Width</label>
<input type="number" class="form-control" placeholder="Width" id="width">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Length</label>
<input type="number" class="form-control" placeholder="Length" id="Length">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Height</label>
<input type="number" class="form-control" placeholder="Height" id="Height">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Weight</label>
<input type="number" class="form-control" placeholder="Weight" id="Weight">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Price</label>
<input type="number" class="form-control" placeholder="Price" id="Price">
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="form-group">
<label>Discount</label>
<input type="number" class="form-control" placeholder="Discount" id="Discount">
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="form-group">
<label>Size</label>
<select class="form-select" id="size">
<option selected>Select Size</option>
<option value="S">S</option>
<option value="M">M</option>
<option value="L">L</option>
<option value="XL">XL</option>
<option value="XXL">XXL</option>
<option value="XXXL">XXXL</option>
<option value="XXXXL">XXXXL</option>
<option value="XXXXXL">XXXXXL</option>
</select>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="form-group">
<label>Gender</label>
<select class="form-select" id="gender">
<option selected>Select Gender</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
</select>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="form-group">
<label>Color</label>
<input type="text" class="form-control" placeholder="Color" id="Color">
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="form-group">
<label>Material</label>
<input type="text" class="form-control" placeholder="Material" id="Material">
</div>
</div>
<div class="col-lg-12 my-3 d-flex justify-content-center">
<button type="button" class="btn btn-add me-2" onclick="addProductVariant()" id="addBtn">Add Variant</button>
</div>
</div>
</div>
</div>
<button class="btn btn-add mx-2" onclick="addProduct()">Add</button>
</div>
</div>
</div>
<div class="container">
<table class="table table-striped text-center">
<thead>
<tr>
<th>Quantity</th>
<th>Min Quantity</th>
<th>Max Quantity</th>
<th>Width</th>
<th>Length</th>
<th>Height</th>
<th>Weight</th>
<th>Price</th>
<th>Discount</th>
<th>Color</th>
<th>Material</th>
<th>Size</th>
<th>Gender</th>
<th colspan="2">Action</th>
</tr>
</thead>
<tbody id="table-body">
</tbody>
</table>
</div>
</div>
and javascript code
function addProduct() {
let product = {
name: document.getElementById("Name").value,
description: document.getElementById("Description").value,
subcategoryId: document.getElementById("SubcategoryId").value,
variants: Variants,
}
var files = Array.from($('#inputFiles')[0].files);
var formData = new FormData();
for (var key in product) {
formData.append(key, product[key]);
}
for (var key in files) {
formData.append("formFiles", files[key]);
}
$.ajax({
url: "/Administration/Product/Create",
type: 'POST',
data: formData,
dataType: "json",
contentType: "multipart/form-data",
processData: false,
contentType: false,
success: function (data) {
console.log(data);
}
});
}
The problem is everything is passed to controller except product Variant list is null.
Product Controller
public async Task<JsonResult> Create(Product product)
{
if (ModelState.IsValid)
{
UnitOfWork.ProductRepo.Add(product);
UnitOfWork.Save();
return Json("Success");
}
return Json("Fail");
}
Also here how product variants is added
let productQuantityInput = document.getElementById('Quantity');
let productMinuantityInput = document.getElementById('Min-Quantity');
let productMaxuantityInput = document.getElementById('Max-Quantity');
let productWidthInput = document.getElementById('width');
let productLengthInput = document.getElementById('Length');
let productHeightInput = document.getElementById('Height');
let productWeightInput = document.getElementById('Weight');
let productPriceInput = document.getElementById('Price');
let productDiscountInput = document.getElementById('Discount');
let productColorInput = document.getElementById('Color');
let productMaterialInput = document.getElementById('Material');
let productSize = document.getElementById('size');
let productGender = document.getElementById('gender')
let Variants = [];
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);
}
2
Answers
The ASP.NET default serialization behavior default use CamelCase, so you have to name your HTML input tag with lower case Naming not like the c# class properties:
I found when we send request like what you did, in controller we would receive string value of
[object]
.So I found this document which mentioned
So I think the workaround here is sending JSON string to controller and convert the string to JSON object manually. Using
public string Variants { get; set; }
instead ofpublic virtual List<ProductImage> Images { get; set; } = new List<ProductImage>();
.Then the test result:
List<ProductVariant> objs = JsonSerializer.Deserialize<List<ProductVariant>>(product.Variants);