Mac Reichelt Mac Reichelt - 9 months ago 143
C# Question

Update many-to-many entity with ASP.NET Core MVC MultiSelectList

I have a form where I want to update an entity with a many-to-many relationship with another entity, and I'm trying to use the

tag to create the
control. I'm using ASP.NET Core 1.0 with Entity Framework.

Say I have the following entities:

public class Student
public int Id { get; set; }
public string Name { get; set; }
public List<StudentCourse> Courses { get; set; }

public class Course
public int Id { get; set; }
public string Name { get; set; }
public List<StudentCourse> Students { get; set; }

public class StudentCourse
public int StudentId { get; set; }
public Student Student { get; set; }

public int CourseId { get; set; }
public Course Course { get; set; }

The view model:

public class StudentVM
public Student Student { get; set; }
public IEnumerable<SelectListItem> CourseList { get; set; }

The view:

@model StudentVM
<form asp-action="Edit">
<div class="form-horizontal>
<div class="form-group">
<select asp-for="CourseList" asp-items="Model.CourseList" />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />

The controller's action methods:

public async Task<IActionResult> Edit(Student student)
var viewmodel = new StudentVM {
Student = student,
CourseList = new MultiSelectList(
.Select(sc => sc.CourseId));
return View(viewmodel);

public async Task<IActionResult> Edit(StudentVM viewmodel)
if (viewmodel.Student == null)
return NotFound();

if (ModelState.IsValid)
Student student = viewmodel.Student;
var newCourses = viewmodel.CourseList
.Where(i => i.Selected)
.Select(c => new StudentCourse {
StudentId = student.Id,
CourseId = Convert.ToInt32(c.Value)
student.Courses.RemoveAll(sc => !newCourses.Contains(sc));
newCourses.Where(nc => !student.Courses.Contains(nc)));

return RedirectToAction("Index");
return View(viewmodel);

Now come the questions...

Question 1

comes into the
variant of
, both the properties are
. Why do the properties in the view model get cleared before entering the
function? I've confirmed they are being set correctly in the
variant, and that the data displays correctly in the view.

Question 2

Since the data coming into
is invalid, I can't test my database updates. Is my method of getting the selected items from the
and adding/removing items from
correct? I haven't been able to find a lot of documentation regarding many-to-many relationships in EF Core.

I'm rather new to ASP.NET and Entity Framework, so any tips or criticisms would be greatly appreciated.

Answer Source

You cannot bind a <select> to a collection of complex objects (which is what MultiSelectList is. A <select multiple> element posts back an array of its selected option values (which in your case will be an array of the Courses Id values). Your model needs a property to bind to. Assuming he Id property of Course is int, then add the following property

public IEnumerable<int> SelectedCourses { get; set; }

Note also that setting the selectedValues property in the MultiSelectList constructor is ignored by the tag helper (internally the method builds a new IEnumerable<SelectListItem> based on the value of the property. The code in your GET method should be

var viewmodel = new StudentVM
    Student = student,
    CourseList = new SelectList(_db.Courses, "Id", "Name"),
    SelectedCourses = student.Courses.Select(sc => sc.CourseId)   
return View(viewmodel);

Then in the view, use

<select asp-for="SelectedCourses" asp-items="Model.CourseList"></select>

And in the POST method, the value of SelectedCourses will contain an array of Course Id values that you selected in the view.