'Pass list item wih viewmodel in .net core

I'm new to this and this might be quite easy for some people. I'm struggling to connect a particular field (dropdown list item) between a View and a Controller. What I'm pretending is to associate a category (from a list) to a product.

For this particular Controller, my system as a Model, a Service and a ViewModel.

For the product class I have the following code:

public class DryGoodsProduct
{
    public int Id { get; set; }
    public string ProductName { get; set; }

    [Display(Name = "Category")]
    [Required]
    public DryGoodsCategory DryGoodsCategory { get; set; }
    public int DryGoodsCategoryId { get; set; }

    public DryGoodsProduct()
    { }

    public DryGoodsProduct(int id, string productName, DryGoodsCategory category)
    {
        Id = id;
        ProductName = productName;
        DryGoodsCategory = category;

    }
}

The category class is the following:

public class DryGoodsCategory
{
    public int Id { get; set; }
    public string CategoryName { get; set; }
    public string Description { get; set; }
    public virtual ICollection<DryGoodsProduct> DryGoodsProducts { get; set; }
    public DryGoodsCategory()
    {
    }

    public DryGoodsCategory(int id, string categoryName)
    {
        Id = id;
        CategoryName = categoryName;
    }
}

The product service class is the following:

public class DryGoodsProductService
{
    private readonly DB _context;

    public DryGoodsProductService(DB context)
    {
        _context = context;
    }

    public async Task<List<DryGoodsProduct>> FindAllAsync()
    {
        return await _context.DryGoodsProducts
            .Include(obj => obj.DryGoodsCategory)
            .ToListAsync();
    }

    public async Task InsertAsync(DryGoodsProduct drygoodsProduct)
    {
        _context.Add(drygoodsProduct);
        await _context.SaveChangesAsync();
    }

    public async Task<DryGoodsProduct> FindByIdAsync(int id)
    {
        return await _context.DryGoodsProducts
            .Include(obj => obj.DryGoodsCategory)
            .FirstOrDefaultAsync(x => x.Id == id);
    }

    public async Task RemoveAsync(int id)
    {
        try
        {
            var obj = await _context.DryGoodsProducts.FindAsync(id);
            _context.DryGoodsProducts.Remove(obj);
            await _context.SaveChangesAsync();
        }
        catch (IntegrityException e)
        {

            throw new IntegrityException(e.Message);
        }

    }

    public async Task UpdateAsync(DryGoodsProduct drygoodsProduct)
    {
        bool hasAny = await _context.DryGoodsProducts.AnyAsync(x => x.Id == drygoodsProduct.Id);

        if (!hasAny)
        {
            throw new NotFoundException("ID não encontrado");
        }

        try
        {
            _context.Update(drygoodsProduct);
            await _context.SaveChangesAsync();
        }
        catch (DbConcurrencyException ex)
        {

            throw new DbConcurrencyException(ex.Message);
        }

    }
}

and the view model class is:

public class DryGoodsProductViewModel
{
    public DryGoodsProduct drygoodsProduct { get; set; }
    public virtual ICollection<DryGoodsCategory> DryGoodsCategories { get; set; }
}

the controller is the following:

public async Task<IActionResult> Create()
{
    var categories = await _categoryService.FindAllAsync();
    var product = new DryGoodsProduct();

    var viewmodel = new DryGoodsProductViewModel()
    {
        drygoodsProduct = product,
        DryGoodsCategories = categories,
    };

    return View(viewmodel);
}


[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(DryGoodsProduct product)
{
    if (ModelState.IsValid)
    {
        await _productService.InsertAsync(product);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }

    return View(product);
}

and the create view :

  enter @model Library.ViewModels.Logistics.DryGoodsProductViewModel

    @{
    ViewData["Title"] = "Create";
    Layout = "~/Areas/Logistics/Views/Shared/_LogisticsLayout.cshtml"; }

    <h1>Create</h1>

    <h4>Products</h4>
    <hr />

<div class="row">
    <div class="col-md-4">
        <form asp-action="Create" enctype="multipart/form-data">
               @Html.AntiForgeryToken()
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>

            <div class="form-group">
                <label asp-for="drygoodsProduct.ProductName" class="control-label"></label>
                <input asp-for="drygoodsProduct.ProductName" class="form-control" />
                <span asp-validation-for="drygoodsProduct.ProductName" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="drygoodsProduct.DryGoodsCategoryId" class="control-label"></label>
                <select asp-for="drygoodsProduct.DryGoodsCategoryId" asp-items="@(new SelectList(Model.DryGoodsCategories, "Id", "CategoryName"))" class="form-control"></select>
                <span asp-validation-for="drygoodsProduct.DryGoodsCategoryId" class="text-danger"></span>
            </div>


            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

}

So... The error says:

An unhandled exception occurred while processing the request. InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'Library.Models.Logistics.DryGoods.DryGoodsProduct', but this ViewDataDictionary instance requires a model item of type 'Library.ViewModels.Logistics.DryGoodsProductViewModel'.

I mean, I've done something quit similar with another controller and everything is ok.

I would appreciate some feedback on how to solve this.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source