I have some issues with a Many-to-many relationship saving the results of a create view.
I want to do a create page for a new user profile that has a checklist that lets them choose courses (many to many relationship).
My view takes the records from the Courses
database and shows them all with checkboxes.
Once the user posts the data, I want to update my userprofile
model, and also the courses
many-to-many relationship. That's the code that I have missing!
I'm new at MVC and I've been researching but I couldn't do it yet.
I am following this example: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
This is the model:
public class UserProfile
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Courses> usercourses { get; set; }
}
public class Courses
{
public int CourseID { get; set; }
public string CourseDescripcion { get; set; }
public virtual ICollection<UserProfile> UserProfiles { get; set; }
}
public class UserProfileDBContext : DbContext
{
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Courses> usrCourses{ get; set; }
}
I also added a ViewModel:
namespace Mysolution.ViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string CourseDescription { get; set; }
public bool Assigned { get; set; }
}
}
This is the create
Controller that populates the courses checkboxes:
public ActionResult Create()
{
PopulateCoursesData();
return View();
}
private void PopulateCoursesData()
{
var CoursesData = db.usrCourses;
var viewModel = new List<AssignedCourseData>();
foreach (var item in CoursesData)
{
viewModel.Add(new AssignedCourseData {
CourseID = item.CourseID,
CourseDescription = item.CourseDescription,
Assigned = false });
}
ViewBag.CoursePopulate = viewModel;
}
This is the view
@{
int cnt = 0;
List<MySolution.ViewModels.AssignedCourseData> courses = ViewBag.CoursePopulate;
foreach (var course in courses)
{
<input type="checkbox" name="selectedCourse" value="@course.CourseID" />
@course.CourseDescription
}
}
And this is the controler that gets the data (and where I want to save it).
It gets as a parameter string[] selectedCourse
for the checkboxes:
[HttpPost]
public ActionResult Create(UserProfile userprofile, string[] selectedCourse)
{
if (ModelState.IsValid)
{
db.UserProfiles.Add(userprofile);
//TO DO: Save data from many to many (this is what I can't do!)
db.SaveChanges();
}
return View(userprofile);
}
Edit: I've written this up in 3 blog posts with code
Github source: https://github.com/cbruen1/mvc4-many-to-many
I think you've strayed from conventions a little bit in some of your naming for example so I've made changes where I saw fit. In my opinion the best way to have the courses posted back as part of the UserProfile is to have them rendered by an Editor Template which I explain further on.
Here's how I would implement all this:
(Thanks to @Slauma for pointing out a bug when saving new courses).
Starting from the DB leave the UserProfile collection as is and name the Course collection Courses:
In the DbContext class override the OnModelCreating method. This is how you map the many to many relationship between UserProfile and Course:
I would also add a mock initializer class in the same namespace that will give you some courses to start with and means you don't have to manually add them every time your model changes:
Add this line to Application_Start() Global.asax to kick start it:
So here's the model:
Now create 2 new action results in your Controller to create a new user profile:
Here's your PopulateCourseData similar to how you had it except don't put in in the ViewBag - it's now a property on the UserProfileViewModel:
Create an Editor Template - in your Views\Shared folder create a new folder called EditorTemplates if you don't already have one. Add a new partial view called AssignedCourseData and paste the code below. This is the bit of magic that renders and names all your check boxes correctly - you don't need a for each loop as the Editor template will create all the items passed in a collection:
Create a user profile view model in your view models folder - this has a collection of AssignedCourseData objects:
Add a new view called CreateUserprofile.cshtml to create a user profile - you can right click in the already added CreateUserProfile controller method and select "Add View":
This will render the field names correctly in order that they are part of the user profile view model when the form is posted back to the Controller. The fields will be named as such:
The other fields will be named similarly except will be indexed with 1 and 2 respectively. Finally here's how to save the courses to the new user profile when the form is posted back. Add this method to your Controller - this is called from the CreateUserProfile action result when the form is posted back:
Once the courses are part of the user profile EF takes care of the associations. It will add a record for each course selected to the T_UserProfile_Course table created in OnModelCreating. Here's the CreateUserProfile action result method showing the courses posted back :
I selected 2 courses and you can see that the courses have been added to the new user profile object: