A little recommendation to start: I'd use view models instead of entity classes in your views, as it will provide you with more flexibility and help you achieve your goals without starting dirtying your views with code that does not belong there. You could, obviously, do it with your entity classes, providing some changes though, but I wouldn't recommend it.
The error you are getting is, I believe, because you are trying to bind to your entity classes, which define ICollection properties, and the model binder has no way to determine the concrete type that should be use to instantiate them. For a quick fix, you could change your ICollection with a concrete type such as Collection or List, for example. But again, there's that first recommendation of mine...
So, to do it the proper way, here's what I'd do:
public class PersonViewModel
{
public int Age {get;set;}
public string Name {get;set;}
public List<HobbyViewModel> Hobbies {get;set;}
}
public class HobbyViewModel
{
public int HobbyID {get;set;}
public string Name {get;set;}
public string Type {get;set;}
public int? Rating {get;set;}
}
public class PersonController
: Controller
{
...
[HttpGet]
public IActionResult Create()
{
PersonViewModel viewModel = new PersonViewModel();
viewModel.Hobbies = this.DbContext.Hobbies.Select(h => new HobbyViewModel() { Name = h.Name, Type = h.Type }).ToList();
return this.View(viewModel);
}
[HttpPost]
public IActionResult Create(PersonViewModel model)
{
if(!this.ModelState.IsValid)
return this.BadRequest(model);
Person person = new Person(){ Age = model.Age, Name = model.Name };
foreach(HobbyViewModel hobbyViewModel in model.Hobbies)
{
//Check if the user has rated the hobby and if not, skip it
if(!hobbyViewModel.Rating.HasValue)
continue;
person.PersonHobbies.Add(new PersonHobby(){ HobbyId = hobbyViewModel.HobbyId, Rating = hobbyViewModel.Rating.Value });
}
this.DbContext.Persons.Add(person);
this.DbContext.SaveChanges();
return this.RedirectToAction(nameof(Index));
}
}
In order words, in your person form, display a form for each of the hobbies supplied in the view model, with a Rating input. When the user posts the form, check all supplied hobbies to check if the user has rated it (ie: check if the nullable Rating property has been set) and, if she has, add a new PersonHobby to the resulting Person entity. Voila!
Finally, some recommendations on your entity models:
- Forget about the tedious PersonHobbies property names. It is not ubiquitous, and you should always aim for ubiquity whenever possible: it makes your code prettier, easier understood and therefore easier maintainable. Name your properties Hobbies for the Person entity, and Persons or Voters for the Hobby entity, for example.
- Rename your key properties to Id, as it will be automatically picked up by EntityFramework as the key property, with no configuration and/or data annotation. Furthermore, I expect the Person's Id property to be... well, the id of the Person. No need for specifying it.
- I insist, to make your life easier, forget about using your entity types in views or, worst, as Json/Xml return types. You should use view models for your Views, and Data Transfer Objects (DTOs) for your Json/Xml return types. Even if this concept might seem to go against the KISS principle, believe me, it doesn't. It will just simplify your life over time, even though it might look as a pointless hassle at start. As a matter of fact, your entity types will probably evolve over time, whereas your views might not, and inversely. In addition, your views will usually require more or different info than your entity types, and keeping on this path, you might find yourself starting plumbing your views with calls and such, whereas it should simply contain view logic. Calls should be made in your controllers actions, and those should populate models with all the data/info required by your views.