ASP.Net Core 3.1 MVC - MultiSelect with List of Objects

Viewed 1826

So, basically the aim is:

User selects multiple 'categories' for a book (genres) from a select list. Some examples are:

  • Action and Adventure
  • Classics
  • Comic Book or Graphic Novel

After the user selects 1 or more (so multiple), they save the book to a database.

Functiality wise, everything worked just fine when I was dealing with singular genres (the genre was saved as a single string), but I need to have support for multiple genres.

The first thing I did was simply change the string Category to List<string> Categories, but that caused the error seen here: StackOverflow issue, which was solved I tried the solution that 'TuPack' offered, and that error went away... but it led to a whole new issue.

You're up to speed on where I am now: In order to get any results out of my <select></select> , I have to do this:

<select asp-for="CategoryIds[0].CategoryId" asp-items="ViewBag.Language"
        class="form-control" multiple="multiple">
   <option value="">Please choose book language</option>                    
</select>

That certainly works.. for getting the first category the user selects. That's all though.

As for the code so far, relevance wise... I'll cut off the db related stuff, since the issue occurs long before there:

Controller:

public async Task<ViewResult> AddNewBookAsync(bool isSuccess = false, int bookId = 0)
        {
            var model = new BookModel();
            
            ViewBag.Language = new SelectList(await _languageRepository.GetLanguages(), "Id", "Name");

            ViewBag.IsSucess = isSuccess;
            ViewBag.BookId = bookId;            
            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> AddNewBook(BookModel bookModel)
        {


            if (ModelState.IsValid)
            {
                int id = await _bookRepository.AddNewBook(bookModel);
                if(id > 0)
                {
                    return RedirectToAction(nameof(AddNewBook), new { isSuccess = true, bookId = true });
                }
            }



            ViewBag.Language = new SelectList(await _languageRepository.GetLanguages(), "Id", "Name");
            return View();
        }

BookModel:

public class BookModel
    {
        public int Id { get; set; }
        [StringLength(100, MinimumLength = 5)]
        [Required(ErrorMessage ="Please enter the title of your book")]
        public string Title { get; set; }
        [Required(ErrorMessage = "Please enter the author's name")]
        public string Author { get; set; }
        [StringLength(500)]
        public string Description { get; set; }
        
        [Required(ErrorMessage = "Please choose the language of your book")]
        public string Language { get; set; }
        [Display(Name = "Genre")]
        public List<Categories> CategoryIds { get; set; } = new List<Categories>();

        [Required(ErrorMessage = "Please enter the total pages")]
        [Display(Name = "Total pages of book")]
        public int? TotalPages  { get; set; }
    }

I showed the relevant Html code previously, so won't post it again.

Visuals: Example Input: Example Input Current Output: Current Output

Category Id 1 is 'Action And Adventure'.

By the way, I'm sure it is quite easy to tell, but this is just a 'practice project'. I don't want to make big changes to the main project I'm working on without actually knowing what I'm doing.

Update: After changing CategoryIds[0].CategoryId and running it with the exact same values as before, I get: 0 object results

1 Answers

Update

enter image description here

I used to add a IList<SelectListItem> property to Book model and store multiple categories in CategoryIds property. It works on me.

Codes of controller

    public IList<SelectListItem> GetCategories()
    {
        var categories = new List<Category>
        {
            new Category {Id = 1, CategoryName = "Action and Adventure"},
            new Category {Id = 2, CategoryName = "Classics"},
            new Category {Id = 3, CategoryName = "Comic Book or Graphic Novel"},
        };  // here you can load categories from DB, this is only for test
        


        var categoryListItem = categories
       .Select(x => new SelectListItem { Text = x.CategoryName, Value = x.Id.ToString() })
       .ToList();

        return categoryListItem;
    }


    [Route("/books/add")]
    public IActionResult _AddNewBooks()
    {
        var model = new BookModel { CategoryList = GetCategories() };

        return View(model);
    }


    [Route("/books/addnewbook")]
    [HttpPost]
    public IActionResult AddNewBook(BookModel model)
    {
        if (ModelState.IsValid)
        {
            //int id = await _bookRepository.AddNewBook(bookModel);
            //if (id > 0)
            //{
            //    return RedirectToAction(nameof(AddNewBook), new { isSuccess = true, bookId = true });
            //}
        }

        //ViewBag.Language = new SelectList(await _languageRepository.GetLanguages(), "Id", "Name");
        model.CategoryList = GetCategories();

        return View();

    }

Codes of model

public class BookModel
{
    public int Id { get; set; }

    public string Title { get; set; }

    public IList<SelectListItem> CategoryList { get; set; }

    [Required(ErrorMessage = "Please select at last 1 Category")]
    public List<int> CategoryIds { get; set; }


    public BookModel(){
        CategoryList = new List<SelectListItem>();
    }
}


public class Category
{
    public int Id { get; set; }
    public string CategoryName { get; set; }
}

Codes of view

<select asp-for="CategoryIds" asp-items="Model.CategoryList" 
           placeholder="Select Categories" 
           onchange="console.log($(this).children(':selected').length)" 
           class="search-box form-control">
</select>




@MJDeveloping,

1> You are using ViewBag.Language as items of Category. I suggest you return Categories to the view.

2> asp-for should be CategoryIds.

<select asp-for="CategoryIds" asp-items="ViewBag.Language"
        class="form-control" multiple="multiple">
   <option value="">Please choose book language</option>                    
</select>
Related