Search code examples
c#entity-framework-coreasp.net-core-mvchtml-selectasp.net-core-tag-helpers

How to pass multiple select data with TagHelpers in ASP.NET Core MVC


My problem is: I am unable to pass array data from the view (HTML-select component multiple in mode) to the controller where there is a one-to-many relationship.

I tried to use Microsoft.AspNetCore.Mvc.TagHelpers for the view.

Please see the MVC design (I simplified it):

Model

public class Product
{
    [Key]
    public int id { get; set; }        
    public string? Name { get; set; }
    [ForeignKey("ProductCategory")]
    public int Categoryid { get; set; }
    public ProductCategory ProductCategory{ get; set; }
}

public class ProductCategory
{
    [Key]
    public int id { get; set; }        
    public string Name { get; set; }
    public IList<Product> Products{ get; set; }
}

View

@using Microsoft.EntityFrameworkCore
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@model ProjectCategory

<form method="post">
    <div class="container-fluid">
        <div class="row">
             <div class="col-12 col-lg-6 pt-3">
                <label asp-for="Name"></label>
                <input asp-for="Name" class="form-control"/>
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
              <div class="col-12 col-lg-6 pt-3">
                <label asp-for="Products"></label><br/>      
                <select id="Products" asp-for="Accounts" class="form-control" multiple>
                     <option value="">Please select products...</option>       
                     

                   @{
                        Context c = new Context();
                        var products = c.Products.ToList();
                    }

                     @foreach(var r in products){<option value="@r.id">@r.Name</option>}
                 </select>
                 <span asp-validation-for="Products" class="text-danger"></span>   
            </div> 
            <div class="col-12" >
                 <br/> <button type="submit"> Create</button>
            </div>
        </div>
    </div>
</form>

<script>
// some js code to handle multiple select.. (selectize.js used)
</script>

Controller

public IActionResult Create()
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(ProductCategory productcategory)
{
    if (!ModelState.IsValid)
        return View(productcategory);

    // Problem is right here.
    // in debug mode I see, productcategory.Products Count : 0 
    // I could not pass Products from the view to controller
    Context c = new Context();
    c.ProductCategories.Add(productcategory);
    c.SaveChanges();

    return RedirectToAction("Index");
}

I searched, I saw examples for passing multiple select items to controller but those examples are just with an array, there was no model like this one-to-many, passing model object like my example.

How to do that?


Solution

  • In your select, the option field value is id, hence you should expect a list of Product.id.

    Follow the steps below;

    1. Make a view model where we will bind the Name and Id list.
    public class ProductCategoryCreateViewModel {
       public string Name {get;set;}
       public List<int> ProductIds {get;set;}
    }
    
    1. Use the code below for controller, see comments.
    [HttpPost]
    [ValidateAntiForgeryToken]
    // bind the form data to the view model
    public IActionResult Create(ProductCategoryCreateViewModel viewModel)
    {
       if (!ModelState.IsValid)
          return RedirectToAction("Create");
       
       Context c = new Context();
    
       // make a new ProductCategory using the ViewModel
       ProductCategory newCategory = new ProductCategory();
    
       // assign name to new category
       newCategory.Name = viewModel.Name;
    
       // save the category first so it will generate a new category id
       c.ProductCategories.Add(newCategory);
       c.SaveChanges();
       
       // loop through all product ids selected, and update them with the newcategoryid
       foreach(var id in viewModel.ProductIds){
    
          // load the product
          var updateProduct = c.Products.FirstOrDefault(p=>p.id == id);
    
          if(updateProduct != null){
    
             // if product is found, update the category id
             updateProduct.Categoryid = newCategory.id;
             c.SaveChanges();
          }
       }
    
       return RedirectToAction("Index");
    }
    
    1. Add name="ProductIds" to select tag. Remove for attribute.
    <select name="ProductIds" id="Products" class="form-control" multiple>
       ...        
    </select>