ArgumentNullException for 'items' when using SelectList with ViewBag in ASP.NET Core MVC

MrBarszcz 20 Reputation points
2025-05-16T18:14:01.03+00:00

I'm developing an e-commerce project in ASP.NET Core MVC 9, running on a Debian server. I have a form to register products, where I need to allow the user to select a gender for the product from a list of options loaded from the database.

In my RegisterProduct.cshtml View, I'm using radio buttons to display the gender options, inside a form that uses the asp-for Helper Tag to link the fields to the properties of my ProductModel. The list of genders is passed to the View through ViewBag.Genders, which contains a List<GenderModel>.

RegisterProduct.cshtml:

@model ProductModel

@{
    Layout = "_Layout";
}

<div class="text-center">
    <h1 class="display-4">Cadastrar Produto</h1>

    <form asp-controller="Product" asp-action="Register" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label class="control-label">Imagens do Produto</label>
            <input type="file" name="ProductImages" id="productImagesInput" class="form-control" multiple accept="image/*"/>
            <div id="imagePreviewContainer" class="row mt-3">
                @if (ViewBag.ImagePreviews != null) {
                    for (var i = 0; i < ((List<string>)ViewBag.ImagePreviews).Count; i++) {
                        <div class="image-preview-container" draggable="true" data-index="@i">
                            <img alt="" class="image-preview" src="@((List<string>)ViewBag.ImagePreviews)[i]"/>
                        </div>
                    }
                }
            </div>
        </div>


        <div class="form-group p-2">
            <label for="name" class="control-label">Nome do Produto</label>
            <input asp-for="ProductName" class="form-control" id="name" placeholder="Camisa Regular Em Sarja"/>
        </div>

        <div class="form-group p-2">
            <label for="description" class="control-label">Descrição Curta</label>
            <textarea asp-for="ProductDescription" class="form-control" id="description"
                      placeholder="Essa camisa regular em sarja oferece um visual clássico e confortável, ideal para diversas ocasiões. O tecido de sarja proporciona um toque macio e durabilidade, enquanto a modelagem regular garante um caimento solto e elegante."></textarea>
        </div>

        <div class="form-group p-2">
            <label for="price" class="control-label">Valor</label>
            <input type="number" min="0" step=".01" asp-for="ProductPrice" class="form-control" id="price"/>
        </div>

        <div class="form-group">
            <div class="dropdown p-2">
                <button class="btn btn-light dropdown-toggle" type="button" id="dropdownGender"
                        data-bs-toggle="dropdown" aria-expanded="false">
                    Selecionar Gênero
                </button>

                <ul class="dropdown-menu" aria-labelledby="dropdownGender">
                    @if (ViewBag.Genders != null) {
                        foreach (var gender in (List<GenderModel>)ViewBag.Genders) {
                            <li>
                                <div class="form-check ms-2">
                                    <input type="radio" class="form-check-input" name="GenderId"
                                           value="@gender.GenderId"
                                           asp-for="GenderId"/>
                                    <label class="form-check-label" asp-for="GenderId">@gender.Gender</label>
                                </div>
                            </li>
                        }
                    } else {
                        <li>
                            <span class="dropdown-item">Nenhum gênero cadastrado</span>
                        </li>
                    }
                </ul>
            </div>
        </div>

        <div class="form-group">
            <div class="dropdown p-2">
                <button class="btn btn-light dropdown-toggle" type="button" id="dropdownCategories"
                        data-bs-toggle="dropdown" aria-expanded="false">
                    Selecionar Categorias
                </button>

                <ul class="dropdown-menu" aria-labelledby="dropdownCategories">
                    @if (ViewBag.Categories != null) {
                        foreach (var category in (List<CategoryModel>)ViewBag.Categories) {
                            <li>
                                <div class="form-check ms-2">
                                    <input type="checkbox" class="form-check-input" name="SelectedCategories"
                                           value="@category.CategoryId"/>
                                    <label class="form-check-label">@category.Category</label>
                                </div>
                            </li>
                        }
                    } else {
                        <li>
                            <span class="dropdown-item">Nenhuma categoria cadastrada</span>
                        </li>
                    }
                    <li>
                        <a role="button" class="btn" asp-controller="Category" asp-action="Register">cadastrar</a>
                    </li>
                </ul>
            </div>
        </div>

        <div class="form-group">
            <div class="dropdown p-2">
                <button class="btn btn-light dropdown-toggle" type="button" id="dropdownColors"
                        data-bs-toggle="dropdown" aria-expanded="false">
                    Selecionar Cores
                </button>
                <ul class="dropdown-menu" aria-labelledby="dropdownColors">
                    @if (ViewBag.Colors != null) {
                        foreach (var color in (List<ColorModel>)ViewBag.Colors) {
                            <li>
                                <div class="form-check ms-2">
                                    <input type="checkbox" class="form-check-input" name="SelectedColors"
                                           value="@color.ColorId"/>
                                    <label class="form-check-label">
                                        <div class="color-box color-s"
                                             style="background-color: @color.HexColor"></div>
                                        @color.Color
                                    </label>
                                </div>
                            </li>
                        }
                    } else {
                        <li>
                            <span class="dropdown-item">Nenhuma cor cadastrada</span>
                        </li>
                    }
                    <li>
                        <a role="button" class="btn" asp-controller="Color" asp-action="Register">cadastrar</a>
                    </li>
                </ul>
            </div>
        </div>

        <div class="form-group">
            <div class="dropdown p-2">
                <button class="btn btn-light dropdown-toggle" type="button" id="dropdownSizes"
                        data-bs-toggle="dropdown" aria-expanded="false">
                    Selecionar Tamanhos
                </button>
                <ul class="dropdown-menu" aria-labelledby="dropdownSizes">
                    @if (ViewBag.Sizes != null) {
                        foreach (var size in (List<SizeModel>)ViewBag.Sizes) {
                            <li>
                                <div class="form-check ms-2">
                                    <input type="checkbox" class="form-check-input" name="SelectedSizes"
                                           value="@size.SizeId"/>
                                    <label class="form-check-label">@size.Size</label>
                                </div>
                            </li>
                        }
                    } else {
                        <li>
                            <span class="dropdown-item">Nenhum tamanho cadastrado</span>
                        </li>
                    }
                    <li>
                        <a role="button" class="btn" asp-controller="Size" asp-action="Register">cadastrar</a>
                    </li>
                </ul>
            </div>
        </div>

        <div class="form-group">
            <div id="stockInputsContainer">
            </div>
        </div>

        <div class="form-group">
            <a role="button" class="btn btn-secondary" asp-controller="Product" asp-action="Index">Cancelar</a>
            <button type="submit" class="btn btn-primary">Enviar</button>
        </div>
    </form>
</div>

ProductController.cs:

using System.Net.Http.Headers;
using System.Text.Json;
using DLyah_Boutique_System.Models;
using DLyah_Boutique_System.Repository;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;

namespace DLyah_Boutique_System.Controllers;

public class ProductController : Controller {
    private readonly IProductRepository _productRepository;
    private readonly IGenderRepository _genderRepository;
    private readonly ICategoryRepository _categoryRepository;
    private readonly IColorRepository _colorRepository;
    private readonly ISizeRepository _sizeRepository;
    private readonly IWebHostEnvironment _environment;
    private readonly ILogger<ProductController> _logger;

    private readonly string _pathImage;

    public ProductController(
        IProductRepository productRepository, IGenderRepository genderRepository,
        ICategoryRepository categoryRepository, IColorRepository colorRepository, ISizeRepository sizeRepository,
        IWebHostEnvironment environment, IFileUploadService fileUploadService, ILogger<ProductController> logger
    ) {
        _productRepository = productRepository;
        _genderRepository = genderRepository;
        _categoryRepository = categoryRepository;
        _colorRepository = colorRepository;
        _sizeRepository = sizeRepository;
        _environment = environment;
        _logger = logger;
        _pathImage = Path.Combine(_environment.WebRootPath, "images", "products");
    }

    // GET
    public IActionResult Index() {
        return View();
    }

    public IActionResult Register() {
        List<GenderModel> genders = _genderRepository.FindAll();
        List<CategoryModel> categories = _categoryRepository.FindAll();
        List<ColorModel> colors = _colorRepository.FindAll();
        List<SizeModel> sizes = _sizeRepository.FindAll();

        ViewBag.Genders = genders;
        ViewBag.Categories = categories;
        ViewBag.Colors = colors;
        ViewBag.Sizes = sizes;

        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Register(
        List<IFormFile> pi, ProductModel product, int genderId, List<int> ct, List<int> c, List<int> s,  List<StockProductModel> stck
    ) {
        Console.WriteLine("Register método iniciado");
        
        if (ModelState.IsValid) {
            Console.WriteLine("ModelState é válido");
            
            // Adicionar o produto e salvar para obter o ProductId
            product.GenderId = genderId;
            
            _productRepository.Create(product);
            await _productRepository.SaveChanges();
            
            Console.WriteLine($"Produto adicionado com ID: {product.ProductId}");

            if (pi != null && pi.Any()) {
                Console.WriteLine($"{pi.Count} imagens recebidas.");
                
                string uploadDir = Path.Combine(_environment.WebRootPath, "images", "products");
                
                Console.WriteLine($"Diretório de upload: {uploadDir}");
                if (!Directory.Exists(uploadDir)) {
                    Console.WriteLine($"Diretório não existe, criando: {uploadDir}");

                    Directory.CreateDirectory(uploadDir);
                }

                int order = 1;
                foreach (var imageFile in pi) {
                    if (imageFile.Length > 0) {
                        Console.WriteLine($"Processando imagem: {imageFile.FileName}, Tamanho: {imageFile.Length}");

                        try {
                            string imageName = $"{product.ProductName.Replace(" ", "-")}-{Guid.NewGuid().ToString().Substring(0, 8)}{Path.GetExtension(imageFile.FileName)}";
                            string filePath = Path.Combine(uploadDir, imageName);

                            using (var stream = new FileStream(filePath, FileMode.Create)) {
                                await imageFile.CopyToAsync(stream);
                                
                                Console.WriteLine($"[DEBUG] Imagem salva em: {filePath}");
                            }

                            _productRepository.CreateProductImage(new ProductImageModel {
                                ProductId = product.ProductId,
                                ProductImagePath = $"/images/products/{imageName}",
                                ImageOrder = order++
                            });
                        } catch (Exception ex) {
                            Console.WriteLine($"[ERROR] Erro ao salvar a imagem {imageFile.FileName}: {ex.Message}");

                            ModelState.AddModelError("pi", $"Ocorreu um erro ao salvar a imagem {imageFile.FileName}.");
                        }
                    }
                }

                await _productRepository.SaveChanges();
                Console.WriteLine("Imagens salvas.");
            }


            if (ct != null && ct.Any()) {
                foreach (var categoryId in ct) {
                    _productRepository.CreateProductCategory(product.ProductId, categoryId);
                }
            }

            if (c != null && c.Any()) {
                foreach (var colorId in c) {
                    _productRepository.CreateProductColor(product.ProductId, colorId);
                }
            }

            if (s != null && s.Any()) {
                foreach (var sizeId in s) {
                    _productRepository.CreateProductSize(product.ProductId, sizeId);
                }
            }

            if (stck != null && stck.Any()) {
                foreach (var stockItem in stck) {
                    stockItem.ProductId = product.ProductId; // Garante que o ProductId esteja definido
                    _productRepository.CreateStockProduct(stockItem);
                }
                await _productRepository.SaveChanges();
            }

            await _productRepository.SaveChanges();

            return RedirectToAction("Index");
        } else {
            Console.WriteLine("ModelState não é válido");
            foreach (var error in ModelState.Values.SelectMany(v => v.Errors)) {
                Console.WriteLine($"Erro: {error.ErrorMessage}");
            }
        }

        // Se o modelo não for válido, retornar à view com os dados do produto
        List<GenderModel> genders = _genderRepository.FindAll();
        List<CategoryModel> categories = _categoryRepository.FindAll();
        List<ColorModel> colors = _colorRepository.FindAll();
        List<SizeModel> sizes = _sizeRepository.FindAll();

        ViewBag.Genders = genders;
        ViewBag.Categories = categories;
        ViewBag.Colors = colors;
        ViewBag.Sizes = sizes;

        return View(product);
    }
}

ProductRepository.cs:

using DLyah_Boutique_System.Data;
using DLyah_Boutique_System.Models;
using Microsoft.EntityFrameworkCore;

namespace DLyah_Boutique_System.Repository;

public class ProductRepository : IProductRepository {
    private readonly BankContext _context;

    public ProductRepository(BankContext context) {
        _context = context;
    }

    public List<ProductModel> FindAll() {
        return _context.Products
            .Include(pg => pg.Gender)
            .Include(p => p.ProductCategories)
            .ThenInclude(pc => pc.Category)
            .Include(p => p.ProductColors)
            .ThenInclude(pc => pc.Color)
            .Include(p => p.ProductSizes)
            .ThenInclude(ps => ps.Size)
            .Include(p => p.ProductImages.OrderBy(pi => pi.ImageOrder))
            .Include(pstck => pstck.StockProducts)
            .ToList();
    }

    public ProductModel? FindById(int id) {
        return _context.Products
            .Include(pg => pg.Gender)
            .Include(p => p.ProductCategories)
            .ThenInclude(pc => pc.Category)
            .Include(p => p.ProductColors)
            .ThenInclude(pc => pc.Color)
            .Include(p => p.ProductSizes)
            .ThenInclude(ps => ps.Size)
            .Include(p => p.ProductImages.OrderBy(pi => pi.ImageOrder))
            .Include(p => p.StockProducts) // Inclui os itens de estoque
            .FirstOrDefault(p => p.ProductId == id);
    }

    public ProductModel Update(ProductModel product) {
        ProductModel productDb = FindById(product.ProductId);

        if (productDb == null) throw new Exception("Houve um erro ao atualizar o produto");

        productDb.ProductName = product.ProductName;
        productDb.ProductDescription = product.ProductDescription;
        productDb.ProductPrice = product.ProductPrice;
        productDb.GenderId = product.GenderId;

        productDb.ProductColors.Clear();
        if (product.ProductColors != null) {
            foreach (var pc in product.ProductColors) {
                productDb.ProductColors.Add(new ProductColorModel
                    { ProductId = product.ProductId, ColorId = pc.ColorId });
            }
        }

        // Atualizar ProductSizes
        productDb.ProductSizes.Clear();
        if (product.ProductSizes != null) {
            foreach (var ps in product.ProductSizes) {
                productDb.ProductSizes.Add(new ProductSizeModel { ProductId = product.ProductId, SizeId = ps.SizeId });
            }
        }

        // Atualizar ProductCategories
        productDb.ProductCategories.Clear();
        if (product.ProductCategories != null) {
            foreach (var pc in product.ProductCategories) {
                productDb.ProductCategories.Add(new ProductCategoryModel
                    { ProductId = product.ProductId, CategoryId = pc.CategoryId });
            }
        }

        // Atualizar ProductImages (requer mais lógica dependendo de como você gerencia as imagens - adicionar, remover, etc.)
        // Este é um exemplo básico que remove todas as existentes e adiciona as novas
        productDb.ProductImages.Clear();
        if (product.ProductImages != null) {
            foreach (var pi in product.ProductImages) {
                productDb.ProductImages.Add(new ProductImageModel {
                    ProductId = product.ProductId, ProductImagePath = pi.ProductImagePath, ImageOrder = pi.ImageOrder
                });
            }
        }

        _context.Products.Update(productDb);
        _context.SaveChanges();

        return productDb;
    }

    public ProductModel Create(ProductModel product) {
        _context.Products.Add(product);
        _context.SaveChanges();

        return product;
    }

    public ProductCategoryModel CreateProductCategory(int productId, int categoryId) {
        ProductCategoryModel productCategory = new ProductCategoryModel {
            ProductId = productId,
            CategoryId = categoryId
        };

        _context.ProductCategories.Add(productCategory);

        return productCategory;
    }

    public ProductColorModel CreateProductColor(int productId, int colorId) {
        ProductColorModel productColor = new ProductColorModel {
            ProductId = productId,
            ColorId = colorId
        };

        _context.ProductColors.Add(productColor);

        return productColor;
    }

    public ProductSizeModel CreateProductSize(int productId, int sizeId) {
        ProductSizeModel productSize = new ProductSizeModel {
            ProductId = productId,
            SizeId = sizeId
        };

        _context.ProductSizes.Add(productSize);

        return productSize;
    }

    public ProductImageModel CreateProductImage(ProductImageModel productImage) {
        _context.ProductImages.Add(productImage);

        return productImage;
    }

    public StockProductModel CreateStockProduct(StockProductModel stock) {
        _context.StockProducts.Add(stock);

        return stock;
    }

    public ProductModel Kill(ProductModel product) {
        throw new NotImplementedException();
    }

    public Task<int> SaveChanges() {
        return _context.SaveChangesAsync();
    }
}
Community Center Not monitored
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 77,686 Reputation points Volunteer Moderator
    2025-05-16T23:25:18.4666667+00:00

    your question is not clear. there is no <select> or SelectItemList in the sample code.

    the action:

    public async Task<IActionResult> Register(
       List<IFormFile> pi, 
       ProductModel product, 
       int genderId, 
       List<int> ct, 
       List<int> c, 
       List<int> s,  
       List<StockProductModel> stck
    ) {
       
    

    has a bunch of invalid binding (pi, ct, c, s and stck) that have no matching form item. genderId is expected from the route or query string instead of the form, but you defined a form item.

    why didn't you just define a postback view model, so there was just one parameter, and binding be the same for render and postback?

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.