this is gonna be a long one so if you help me i would really appreciate it. i have a page where both create and update product is. my create page works find but i want this page to do update too so there are some preload data like features and pictures i want to be shown to admin which is fine i preloaded features and have no problem but i preloaded pictures with js(since i couldnt preload iformfille in viewmodel which yall will see) i cannt update my product cause i cant get those preloaded images sent to controller would really appreciate the HELP the below is my code My Action for getting update data from service
public IActionResult ProductDetail(NewProductViewModel product){
var brands = _brand.BrandLookupService().Excute().Select(b => new ProductsBrandViewModel { Id = b.Id, Name = b.Name });
var model = new NewProductViewModel();
var categories = _product.GetCategoriesService().Excute().Data.Select(CategoryMapper.ConvertDto);
if (product.Id==0)
{
ViewBag.Brands = new SelectList(brands, "Id", "Name");
ViewBag.Categories = new SelectList(CategoryMapper.FlattenCategoriesWithLevel(categories), "Id", "Name");
}
else
{
var productDetail= _product.GetProductDetail().Excute(new CategoryDetailParamDTO { Id=product.Id});
if (!productDetail.IsSuccess)
{
return NotFound();
}
model.Id = productDetail.Data.Id;
model.Name=productDetail.Data.Name;
model.Price=productDetail.Data.Price;
model.Description=productDetail.Data.Description;
model.HowToUse=productDetail.Data.HowToUse;
model.IsActive = productDetail.Data.IsDisplayed;
model.Stock = productDetail.Data.Quantity;
model.Features = productDetail.Data.Features.Select(r => new FeatureViewModel { Id=r.Id,Name=r.DisplayName,Value=r.Value}).ToList();
ViewBag.Main = productDetail.Data.MainPicture;
ViewBag.Pictures= productDetail.Data.Pictures.OrderBy(p=>p==productDetail.Data.MainPicture);
ViewBag.Brands = new SelectList(brands, "Id", "Name",productDetail.Data.BrandId);
ViewBag.Categories = new SelectList(CategoryMapper.FlattenCategoriesWithLevel(categories), "Id", "Name",productDetail.Data.CategoryId);
}
return View(model);}
My cshtml Codes
@using PetPaw.EndPoint.Areas.Admin.Models.ViewModels.Product
@model NewProductViewModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
*@{
Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
if (Model.Id==0)
{ ViewData["Title"] = "CreateProduct";
}
else
{
ViewData["Title"] = "UpdateProduct";
}
}
<div class="dashboard-header mb-4">
<h2 style="color: #558b2f; font-size: 28px; font-weight: 700;">افزودن محصول جدید</h2>
<p style="color: #999;">product info</p>
</div>
<form id="productForm" asp-area="Admin" asp-controller="Product" asp-action="CreateProduct" method="post" enctype="multipart/form-data">
<div class="form-section">
<h4>اطلاعات پایه</h4>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">product name</label>
<input asp-for="Name" type="text" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label">category</label>
<select class="form-select" asp-items="ViewBag.Categories" >
<option>categories</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">brand</label>
<select class="form-select" asp-items="ViewBag.Brands">
<option>brands</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">animal</label>
<select class="form-select">
<option>dog</option>
<option>cat</option>
<option>all</option>
</select>
</div>
<div class="col-12">
<label class="form-label">Des</label>
<textarea class="form-control" rows="3" asp-for="Description"></textarea>
</div>
<div class="col-12">
<label class="form-label">How to use</label>
<textarea class="form-control" rows="2" asp-for="HowToUse"></textarea>
</div>
</div>
</div>
<div class="form-section">
<h4>stock and price</h4>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Price</label>
<input type="number" class="form-control" required asp-for="Price">
</div>
<div class="col-md-6">
<label class="form-label">stock</label>
<input type="number" class="form-control" required asp-for="Stock">
</div>
</div>
</div>
<div class="form-section">
<h4>features</h4>
<div id="featuresContainer">
<div id="featuresContainer">
@if (Model.Id != 0)
{
for (int i = 0; i < Model.Features.Count(); i++)
{
<div class="row g-2 mb-2" id="feature-@i">
<div class="col-md-4">
<input type="text" name="Features[@i].Name" value="@Model.Features[i].Name" class="form-control" placeholder="name" required />
</div>
<div class="col-md-4">
<input type="text" name="Features[@i].Value" value="@Model.Features[i].Value" class="form-control" placeholder="value" required />
</div>
<div class="col-md-4">
<button type="button" class="btn btn-sm" style="background: #ffebee; color: #c62828; border: none; border-radius: 10px; width: 100%;" onclick="removeFeature(@i)">
<div class="icon-trash-2" style="display: inline-block; font-size: 14px;"></div>
remove
</button>
</div>
</div>
}
}
</div>
</div>
<button type="button" class="btn-organic" style="background: #e3f2fd; color: #1976d2; margin-top: 10px;" onclick="addFeature()">
<div class="icon-plus" style="display: inline-block; margin-left: 5px;"></div>
add feature
</button>
</div>
<div class="form-section">
<h4>pics</h4>
<div class="image-upload-area" onclick="document.getElementById('productImages').click()">
<div class="icon-images text-2xl" style="color: #8bc34a;"></div>
<p style="margin-top: 10px;">uplaod pic</p>
<!-- File input sent by form -->
<input asp-for="Pictures" type="file" id="productImages" accept="image/*" style="display: none;" onchange="handleImageUpload(event)" multiple>
<input type="hidden" id="MainPictureIndex" name="MainPictureIndex" value="0">
</div>
<div id="imagesPreview" class="image-preview">
</div>
<p style="color: #999; font-size: 12px; margin-top: 10px;">first will be main bydefault or u can change it</p>
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button type="button" class="btn-organic" style="background: #f5f5f5; color: #666;" onclick="window.location.href='products.html'">cancel</button>
<button type="submit" class="btn-organic btn-primary-organic">save</button>
</div>
</form>
@section Scripts
{
<script>
let featureCounter = @(Model.Features?.Count() ?? 0);
const pictures = @Html.Raw(Json.Serialize(ViewBag.Pictures ?? new List<string>()));
</script>
<script src="~/js/product/porduct-form.js"></script> }
my product-form js
let uploadedImages = []; // { id, url, file?, isMain }
document.addEventListener('DOMContentLoaded', function () {
try {
preloadImagesFromServer();
} catch (error) {
console.error('Add product page error:', error);
}
});
// ---------- Features ----------
function addFeature() {
const container = document.getElementById('featuresContainer');
if (!container) return;
const featureId = featureCounter++;
const featureDiv = document.createElement('div');
featureDiv.className = 'row g-2 mb-2';
featureDiv.id = `feature-${featureId}`;
featureDiv.innerHTML = `
<div class="col-md-4">
<input type="text" class="form-control" placeholder="name of feature" required>
</div>
<div class="col-md-4">
<input type="text" class="form-control" placeholder="value" required>
</div>
<div class="col-md-4">
<button type="button" class="btn btn-sm" style="background: #ffebee; color: #c62828; border: none; border-radius: 10px; width: 100%;" onclick="removeFeature(${featureId})">
<div class="icon-trash-2" style="display: inline-block; font-size: 14px;"></div>
remove
</button>
</div>
`;
container.appendChild(featureDiv);
}
function removeFeature(id) {
const feature = document.getElementById(`feature-${id}`);
if (feature) feature.remove();
}
// ---------- Preload Images ----------
function preloadImagesFromServer() {
const preview = document.getElementById('imagesPreview');
if (!preview) return;
const mainPic = "@ViewBag.Main";
uploadedImages = [];
preview.innerHTML = '';
pictures.forEach((url, index) => {
const isMain = url === mainPic || (!uploadedImages.some(img => img.isMain) && index === 0);
uploadedImages.push({ id: index, url: url, isMain: isMain });
const imageItem = document.createElement('div');
imageItem.className = 'image-preview-item';
imageItem.id = `image-${index}`;
imageItem.innerHTML = `
<img src="${url}" alt="تصویر ${index + 1}">
<button type="button" class="remove-btn" onclick="removeImage(${index})">×</button>
<button type="button" class="btn btn-sm" style="position: absolute; bottom: 5px; left: 5px; background: ${isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)'}; color: ${isMain ? 'white' : '#666'}; border: none; border-radius: 8px; padding: 3px 8px; font-size: 11px;" onclick="setMainImage(${index})">
${isMain ? '✓ main' : 'choose as main'}
</button>
`;
preview.appendChild(imageItem);
});
if (!uploadedImages.some(img => img.isMain) && uploadedImages.length > 0) {
setMainImage(uploadedImages[0].id);
}
}
// ---------- Upload New Images ----------
function handleImageUpload(event) {
const files = Array.from(event.target.files);
const preview = document.getElementById('imagesPreview');
if (!preview) return;
files.forEach(file => {
const reader = new FileReader();
reader.onload = function (e) {
const imageId = uploadedImages.length;
const isMain = !uploadedImages.some(img => img.isMain); // اولین main
uploadedImages.push({ id: imageId, url: e.target.result, file: file, isMain: isMain });
const imageItem = document.createElement('div');
imageItem.className = 'image-preview-item';
imageItem.id = `image-${imageId}`;
imageItem.innerHTML = `
<img src="${e.target.result}" alt="picture ${imageId + 1}">
<button type="button" class="remove-btn" onclick="removeImage(${imageId})">×</button>
<button type="button" class="btn btn-sm" style="position: absolute; bottom: 5px; left: 5px; background: ${isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)'}; color: ${isMain ? 'white' : '#666'}; border: none; border-radius: 8px; padding: 3px 8px; font-size: 11px;" onclick="setMainImage(${imageId})">
${isMain ? '✓ main' : 'choose as main'}
</button>
`;
preview.appendChild(imageItem);
if (isMain) setMainImage(imageId);
};
reader.readAsDataURL(file);
});
}
// ---------- Main Image ----------
function setMainImage(imageId) {
uploadedImages.forEach(img => img.isMain = false);
const mainImg = uploadedImages.find(img => img.id === imageId);
if (mainImg) mainImg.isMain = true;
uploadedImages.forEach(img => {
const btn = document.querySelector(`#image-${img.id} .btn`);
if (btn) {
btn.style.background = img.isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)';
btn.style.color = img.isMain ? 'white' : '#666';
btn.textContent = img.isMain ? '✓ main' : 'choose as main';
}
});
const mainIndexInput = document.getElementById('MainPictureIndex');
if (mainIndexInput) mainIndexInput.value = imageId;
}
// ---------- Remove Image ----------
function removeImage(imageId) {
const imageItem = document.getElementById(`image-${imageId}`);
if (imageItem) imageItem.remove();
uploadedImages = uploadedImages.filter(img => img.id !== imageId);
if (uploadedImages.length > 0 && !uploadedImages.some(img => img.isMain)) {
setMainImage(uploadedImages[0].id);
}
}
my viewModel
public class NewProductViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public string? HowToUse { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public long CategoryId { get; set; }
public long BrandId { get; set; }
public long AnimalId { get; set; }
public bool IsActive { get; set; }
public int MainPictureIndex { get; set; }
public List<string> ExistingProductPictures { get; set; } = new List<string>();
public List<IFormFile> Pictures { get; set; }=new List<IFormFile>();
public List<FeatureViewModel> Features { get; set; }=new List<FeatureViewModel>();
}