ASP.NET Core 中的 Razor 页面和 EF Core - 更新相关数据 - 第 7 个教程(共 8 个)Razor Pages with EF Core in ASP.NET Core - Update Related Data - 7 of 8

本文内容

作者:Tom DykstraRick Anderson

Contoso University Web 应用演示了如何使用 EF Core 和 Visual Studio 创建 Razor 页面 Web 应用。若要了解系列教程,请参阅第一个教程

如果遇到无法解决的问题,请下载已完成的应用,然后对比该代码与按教程所创建的代码。

本教程将介绍如何更新相关数据。下图显示了部分已完成页面。

课程“编辑”页讲师”编辑”页

更新课程“创建”和“编辑”页Update the Course Create and Edit pages

课程“创建”和“编辑”页的基架搭建代码具有显示院系 ID(整数)的“院系”下拉列表。下拉列表应显示院系名称,因此这两个页面都需要院系名称列表。若要提供该列表,请使用“创建”和“编辑”页的基类。

创建课程“创建”和“编辑”的基类Create a base class for Course Create and Edit

使用以下代码创建 Pages/Courses/DepartmentNamePageModel.cs 文件 :

  1. using ContosoUniversity.Data;
  2. using Microsoft.AspNetCore.Mvc.RazorPages;
  3. using Microsoft.AspNetCore.Mvc.Rendering;
  4. using Microsoft.EntityFrameworkCore;
  5. using System.Linq;
  6. namespace ContosoUniversity.Pages.Courses
  7. {
  8. public class DepartmentNamePageModel : PageModel
  9. {
  10. public SelectList DepartmentNameSL { get; set; }
  11. public void PopulateDepartmentsDropDownList(SchoolContext _context,
  12. object selectedDepartment = null)
  13. {
  14. var departmentsQuery = from d in _context.Departments
  15. orderby d.Name // Sort by name.
  16. select d;
  17. DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
  18. "DepartmentID", "Name", selectedDepartment);
  19. }
  20. }
  21. }

上面的代码创建 SelectList 以包含系名称列表。如果指定了 selectedDepartment,可在 SelectList 中选择该系。

“创建”和“编辑”页模型类将派生自 DepartmentNamePageModel

更新课程“创建”页模型Update the Course Create page model

课程分配给院系。“创建”和“编辑”页的基类提供 SelectList,用于选择院系。采用 SelectList 的下拉列表设置 Course.DepartmentID 外键 (FK) 属性。EF Core 使用 Course.DepartmentID FK 加载 Department 导航属性。

创建课程

使用以下代码更新 Pages/Courses/Create.cshtml.cs :

  1. using ContosoUniversity.Models;
  2. using Microsoft.AspNetCore.Mvc;
  3. using System.Threading.Tasks;
  4. namespace ContosoUniversity.Pages.Courses
  5. {
  6. public class CreateModel : DepartmentNamePageModel
  7. {
  8. private readonly ContosoUniversity.Data.SchoolContext _context;
  9. public CreateModel(ContosoUniversity.Data.SchoolContext context)
  10. {
  11. _context = context;
  12. }
  13. public IActionResult OnGet()
  14. {
  15. PopulateDepartmentsDropDownList(_context);
  16. return Page();
  17. }
  18. [BindProperty]
  19. public Course Course { get; set; }
  20. public async Task<IActionResult> OnPostAsync()
  21. {
  22. var emptyCourse = new Course();
  23. if (await TryUpdateModelAsync<Course>(
  24. emptyCourse,
  25. "course", // Prefix for form value.
  26. s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
  27. {
  28. _context.Courses.Add(emptyCourse);
  29. await _context.SaveChangesAsync();
  30. return RedirectToPage("./Index");
  31. }
  32. // Select DepartmentID if TryUpdateModelAsync fails.
  33. PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
  34. return Page();
  35. }
  36. }
  37. }

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

前面的代码:

  • 派生自 DepartmentNamePageModel
  • 使用 TryUpdateModelAsync 防止过多发布
  • 删除 ViewData["DepartmentID"]。基类中的 DepartmentNameSL 是强类型模型,将用于 Razor 页面。建议使用强类型而非弱类型。有关详细信息,请参阅弱类型数据(ViewData 和 ViewBag)

更新课程“创建”Razor 页面Update the Course Create Razor page

使用以下代码更新 Pages/Courses/Create.cshtml :

  1. @page
  2. @model ContosoUniversity.Pages.Courses.CreateModel
  3. @{
  4. ViewData["Title"] = "Create Course";
  5. }
  6. <h2>Create</h2>
  7. <h4>Course</h4>
  8. <hr />
  9. <div class="row">
  10. <div class="col-md-4">
  11. <form method="post">
  12. <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  13. <div class="form-group">
  14. <label asp-for="Course.CourseID" class="control-label"></label>
  15. <input asp-for="Course.CourseID" class="form-control" />
  16. <span asp-validation-for="Course.CourseID" class="text-danger"></span>
  17. </div>
  18. <div class="form-group">
  19. <label asp-for="Course.Title" class="control-label"></label>
  20. <input asp-for="Course.Title" class="form-control" />
  21. <span asp-validation-for="Course.Title" class="text-danger"></span>
  22. </div>
  23. <div class="form-group">
  24. <label asp-for="Course.Credits" class="control-label"></label>
  25. <input asp-for="Course.Credits" class="form-control" />
  26. <span asp-validation-for="Course.Credits" class="text-danger"></span>
  27. </div>
  28. <div class="form-group">
  29. <label asp-for="Course.Department" class="control-label"></label>
  30. <select asp-for="Course.DepartmentID" class="form-control"
  31. asp-items="@Model.DepartmentNameSL">
  32. <option value="">-- Select Department --</option>
  33. </select>
  34. <span asp-validation-for="Course.DepartmentID" class="text-danger" />
  35. </div>
  36. <div class="form-group">
  37. <input type="submit" value="Create" class="btn btn-primary" />
  38. </div>
  39. </form>
  40. </div>
  41. </div>
  42. <div>
  43. <a asp-page="Index">Back to List</a>
  44. </div>
  45. @section Scripts {
  46. @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
  47. }

上面的代码执行以下更改:

  • 将标题从“DepartmentID”更改为“Department” 。
  • "ViewBag.DepartmentID" 替换为 DepartmentNameSL(来自基类)。
  • 添加“选择系”选项。如果尚未选择院系(而不是已选中首个院系),此更改将在下拉列表中显示“选择院系”。
  • 在未选择系时添加验证消息。

Razor 页面使用选择标记帮助器

  1. <div class="form-group">
  2. <label asp-for="Course.Department" class="control-label"></label>
  3. <select asp-for="Course.DepartmentID" class="form-control"
  4. asp-items="@Model.DepartmentNameSL">
  5. <option value="">-- Select Department --</option>
  6. </select>
  7. <span asp-validation-for="Course.DepartmentID" class="text-danger" />
  8. </div>

测试“创建”页。“创建”页显示系名称,而不是系 ID。

更新课程“编辑”页模型Update the Course Edit page model

使用以下代码更新 Pages/Courses/Edit.cshtml.cs :

  1. using ContosoUniversity.Models;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.EntityFrameworkCore;
  4. using System.Threading.Tasks;
  5. namespace ContosoUniversity.Pages.Courses
  6. {
  7. public class EditModel : DepartmentNamePageModel
  8. {
  9. private readonly ContosoUniversity.Data.SchoolContext _context;
  10. public EditModel(ContosoUniversity.Data.SchoolContext context)
  11. {
  12. _context = context;
  13. }
  14. [BindProperty]
  15. public Course Course { get; set; }
  16. public async Task<IActionResult> OnGetAsync(int? id)
  17. {
  18. if (id == null)
  19. {
  20. return NotFound();
  21. }
  22. Course = await _context.Courses
  23. .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);
  24. if (Course == null)
  25. {
  26. return NotFound();
  27. }
  28. // Select current DepartmentID.
  29. PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
  30. return Page();
  31. }
  32. public async Task<IActionResult> OnPostAsync(int? id)
  33. {
  34. if (id == null)
  35. {
  36. return NotFound();
  37. }
  38. var courseToUpdate = await _context.Courses.FindAsync(id);
  39. if (courseToUpdate == null)
  40. {
  41. return NotFound();
  42. }
  43. if (await TryUpdateModelAsync<Course>(
  44. courseToUpdate,
  45. "course", // Prefix for form value.
  46. c => c.Credits, c => c.DepartmentID, c => c.Title))
  47. {
  48. await _context.SaveChangesAsync();
  49. return RedirectToPage("./Index");
  50. }
  51. // Select DepartmentID if TryUpdateModelAsync fails.
  52. PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
  53. return Page();
  54. }
  55. }
  56. }

这些更改与在“创建”页模型中所做的更改相似。在上面的代码中,PopulateDepartmentsDropDownList 在院系 ID 中传递并将在下拉列表中选择该院系。

更新课程“编辑”Razor 页面Update the Course Edit Razor page

使用以下代码更新 Pages/Courses/Edit.cshtml :

  1. @page
  2. @model ContosoUniversity.Pages.Courses.EditModel
  3. @{
  4. ViewData["Title"] = "Edit";
  5. }
  6. <h2>Edit</h2>
  7. <h4>Course</h4>
  8. <hr />
  9. <div class="row">
  10. <div class="col-md-4">
  11. <form method="post">
  12. <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  13. <input type="hidden" asp-for="Course.CourseID" />
  14. <div class="form-group">
  15. <label asp-for="Course.CourseID" class="control-label"></label>
  16. <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
  17. </div>
  18. <div class="form-group">
  19. <label asp-for="Course.Title" class="control-label"></label>
  20. <input asp-for="Course.Title" class="form-control" />
  21. <span asp-validation-for="Course.Title" class="text-danger"></span>
  22. </div>
  23. <div class="form-group">
  24. <label asp-for="Course.Credits" class="control-label"></label>
  25. <input asp-for="Course.Credits" class="form-control" />
  26. <span asp-validation-for="Course.Credits" class="text-danger"></span>
  27. </div>
  28. <div class="form-group">
  29. <label asp-for="Course.Department" class="control-label"></label>
  30. <select asp-for="Course.DepartmentID" class="form-control"
  31. asp-items="@Model.DepartmentNameSL"></select>
  32. <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
  33. </div>
  34. <div class="form-group">
  35. <input type="submit" value="Save" class="btn btn-primary" />
  36. </div>
  37. </form>
  38. </div>
  39. </div>
  40. <div>
  41. <a asp-page="./Index">Back to List</a>
  42. </div>
  43. @section Scripts {
  44. @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
  45. }

上面的代码执行以下更改:

  • 显示课程 ID。通常不显示实体的主键 (PK)。PK 对用户不具有任何意义。在这种情况下,PK 就是课程编号。
  • 将“院系”下拉列表的标题从“DepartmentID”更改为“Department” 。
  • "ViewBag.DepartmentID" 替换为 DepartmentNameSL(来自基类)。

该页面包含课程编号的隐藏域 (<input type="hidden">)。添加具有 asp-for="Course.CourseID"<label> 标记帮助器也同样需要隐藏域。用户单击“保存”时,需要 <input type="hidden"> ,以便在已发布的数据中包括课程编号。

更新课程“详细信息”和“删除”页Update the Course Details and Delete pages

AsNoTracking 可以在不需要跟踪时提高性能。

更新“课程”页模型Update the Course page models

使用以下代码更新 Pages/Courses/Delete.cshtml.cs 以添加 AsNoTracking

  1. using ContosoUniversity.Models;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.AspNetCore.Mvc.RazorPages;
  4. using Microsoft.EntityFrameworkCore;
  5. using System.Threading.Tasks;
  6. namespace ContosoUniversity.Pages.Courses
  7. {
  8. public class DeleteModel : PageModel
  9. {
  10. private readonly ContosoUniversity.Data.SchoolContext _context;
  11. public DeleteModel(ContosoUniversity.Data.SchoolContext context)
  12. {
  13. _context = context;
  14. }
  15. [BindProperty]
  16. public Course Course { get; set; }
  17. public async Task<IActionResult> OnGetAsync(int? id)
  18. {
  19. if (id == null)
  20. {
  21. return NotFound();
  22. }
  23. Course = await _context.Courses
  24. .AsNoTracking()
  25. .Include(c => c.Department)
  26. .FirstOrDefaultAsync(m => m.CourseID == id);
  27. if (Course == null)
  28. {
  29. return NotFound();
  30. }
  31. return Page();
  32. }
  33. public async Task<IActionResult> OnPostAsync(int? id)
  34. {
  35. if (id == null)
  36. {
  37. return NotFound();
  38. }
  39. Course = await _context.Courses.FindAsync(id);
  40. if (Course != null)
  41. {
  42. _context.Courses.Remove(Course);
  43. await _context.SaveChangesAsync();
  44. }
  45. return RedirectToPage("./Index");
  46. }
  47. }
  48. }

在 Pages/Courses/Details.cshtml.cs 文件中进行相同的更改 :

  1. using ContosoUniversity.Models;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.AspNetCore.Mvc.RazorPages;
  4. using Microsoft.EntityFrameworkCore;
  5. using System.Threading.Tasks;
  6. namespace ContosoUniversity.Pages.Courses
  7. {
  8. public class DetailsModel : PageModel
  9. {
  10. private readonly ContosoUniversity.Data.SchoolContext _context;
  11. public DetailsModel(ContosoUniversity.Data.SchoolContext context)
  12. {
  13. _context = context;
  14. }
  15. public Course Course { get; set; }
  16. public async Task<IActionResult> OnGetAsync(int? id)
  17. {
  18. if (id == null)
  19. {
  20. return NotFound();
  21. }
  22. Course = await _context.Courses
  23. .AsNoTracking()
  24. .Include(c => c.Department)
  25. .FirstOrDefaultAsync(m => m.CourseID == id);
  26. if (Course == null)
  27. {
  28. return NotFound();
  29. }
  30. return Page();
  31. }
  32. }
  33. }

更新“课程”Razor 页面Update the Course Razor pages

使用以下代码更新 Pages/Courses/Delete.cshtml :

  1. @page
  2. @model ContosoUniversity.Pages.Courses.DeleteModel
  3. @{
  4. ViewData["Title"] = "Delete";
  5. }
  6. <h2>Delete</h2>
  7. <h3>Are you sure you want to delete this?</h3>
  8. <div>
  9. <h4>Course</h4>
  10. <hr />
  11. <dl class="row">
  12. <dt class="col-sm-2">
  13. @Html.DisplayNameFor(model => model.Course.CourseID)
  14. </dt>
  15. <dd class="col-sm-10">
  16. @Html.DisplayFor(model => model.Course.CourseID)
  17. </dd>
  18. <dt class="col-sm-2">
  19. @Html.DisplayNameFor(model => model.Course.Title)
  20. </dt>
  21. <dd class="col-sm-10">
  22. @Html.DisplayFor(model => model.Course.Title)
  23. </dd>
  24. <dt class="col-sm-2">
  25. @Html.DisplayNameFor(model => model.Course.Credits)
  26. </dt>
  27. <dd class="col-sm-10">
  28. @Html.DisplayFor(model => model.Course.Credits)
  29. </dd>
  30. <dt class="col-sm-2">
  31. @Html.DisplayNameFor(model => model.Course.Department)
  32. </dt>
  33. <dd class="col-sm-10">
  34. @Html.DisplayFor(model => model.Course.Department.Name)
  35. </dd>
  36. </dl>
  37. <form method="post">
  38. <input type="hidden" asp-for="Course.CourseID" />
  39. <input type="submit" value="Delete" class="btn btn-danger" /> |
  40. <a asp-page="./Index">Back to List</a>
  41. </form>
  42. </div>

对“详细信息”页执行相同更改。

  1. @page
  2. @model ContosoUniversity.Pages.Courses.DetailsModel
  3. @{
  4. ViewData["Title"] = "Details";
  5. }
  6. <h2>Details</h2>
  7. <div>
  8. <h4>Course</h4>
  9. <hr />
  10. <dl class="row">
  11. <dt class="col-sm-2">
  12. @Html.DisplayNameFor(model => model.Course.CourseID)
  13. </dt>
  14. <dd class="col-sm-10">
  15. @Html.DisplayFor(model => model.Course.CourseID)
  16. </dd>
  17. <dt class="col-sm-2">
  18. @Html.DisplayNameFor(model => model.Course.Title)
  19. </dt>
  20. <dd class="col-sm-10">
  21. @Html.DisplayFor(model => model.Course.Title)
  22. </dd>
  23. <dt class="col-sm-2">
  24. @Html.DisplayNameFor(model => model.Course.Credits)
  25. </dt>
  26. <dd class="col-sm-10">
  27. @Html.DisplayFor(model => model.Course.Credits)
  28. </dd>
  29. <dt class="col-sm-2">
  30. @Html.DisplayNameFor(model => model.Course.Department)
  31. </dt>
  32. <dd class="col-sm-10">
  33. @Html.DisplayFor(model => model.Course.Department.Name)
  34. </dd>
  35. </dl>
  36. </div>
  37. <div>
  38. <a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
  39. <a asp-page="./Index">Back to List</a>
  40. </div>

测试“课程”页Test the Course pages

测试“创建”、“编辑”、“详细信息”和“删除”页面。

更新讲师“创建”和“编辑”页Update the instructor Create and Edit pages

讲师可能教授任意数量的课程。下图显示包含一系列课程复选框的讲师“编辑”页。

带课程信息的讲师“编辑”页

通过复选框可对分配给讲师的课程进行更改。数据库中的每一门课程均有对应显示的复选框。已分配给讲师的课程将会被选中。用户可以通过选择或清除复选框来更改课程分配。如果课程数过多,另一个 UI 的使用效果可能更好。但此处所示的用于管理多对多关系的方法不会发生变化。若要创建或删除关系,则需要使用联接实体。

为已分配的课程数据创建类Create a class for assigned courses data

使用以下代码创建 SchoolViewModels/AssignedCourseData.cs :

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

AssignedCourseData 类包含的数据可用于为已分配给讲师的课程创建复选框。

创建“讲师”页模型基类Create an Instructor page model base class

创建 Pages/Instructors/InstructorCoursesPageModel.cs 基类 :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

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

            var selectedCoursesHS = new HashSet<string>(selectedCourses);
            var instructorCourses = new HashSet<int>
                (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
            foreach (var course in context.Courses)
            {
                if (selectedCoursesHS.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
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

InstructorCoursesPageModel 是将用于“编辑”和“创建”页模型的基类。PopulateAssignedCourseData 读取所有 Course 实体以填充 AssignedCourseDataList该代码将设置每门课程的 CourseID 和标题,并决定是否为讲师分配该课程。HashSet 用于高效查找。

Razor 页面没有 Course 实体的集合,因此模型绑定器无法自动更新 CourseAssignments 导航属性。可在新的 UpdateInstructorCourses 方法中更新 CourseAssignments 导航属性,而不必使用模型绑定器。为此,需要从模型绑定中排除 CourseAssignments 属性。此操作无需对调用 TryUpdateModel 的代码进行任何更改,因为使用的是允许列表重载,并且 CourseAssignments 不包括在该列表中。

如果未选中任何复选框,则 UpdateInstructorCourses 中的代码将使用空集合初始化 CourseAssignments 导航属性,并返回以下内容:

if (selectedCourses == null)
{
    instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
    return;
}

之后,代码会循环访问数据库中的所有课程,并逐一检查当前分配给讲师的课程和页面中处于选中状态的课程。为便于高效查找,后两个集合存储在 HashSet 对象中。

如果某课程的复选框处于选中状态,但该课程不在 Instructor.CourseAssignments 导航属性中,则会将该课程添加到导航属性中的集合中。

if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
    if (!instructorCourses.Contains(course.CourseID))
    {
        instructorToUpdate.CourseAssignments.Add(
            new CourseAssignment
            {
                InstructorID = instructorToUpdate.ID,
                CourseID = course.CourseID
            });
    }
}

如果某课程的复选框未处于选中状态,但该课程存在 Instructor.CourseAssignments 导航属性中,则会从导航属性中删除该课程。

else
{
    if (instructorCourses.Contains(course.CourseID))
    {
        CourseAssignment courseToRemove
            = instructorToUpdate
                .CourseAssignments
                .SingleOrDefault(i => i.CourseID == course.CourseID);
        context.Remove(courseToRemove);
    }
}

处理办公室位置Handle office location

“编辑”页必须处理的另一个关系是 Instructor 实体与 OfficeAssignment 实体之间的一对零或一对一关系。讲师编辑代码必须处理以下场景:

  • 如果用户清除办公室分配,则需删除 OfficeAssignment 实体。
  • 如果用户输入办公室分配,且办公室分配原本为空,则需创建一个新 OfficeAssignment 实体。
  • 如果用户更改办公室分配,则需更新 OfficeAssignment 实体。

更新讲师“编辑”页模型Update the Instructor Edit page model

使用以下代码更新 Pages/Instructors/Edit.cshtml.cs :

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class EditModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            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(_context, Instructor);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(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 (instructorToUpdate == null)
            {
                return NotFound();
            }

            if (await TryUpdateModelAsync<Instructor>(
                instructorToUpdate,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                if (String.IsNullOrWhiteSpace(
                    instructorToUpdate.OfficeAssignment?.Location))
                {
                    instructorToUpdate.OfficeAssignment = null;
                }
                UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            PopulateAssignedCourseData(_context, instructorToUpdate);
            return Page();
        }
    }
}

前面的代码:

  • 使用 OfficeAssignmentCourseAssignmentCourseAssignment.Course 导航属性的预先加载从数据库获取当前的 Instructor 实体。
  • 用模型绑定器中的值更新检索到的 Instructor 实体。TryUpdateModel 可防止过多发布
  • 如果办公室位置为空,则将 Instructor.OfficeAssignment 设置为 null。当 Instructor.OfficeAssignment 为 null 时,OfficeAssignment 表中的相关行将会删除。
  • 调用 OnGetAsync 中的 PopulateAssignedCourseData,使用 AssignedCourseData 视图模型类为复选框提供信息。
  • 调用 OnPostAsync 中的 UpdateInstructorCourses,将复选框中的信息应用于将要编辑的 Instructor 实体。
  • 如果 TryUpdateModel 失败,则调用 OnPostAsync 中的 PopulateAssignedCourseDataUpdateInstructorCourses。这些方法调用将在页面重新显示错误消息时还原页面上所输入的已分配课程数据。

更新讲师“编辑”Razor 页面Update the Instructor Edit Razor page

使用以下代码更新 Pages/Instructors/Edit.cshtml :

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    if (cnt++ % 3 == 0)
                                    {
                                        @:</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>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

上面的代码将创建一个具有三列的 HTML 表。每列均具有一个复选框和包含课程编号及标题的标题。所有复选框均具有相同的名称(“selectedCourses”)。使用相同名称可指示模型绑定器将它们视为一个组。每个复选框的值特性都设置为 CourseID发布页面时,模型绑定器会传递一个数组,该数组只包括所选复选框的 CourseID 值。

初次呈现复选框时,分配给讲师的课程均已选中。

注意:此处所使用的编辑讲师课程数据的方法适用于数量有限的课程。对于规模远大于此的集合,则使用不同的 UI 和不同的更新方法会更实用和更高效。

运行应用并测试更新的讲师“编辑”页。更改某些课程分配。这些更改将反映在“索引”页上。

更新讲师“创建”页Update the Instructor Create page

使用类似于“编辑”页的代码更新讲师“创建”页模型和 Razor 页面:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}
@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="table">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    if (cnt++ % 3 == 0)
                                    {
                                        @:</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>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

测试讲师“创建”页。

更新讲师“删除”页Update the Instructor Delete page

使用以下代码更新 Pages/Instructors/Delete.cshtml.cs :

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors.FirstOrDefaultAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor instructor = await _context.Instructors
                .Include(i => i.CourseAssignments)
                .SingleAsync(i => i.ID == id);

            if (instructor == null)
            {
                return RedirectToPage("./Index");
            }

            var departments = await _context.Departments
                .Where(d => d.InstructorID == id)
                .ToListAsync();
            departments.ForEach(d => d.InstructorID = null);

            _context.Instructors.Remove(instructor);

            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
    }
}

上面的代码执行以下更改:

  • CourseAssignments 导航属性使用预先加载。必须包含 CourseAssignments,否则删除讲师时将不会删除课程。为避免阅读它们,可以在数据库中配置级联删除。

  • 如果要删除的讲师被指派为任何系的管理员,则需从这些系中删除该讲师分配。

运行应用并测试“删除”页。

后续步骤Next steps

上一个教程下一个教程

本教程演示如何更新相关数据。如果遇到无法解决的问题,请下载或查看已完成的应用下载说明

下图显示了部分已完成页面。

课程“编辑”页讲师”编辑”页

查看并测试“创建”和“编辑”课程页。创建新课程。系按其主键(一个整数)进行选择,而不是按名称。编辑新课程。完成测试后,请删除新课程。

创建基类以共享通用代码Create a base class to share common code

“课程/创建”和“课程/编辑”页分别需要一个系名称列表。针对“创建”和“编辑”页创建 Pages/Courses/DepartmentNamePageModel.cshtml.cs 基类 :

using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
    public class DepartmentNamePageModel : PageModel
    {
        public SelectList DepartmentNameSL { get; set; }

        public void PopulateDepartmentsDropDownList(SchoolContext _context,
            object selectedDepartment = null)
        {
            var departmentsQuery = from d in _context.Departments
                                   orderby d.Name // Sort by name.
                                   select d;

            DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(),
                        "DepartmentID", "Name", selectedDepartment);
        }
    }
}

上面的代码创建 SelectList 以包含系名称列表。如果指定了 selectedDepartment,可在 SelectList 中选择该系。

“创建”和“编辑”页模型类将派生自 DepartmentNamePageModel

自定义课程页Customize the Courses Pages

创建新的课程实体时,新实体必须与现有院系有关系。若要在创建课程的同时添加系,则“创建”和“编辑”的基类必须包含用于选择系的下拉列表。该下拉列表设置 Course.DepartmentID 外键 (FK) 属性。EF Core 使用 Course.DepartmentID FK 加载 Department 导航属性。

创建课程

使用以下代码更新“创建”页模型:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class CreateModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateDepartmentsDropDownList(_context);
            return Page();
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var emptyCourse = new Course();

            if (await TryUpdateModelAsync<Course>(
                 emptyCourse,
                 "course",   // Prefix for form value.
                 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits))
            {
                _context.Courses.Add(emptyCourse);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID);
            return Page();
        }
      }
}

前面的代码:

  • 派生自 DepartmentNamePageModel
  • 使用 TryUpdateModelAsync 防止过多发布
  • ViewData["DepartmentID"] 替换为 DepartmentNameSL(来自基类)。

ViewData["DepartmentID"] 将替换为强类型 DepartmentNameSL建议使用强类型而非弱类型。有关详细信息,请参阅弱类型数据(ViewData 和 ViewBag)

更新“课程创建”页Update the Courses Create page

使用以下代码更新 Pages/Courses/Create.cshtml :

@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
    ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <input asp-for="Course.CourseID" class="form-control" />
                <span asp-validation-for="Course.CourseID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control"
                        asp-items="@Model.DepartmentNameSL">
                    <option value="">-- Select Department --</option>
                </select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>
<div>
    <a asp-page="Index">Back to List</a>
</div>
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

上述标记进行以下更改:

  • 将标题从“DepartmentID”更改为“Department” 。
  • "ViewBag.DepartmentID" 替换为 DepartmentNameSL(来自基类)。
  • 添加“选择系”选项。此更改将导致呈现“选择系”而不是第一个系。
  • 在未选择系时添加验证消息。

Razor 页面使用选择标记帮助器

<div class="form-group">
    <label asp-for="Course.Department" class="control-label"></label>
    <select asp-for="Course.DepartmentID" class="form-control"
            asp-items="@Model.DepartmentNameSL">
        <option value="">-- Select Department --</option>
    </select>
    <span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

测试“创建”页。“创建”页显示系名称,而不是系 ID。

更新“课程编辑”页。Update the Courses Edit page.

使用以下代码替换 Pages/Courses/Edit.cshtml.cs 中的代码 :

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
    public class EditModel : DepartmentNamePageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public EditModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Course Course { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Course = await _context.Courses
                .Include(c => c.Department).FirstOrDefaultAsync(m => m.CourseID == id);

            if (Course == null)
            {
                return NotFound();
            }

            // Select current DepartmentID.
            PopulateDepartmentsDropDownList(_context,Course.DepartmentID);
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var courseToUpdate = await _context.Courses.FindAsync(id);

            if (await TryUpdateModelAsync<Course>(
                 courseToUpdate,
                 "course",   // Prefix for form value.
                   c => c.Credits, c => c.DepartmentID, c => c.Title))
            {
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }

            // Select DepartmentID if TryUpdateModelAsync fails.
            PopulateDepartmentsDropDownList(_context, courseToUpdate.DepartmentID);
            return Page();
        }       
    }
}

这些更改与在“创建”页模型中所做的更改相似。在上面的代码中,PopulateDepartmentsDropDownList 在系 ID 中传递并将选择下拉列表中指定的系。

使用以下标记更新 Pages/Courses/Edit.cshtml :

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
    ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Course.CourseID" />
            <div class="form-group">
                <label asp-for="Course.CourseID" class="control-label"></label>
                <div>@Html.DisplayFor(model => model.Course.CourseID)</div>
            </div>
            <div class="form-group">
                <label asp-for="Course.Title" class="control-label"></label>
                <input asp-for="Course.Title" class="form-control" />
                <span asp-validation-for="Course.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Credits" class="control-label"></label>
                <input asp-for="Course.Credits" class="form-control" />
                <span asp-validation-for="Course.Credits" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Course.Department" class="control-label"></label>
                <select asp-for="Course.DepartmentID" class="form-control" 
                        asp-items="@Model.DepartmentNameSL"></select>
                <span asp-validation-for="Course.DepartmentID" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

上述标记进行以下更改:

  • 显示课程 ID。通常不显示实体的主键 (PK)。PK 对用户不具有任何意义。在这种情况下,PK 就是课程编号。
  • 将标题从“DepartmentID”更改为“Department” 。
  • "ViewBag.DepartmentID" 替换为 DepartmentNameSL(来自基类)。

该页面包含课程编号的隐藏域 (<input type="hidden">)。添加具有 asp-for="Course.CourseID"<label> 标记帮助器也同样需要隐藏域。用户单击“保存”时,需要 <input type="hidden"> ,以便在已发布的数据中包括课程编号。

测试更新的代码。创建、编辑和删除课程。

将 AsNoTracking 添加到“详细信息”和“删除”页模型Add AsNoTracking to the Details and Delete page models

AsNoTracking 可以在不需要跟踪时提高性能。AsNoTracking 添加到“删除”和“详细信息”页模型。下面的代码显示更新的“删除”页模型:

public class DeleteModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public DeleteModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Course Course { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Course = await _context.Courses
            .AsNoTracking()
            .Include(c => c.Department)
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Course = await _context.Courses
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.CourseID == id);

        if (Course != null)
        {
            _context.Courses.Remove(Course);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage("./Index");
    }
}

在 Pages/Courses/Details.cshtml.cs 文件中更新 OnGetAsync 方法 :

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Course = await _context.Courses
         .AsNoTracking()
         .Include(c => c.Department)
         .FirstOrDefaultAsync(m => m.CourseID == id);

    if (Course == null)
    {
        return NotFound();
    }
    return Page();
}

修改“删除”和“详细信息”页Modify the Delete and Details pages

使用以下标记更新“删除”Razor 页面:

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Course</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Course.CourseID)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.CourseID)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Title)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Title)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Credits)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Credits)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Course.Department)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Course.Department.DepartmentID)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Course.CourseID" />
        <input type="submit" value="Delete" class="btn btn-default" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

对“详细信息”页执行相同更改。

测试“课程”页Test the Course pages

测试“创建”、“编辑”、“详细信息”和“删除”。

更新“讲师”页Update the instructor pages

以下各部分介绍更新“讲师”页。

添加办公室位置Add office location

编辑讲师记录时,可能希望更新讲师的办公室分配。Instructor 实体与 OfficeAssignment 实体是一对零或一关系。讲师代码必须处理:

  • 如果用户清除办公室分配,则需删除 OfficeAssignment 实体。
  • 如果用户输入办公室分配,且办公室分配原本为空,则需创建一个新 OfficeAssignment 实体。
  • 如果用户更改办公室分配,则需更新 OfficeAssignment 实体。

使用以下代码更新讲师“编辑”页模型:

public class EditModel : PageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        Instructor = await _context.Instructors
            .Include(i => i.OfficeAssignment)
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.ID == id);

        if (Instructor == null)
        {
            return NotFound();
        }
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

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

        if (await TryUpdateModelAsync<Instructor>(
            instructorToUpdate,
            "Instructor",
            i => i.FirstMidName, i => i.LastName, 
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            await _context.SaveChangesAsync();
        }
        return RedirectToPage("./Index");

    }
}

前面的代码:

  • 使用 OfficeAssignment 导航属性的预先加载从数据库获取当前的 Instructor 实体。
  • 用模型绑定器中的值更新检索到的 Instructor 实体。TryUpdateModel 可防止过多发布
  • 如果办公室位置为空,则将 Instructor.OfficeAssignment 设置为 null。当 Instructor.OfficeAssignment 为 null 时,OfficeAssignment 表中的相关行将会删除。

更新讲师“编辑”页Update the instructor Edit page

使用办公室位置更新 Pages/Instructors/Edit.cshtml :

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

验证是否可以更改讲师办公室位置。

向“讲师编辑”页添加课程分配Add Course assignments to the instructor Edit page

讲师可能教授任意数量的课程。本部分将添加更改课程分配的功能。下图显示更新的讲师“编辑”页:

带课程信息的讲师“编辑”页

CourseInstructor 具有多对多关系。若要添加和删除关系,可以从 CourseAssignments 联接实体集中添加和删除实体。

通过复选框可对分配给讲师的课程进行更改。数据库中的每一门课程均有对应显示的复选框。已分配给讲师的课程将会被勾选。用户可以通过选择或清除复选框来更改课程分配。如果课程数量过多:

  • 可以使用其他用户界面显示课程。
  • 使用联接实体创建或删除关系的方法不会发生更改。

添加类以支持“创建”和“编辑”讲师页Add classes to support Create and Edit instructor pages

使用以下代码创建 SchoolViewModels/AssignedCourseData.cs :

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class AssignedCourseData
    {
        public int CourseID { get; set; }
        public string Title { get; set; }
        public bool Assigned { get; set; }
    }
}

AssignedCourseData 类包含的数据可用于为讲师已分配的课程创建复选框。

创建 Pages/Instructors/InstructorCoursesPageModel.cshtml.cs 基类 :

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
    public class InstructorCoursesPageModel : PageModel
    {

        public List<AssignedCourseData> AssignedCourseDataList;

        public void PopulateAssignedCourseData(SchoolContext context, 
                                               Instructor instructor)
        {
            var allCourses = context.Courses;
            var instructorCourses = new HashSet<int>(
                instructor.CourseAssignments.Select(c => c.CourseID));
            AssignedCourseDataList = new List<AssignedCourseData>();
            foreach (var course in allCourses)
            {
                AssignedCourseDataList.Add(new AssignedCourseData
                {
                    CourseID = course.CourseID,
                    Title = course.Title,
                    Assigned = instructorCourses.Contains(course.CourseID)
                });
            }
        }

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

            var selectedCoursesHS = new HashSet<string>(selectedCourses);
            var instructorCourses = new HashSet<int>
                (instructorToUpdate.CourseAssignments.Select(c => c.Course.CourseID));
            foreach (var course in context.Courses)
            {
                if (selectedCoursesHS.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
                                .SingleOrDefault(i => i.CourseID == course.CourseID);
                        context.Remove(courseToRemove);
                    }
                }
            }
        }
    }
}

InstructorCoursesPageModel 是将用于“编辑”和“创建”页模型的基类。PopulateAssignedCourseData 读取所有 Course 实体以填充 AssignedCourseDataList该代码将设置每门课程的 CourseID 和标题,并决定是否为讲师分配该课程。HashSet 用于创建高效查找。

讲师“编辑”页模型Instructors Edit page model

使用以下代码更新讲师“编辑”页模型:

public class EditModel : InstructorCoursesPageModel
{
    private readonly ContosoUniversity.Data.SchoolContext _context;

    public EditModel(ContosoUniversity.Data.SchoolContext context)
    {
        _context = context;
    }

    [BindProperty]
    public Instructor Instructor { get; set; }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        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(_context, Instructor);
        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int? id, string[] selectedCourses)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        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,
            "Instructor",
            i => i.FirstMidName, i => i.LastName,
            i => i.HireDate, i => i.OfficeAssignment))
        {
            if (String.IsNullOrWhiteSpace(
                instructorToUpdate.OfficeAssignment?.Location))
            {
                instructorToUpdate.OfficeAssignment = null;
            }
            UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
        UpdateInstructorCourses(_context, selectedCourses, instructorToUpdate);
        PopulateAssignedCourseData(_context, instructorToUpdate);
        return Page();
    }
}

上面的代码处理办公室分配更改。

更新“讲师”Razor 视图:

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
    ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Instructor.ID" />
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    if (cnt++ % 3 == 0)
                                    {
                                        @:</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>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

备注

将代码粘贴到 Visual Studio 中时,换行符会发生更改,其更改方式会导致代码中断。按 Ctrl+Z 一次可撤消自动格式设置。按 Ctrl+Z 可以修复换行符,使其看起来如此处所示。缩进不一定要呈现完美形式,但 @:</tr><tr>@:<td>@:</td>@:</tr> 行必须各成一行(如下所示)。选中新的代码块后,按 Tab 三次,使新代码与现有代码对齐。通过此链接投票或查看此 bug 的状态。

上面的代码将创建一个具有三列的 HTML 表。每列均具有一个复选框和包含课程编号及标题的标题。所有复选框均具有相同的名称(“selectedCourses”)。使用相同名称可指示模型绑定器将它们视为一个组。每个复选框的值特性都设置为 CourseID发布页面时,模型绑定器会传递一个数组,该数组只包括所选复选框的 CourseID 值。

初次呈现复选框时,分配给讲师的课程具有已勾选的特性。

运行应用并测试更新的讲师“编辑”页。更改某些课程分配。这些更改将反映在“索引”页上。

注意:此处所使用的编辑讲师课程数据的方法适用于数量有限的课程。对于规模远大于此的集合,则使用不同的 UI 和不同的更新方法会更实用和更高效。

更新讲师“创建”页Update the instructors Create page

使用以下代码更新讲师“创建”页模型:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class CreateModel : InstructorCoursesPageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public CreateModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            var instructor = new Instructor();
            instructor.CourseAssignments = new List<CourseAssignment>();

            // Provides an empty collection for the foreach loop
            // foreach (var course in Model.AssignedCourseDataList)
            // in the Create Razor page.
            PopulateAssignedCourseData(_context, instructor);
            return Page();
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnPostAsync(string[] selectedCourses)
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            var newInstructor = new Instructor();
            if (selectedCourses != null)
            {
                newInstructor.CourseAssignments = new List<CourseAssignment>();
                foreach (var course in selectedCourses)
                {
                    var courseToAdd = new CourseAssignment
                    {
                        CourseID = int.Parse(course)
                    };
                    newInstructor.CourseAssignments.Add(courseToAdd);
                }
            }

            if (await TryUpdateModelAsync<Instructor>(
                newInstructor,
                "Instructor",
                i => i.FirstMidName, i => i.LastName,
                i => i.HireDate, i => i.OfficeAssignment))
            {
                _context.Instructors.Add(newInstructor);                
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            PopulateAssignedCourseData(_context, newInstructor);
            return Page();
        }
    }
}

前面的代码与 Pages/Instructors/Edit.cshtml.cs 代码类似 。

使用以下标记更新讲师“创建”Razor 页面:

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Instructor.LastName" class="control-label"></label>
                <input asp-for="Instructor.LastName" class="form-control" />
                <span asp-validation-for="Instructor.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.FirstMidName" class="control-label"></label>
                <input asp-for="Instructor.FirstMidName" class="form-control" />
                <span asp-validation-for="Instructor.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Instructor.HireDate" class="control-label"></label>
                <input asp-for="Instructor.HireDate" class="form-control" />
                <span asp-validation-for="Instructor.HireDate" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="Instructor.OfficeAssignment.Location" class="control-label"></label>
                <input asp-for="Instructor.OfficeAssignment.Location" class="form-control" />
                <span asp-validation-for="Instructor.OfficeAssignment.Location" class="text-danger" />
            </div>
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <table>
                        <tr>
                            @{
                                int cnt = 0;

                                foreach (var course in Model.AssignedCourseDataList)
                                {
                                    if (cnt++ % 3 == 0)
                                    {
                                        @:</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>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

测试讲师“创建”页。

更新“删除”页Update the Delete page

使用以下代码更新“删除”页模型:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Instructor Instructor { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Instructor = await _context.Instructors.SingleAsync(m => m.ID == id);

            if (Instructor == null)
            {
                return NotFound();
            }
            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int id)
        {
            Instructor instructor = await _context.Instructors
                .Include(i => i.CourseAssignments)
                .SingleAsync(i => i.ID == id);

            var departments = await _context.Departments
                .Where(d => d.InstructorID == id)
                .ToListAsync();
            departments.ForEach(d => d.InstructorID = null);

            _context.Instructors.Remove(instructor);

            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }
    }
}

上面的代码执行以下更改:

  • CourseAssignments 导航属性使用预先加载。必须包含 CourseAssignments,否则删除讲师时将不会删除课程。为避免阅读它们,可以在数据库中配置级联删除。

  • 如果要删除的讲师被指派为任何系的管理员,则需从这些系中删除该讲师分配。

其他资源Additional resources

上一页下一页