C# – Update related data – ASP.NET MVC with EF Core

After reading complex the data, it is time to be able to update it. This video fills the following points from the ASP.NET MVC tutorial:

  • Customize Courses pages
  • Add Instructors Edit page
  • Add courses to Edit page
  • Update Delete page
  • Add office location and courses to Create page
C# – Update related data - ASP.NET MVC with EF Core

I liked explicitly the generation of the checkboxes, making a relation to a corresponding table:

This is the html-razor syntax for the generation:

<div class="form-group">
		<table>
			<tr>
				@{ 
					List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;
					foreach (var course in courses)
					{
						@:</tr><tr><td>
							<input type="checkbox"
								   name="selectedCourses"
								   value="@course.CourseId"
								   @(Html.Raw(course.Assigned ? "checked=\"checked\"" : ""))/>
								   @course.CourseId @: @course.Title
						@:</td>                                    
					}
				}
			</tr>
		</table>
</div>

In the InstructorController.cs the Edit POST and GET methods look quite nicely as well – reading and writing from multiple database tables correctly:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
	if (id == null)
	{
		return NotFound();
	}

	var instructorToUpdate = await _context.Instructors
			.Include(i => i.OfficeAssignment)
			.Include(i => i.CourseAssignments)
				.ThenInclude(i=> i.Course)
			.FirstOrDefaultAsync(s => s.ID == id);

	if (await TryUpdateModelAsync<Instructor>(
		instructorToUpdate,
		"",
		i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
		{
		if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
		{
			instructorToUpdate.OfficeAssignment = null;
		}
		UpdateInstructorCourses(selectedCourses, instructorToUpdate);
		try
		{
			await _context.SaveChangesAsync();
		}
		catch (DbUpdateException)
		{
			ModelState.AddModelError("", "Unable to save changes. Please restart!");
		}
		return RedirectToAction(nameof(Index));
	}

	UpdateInstructorCourses(selectedCourses, instructorToUpdate);
	PopulateAssignedCourseData(instructorToUpdate);
	return View(instructorToUpdate);                        
}


// GET: Instructors/Edit/5
public async Task<IActionResult> Edit(int? id)
{
	if (id == null)
	{
		return NotFound();
	}

	var instructor = await _context.Instructors
		.Include(i => i.OfficeAssignment)
		.Include(i=> i.CourseAssignments)
			.ThenInclude(i=>i.Course)
		.AsNoTracking()
		.FirstOrDefaultAsync(m => m.ID == id);
	
	if (instructor == null)
	{
		return NotFound();
	}

	PopulateAssignedCourseData(instructor);
	return View(instructor);
}

The additional methods PopulateAssignedCourseData() and UpdateInstructorCourses() do what their names say – pretty much rendering relevant course data implementing Update functionailty for the courses:

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
	if (selectedCourses == null)
	{
		instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
		return;
	}

	var selectedCoursesNotNull = new HashSet<string>(selectedCourses);
	var instructorCourses = new HashSet<int>
			(instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseId));
	foreach (var course in _context.Courses)
	{
		if (selectedCoursesNotNull.Contains(course.CourseId.ToString()))
		{
			if (!instructorCourses.Contains(course.CourseId))
			{
				instructorToUpdate.CourseAssignments.Add(new CourseAssignment
				{
					InstructorId = instructorToUpdate.ID,
					CourseId = course.CourseId
				});
			}
		}
		else
		{
			if (instructorCourses.Contains(course.CourseId))
			{
				CourseAssignment courseToRemove = instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseId == course.CourseId);
				_context.Remove(courseToRemove);
			}
		}
	}
}

The Course.cs class used in the method below is in the Models folder:

private void PopulateAssignedCourseData(Instructor instructor)
	{
		var allCourses = _context.Courses;
		var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseId));
		var viewModel = new List<AssignedCourseData>();

		foreach (var course in allCourses)
		{
			viewModel.Add(new AssignedCourseData
			{
				CourseId = course.CourseId,
				Title = course.Title,
				Assigned = instructorCourses.Contains(course.CourseId)
			});
		}
		ViewData["Courses"] = viewModel;
	}

Everything is available in GitHub – https://github.com/Vitosh/ASP/tree/master/EFCoreAsp.NetMvcWebApp/ContosoUniversity007

Enjoy! 🙂