第 7 部分,ASP.NET Core 中的 Razor Pages 和 EF Core - 更新相关数据
作者:Tom Dykstra、Jon P Smith 和 Rick Anderson
Contoso University Web 应用演示了如何使用 EF Core 和 Visual Studio 创建 Razor Pages Web 应用。 若要了解系列教程,请参阅第一个教程。
如果遇到无法解决的问题,请下载已完成的应用,然后对比该代码与按教程所创建的代码。
本教程将介绍如何更新相关数据。 下图显示了部分已完成页面。
更新课程“创建”和“编辑”页
课程“创建”和“编辑”页的基架搭建代码具有显示 DepartmentID
(int
) 的“院系”下拉列表。 下拉列表应显示院系名称,因此这两个页面都需要院系名称列表。 若要提供该列表,请使用“创建”和“编辑”页的基类。
创建课程“创建”和“编辑”的基类
使用以下代码创建一个 Pages/Courses/DepartmentNamePageModel.cs
文件:
using ContosoUniversity.Data;
using ContosoUniversity.Models;
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(),
nameof(Department.DepartmentID),
nameof(Department.Name),
selectedDepartment);
}
}
}
上面的代码创建 SelectList 以包含系名称列表。 如果指定了 selectedDepartment
,可在 SelectList
中选择该系。
“创建”和“编辑”页模型类将派生自 DepartmentNamePageModel
。
更新课程“创建”页模型
课程分配给院系。 “创建”和“编辑”页的基类提供 SelectList
,用于选择院系。 采用 SelectList
的下拉列表设置 Course.DepartmentID
外键 (FK) 属性。 EF Core 使用 Course.DepartmentID
FK 加载 Department
导航属性。
使用以下代码更新 Pages/Courses/Create.cshtml.cs
:
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()
{
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();
}
}
}
若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。
前面的代码:
- 派生自
DepartmentNamePageModel
。 - 使用 TryUpdateModelAsync 防止过多发布。
- 删除
ViewData["DepartmentID"]
。DepartmentNameSL
SelectList
是强类型模型,将用于 Razor 页面。 建议使用强类型而非弱类型。 有关详细信息,请参阅弱类型数据(ViewData 和 ViewBag)。
更新“课程创建”Razor 页面
使用以下代码更新 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-primary" />
</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。
更新课程“编辑”页模型
使用以下代码更新 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 (id == null)
{
return NotFound();
}
var courseToUpdate = await _context.Courses.FindAsync(id);
if (courseToUpdate == null)
{
return NotFound();
}
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 中传递并将在下拉列表中选择该院系。
更新“课程编辑”Razor 页面
使用以下代码更新 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-primary" />
</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 可以在不需要跟踪时提高性能。
通过将 AsNoTracking
添加到 OnGetAsync
方法来更新 Pages/Courses/Delete.cshtml.cs
和 Pages/Courses/Details.cshtml.cs
:
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();
}
更新“课程”Razor 页面
使用以下代码更新 Pages/Courses/Delete.cshtml
:
@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="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
对“详细信息”页执行相同更改。
@page
@model ContosoUniversity.Pages.Courses.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
测试“课程”页
测试“创建”、“编辑”、“详细信息”和“删除”页面。
更新讲师“创建”和“编辑”页
讲师可能教授任意数量的课程。 下图显示包含一系列课程复选框的讲师“编辑”页。
通过复选框可对分配给讲师的课程进行更改。 数据库中的每一门课程均有对应显示的复选框。 已分配给讲师的课程将会被选中。 用户可以通过选择或清除复选框来更改课程分配。 如果课程数过多,另一个 UI 的使用效果可能更好。 但此处所示的用于管理多对多关系的方法不会发生变化。 若要创建或删除关系,则需要使用联接实体。
为已分配的课程数据创建类
使用以下代码创建 Models/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.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.Courses.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)
});
}
}
}
}
InstructorCoursesPageModel
是用于“编辑”和“创建”页模型的基类。 PopulateAssignedCourseData
读取所有 Course
实体以填充 AssignedCourseDataList
。 该代码将设置每门课程的 CourseID
和标题,并决定是否为讲师分配该课程。 HashSet 用于高效查找。
处理办公室位置
“编辑”页必须处理的另一个关系是 Instructor 实体与 OfficeAssignment
实体之间的一对零或一对一关系。 讲师编辑代码必须处理以下场景:
- 如果用户清除办公室分配,则需删除
OfficeAssignment
实体。 - 如果用户输入办公室分配,且办公室分配原本为空,则需创建一个新
OfficeAssignment
实体。 - 如果用户更改办公室分配,则需更新
OfficeAssignment
实体。
更新讲师“编辑”页模型
使用以下代码更新 Pages/Instructors/Edit.cshtml.cs
:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
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.Courses)
.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.Courses)
.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(selectedCourses, instructorToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(_context, instructorToUpdate);
return Page();
}
public void UpdateInstructorCourses(string[] selectedCourses,
Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
var selectedCoursesHS = new HashSet<string>(selectedCourses);
var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove = instructorToUpdate.Courses.Single(
c => c.CourseID == course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}
}
}
}
}
前面的代码:
- 使用
OfficeAssignment
和Courses
导航属性的预先加载从数据库获取当前的Instructor
实体。 - 用模型绑定器中的值更新检索到的
Instructor
实体。 TryUpdateModelAsync 可防止过多发布。 - 如果办公室位置为空,则将
Instructor.OfficeAssignment
设置为 null。 当Instructor.OfficeAssignment
为 null 时,OfficeAssignment
表中的相关行将会删除。 - 调用
OnGetAsync
中的PopulateAssignedCourseData
,使用AssignedCourseData
视图模型类为复选框提供信息。 - 调用
OnPostAsync
中的UpdateInstructorCourses
,将复选框中的信息应用于将要编辑的 Instructor 实体。 - 如果 TryUpdateModelAsync 失败,则调用
OnPostAsync
中的PopulateAssignedCourseData
和UpdateInstructorCourses
。 这些方法调用将在页面重新显示错误消息时还原页面上所输入的已分配课程数据。
Razor 页面没有 Course 实体的集合,因此模型绑定器无法自动更新 Courses
导航属性。 可在新的 UpdateInstructorCourses
方法中更新 Courses
导航属性,而不必使用模型绑定器。 为此,需要从模型绑定中排除 Courses
属性。 此操作无需对调用 TryUpdateModelAsync 的代码进行任何更改,因为使用的重载包含已声明的属性,并且 Courses
不包括在该列表中。
如果未选中任何复选框,则 UpdateInstructorCourses
中的代码将使用空集合初始化 instructorToUpdate.Courses
,并返回以下内容:
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
之后,代码会循环访问数据库中的所有课程,并逐一检查当前分配给讲师的课程和页面中处于选中状态的课程。 为便于高效查找,后两个集合存储在 HashSet
对象中。
如果某课程的复选框处于选中状态,但该课程不在 Instructor.Courses
导航属性中,则会将该课程添加到导航属性中的集合中。
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
如果某课程的复选框未处于选中状态,但该课程存在 Instructor.Courses
导航属性中,则会从导航属性中删除该课程。
else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove = instructorToUpdate.Courses.Single(
c => c.CourseID == course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}
更新“讲师编辑”Razor 页面
使用以下代码更新 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 和不同的更新方法会更实用和更高效。
运行应用并测试更新的讲师“编辑”页。 更改某些课程分配。 这些更改将反映在“索引”页上。
更新讲师“创建”页
使用类似于“编辑”页面的代码更新“讲师创建”页面模型:
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class CreateModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<InstructorCoursesPageModel> _logger;
public CreateModel(SchoolContext context,
ILogger<InstructorCoursesPageModel> logger)
{
_context = context;
_logger = logger;
}
public IActionResult OnGet()
{
var instructor = new Instructor();
instructor.Courses = new List<Course>();
// 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.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}
// Add selected Courses courses to the new instructor.
foreach (var course in selectedCourses)
{
var foundCourse = await _context.Courses.FindAsync(int.Parse(course));
if (foundCourse != null)
{
newInstructor.Courses.Add(foundCourse);
}
else
{
_logger.LogWarning("Course {course} not found", course);
}
}
try
{
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");
}
return RedirectToPage("./Index");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
PopulateAssignedCourseData(_context, newInstructor);
return Page();
}
}
}
前面的代码:
添加日志记录以记录警告和错误消息。
调用 Load,它将通过一个数据库调用提取所有课程。 将 FindAsync 用于小型集合时,可优化性能。 无需发出数据库请求,
FindAsync
就会返回跟踪的实体。public async Task<IActionResult> OnPostAsync(string[] selectedCourses) { var newInstructor = new Instructor(); if (selectedCourses.Length > 0) { newInstructor.Courses = new List<Course>(); // Load collection with one DB call. _context.Courses.Load(); } // Add selected Courses courses to the new instructor. foreach (var course in selectedCourses) { var foundCourse = await _context.Courses.FindAsync(int.Parse(course)); if (foundCourse != null) { newInstructor.Courses.Add(foundCourse); } else { _logger.LogWarning("Course {course} not found", course); } } try { 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"); } return RedirectToPage("./Index"); } catch (Exception ex) { _logger.LogError(ex.Message); } PopulateAssignedCourseData(_context, newInstructor); return Page(); }
_context.Instructors.Add(newInstructor)
利用多对多关系创建新的Instructor
,而无需显式映射联接表。 EF 5.0 中添加了多对多关系。
测试讲师“创建”页。
使用类似于“编辑”页面的代码更新“讲师创建”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="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");}
}
更新讲师“删除”页
使用以下代码更新 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.Courses)
.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");
}
}
}
上面的代码执行以下更改:
对
Courses
导航属性使用预先加载。 必须包含Courses
,否则删除讲师时将不会删除课程。 为避免阅读它们,可以在数据库中配置级联删除。如果要删除的讲师被指派为任何系的管理员,则需从这些系中删除该讲师分配。
运行应用并测试“删除”页。
后续步骤
本教程将介绍如何更新相关数据。 下图显示了部分已完成页面。
更新课程“创建”和“编辑”页
课程“创建”和“编辑”页的基架搭建代码具有显示院系 ID(整数)的“院系”下拉列表。 下拉列表应显示院系名称,因此这两个页面都需要院系名称列表。 若要提供该列表,请使用“创建”和“编辑”页的基类。
创建课程“创建”和“编辑”的基类
使用以下代码创建一个 Pages/Courses/DepartmentNamePageModel.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
。
更新课程“创建”页模型
课程分配给院系。 “创建”和“编辑”页的基类提供 SelectList
,用于选择院系。 采用 SelectList
的下拉列表设置 Course.DepartmentID
外键 (FK) 属性。 EF Core 使用 Course.DepartmentID
FK 加载 Department
导航属性。
使用以下代码更新 Pages/Courses/Create.cshtml.cs
:
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()
{
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();
}
}
}
若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。
前面的代码:
- 派生自
DepartmentNamePageModel
。 - 使用
TryUpdateModelAsync
防止过多发布。 - 删除
ViewData["DepartmentID"]
。 基类中的DepartmentNameSL
是强类型模型,将用于 Razor 页面。 建议使用强类型而非弱类型。 有关详细信息,请参阅弱类型数据(ViewData 和 ViewBag)。
更新“课程创建”Razor 页面
使用以下代码更新 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-primary" />
</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。
更新课程“编辑”页模型
使用以下代码更新 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 (id == null)
{
return NotFound();
}
var courseToUpdate = await _context.Courses.FindAsync(id);
if (courseToUpdate == null)
{
return NotFound();
}
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 中传递并将在下拉列表中选择该院系。
更新“课程编辑”Razor 页面
使用以下代码更新 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-primary" />
</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 可以在不需要跟踪时提高性能。
更新“课程”页模型
使用以下代码更新 Pages/Courses/Delete.cshtml.cs
以添加 AsNoTracking
:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
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.FindAsync(id);
if (Course != null)
{
_context.Courses.Remove(Course);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
}
}
在 Pages/Courses/Details.cshtml.cs
文件中进行相同的更改:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class DetailsModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public DetailsModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
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();
}
}
}
更新“课程”Razor 页面
使用以下代码更新 Pages/Courses/Delete.cshtml
:
@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="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
对“详细信息”页执行相同更改。
@page
@model ContosoUniversity.Pages.Courses.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
测试“课程”页
测试“创建”、“编辑”、“详细信息”和“删除”页面。
更新讲师“创建”和“编辑”页
讲师可能教授任意数量的课程。 下图显示包含一系列课程复选框的讲师“编辑”页。
通过复选框可对分配给讲师的课程进行更改。 数据库中的每一门课程均有对应显示的复选框。 已分配给讲师的课程将会被选中。 用户可以通过选择或清除复选框来更改课程分配。 如果课程数过多,另一个 UI 的使用效果可能更好。 但此处所示的用于管理多对多关系的方法不会发生变化。 若要创建或删除关系,则需要使用联接实体。
为已分配的课程数据创建类
使用以下代码创建 Models/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.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);
}
}
处理办公室位置
“编辑”页必须处理的另一个关系是 Instructor 实体与 OfficeAssignment
实体之间的一对零或一对一关系。 讲师编辑代码必须处理以下场景:
- 如果用户清除办公室分配,则需删除
OfficeAssignment
实体。 - 如果用户输入办公室分配,且办公室分配原本为空,则需创建一个新
OfficeAssignment
实体。 - 如果用户更改办公室分配,则需更新
OfficeAssignment
实体。
更新讲师“编辑”页模型
使用以下代码更新 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();
}
}
}
前面的代码:
- 使用
OfficeAssignment
、CourseAssignment
和CourseAssignment.Course
导航属性的预先加载从数据库获取当前的Instructor
实体。 - 用模型绑定器中的值更新检索到的
Instructor
实体。TryUpdateModel
可防止过多发布。 - 如果办公室位置为空,则将
Instructor.OfficeAssignment
设置为 null。 当Instructor.OfficeAssignment
为 null 时,OfficeAssignment
表中的相关行将会删除。 - 调用
OnGetAsync
中的PopulateAssignedCourseData
,使用AssignedCourseData
视图模型类为复选框提供信息。 - 调用
OnPostAsync
中的UpdateInstructorCourses
,将复选框中的信息应用于将要编辑的 Instructor 实体。 - 如果
TryUpdateModel
失败,则调用OnPostAsync
中的PopulateAssignedCourseData
和UpdateInstructorCourses
。 这些方法调用将在页面重新显示错误消息时还原页面上所输入的已分配课程数据。
更新“讲师编辑”Razor 页面
使用以下代码更新 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 和不同的更新方法会更实用和更高效。
运行应用并测试更新的讲师“编辑”页。 更改某些课程分配。 这些更改将反映在“索引”页上。
更新讲师“创建”页
使用类似于“编辑”页面的代码更新“讲师创建”页面模型和 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");}
}
测试讲师“创建”页。
更新讲师“删除”页
使用以下代码更新 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
,否则删除讲师时将不会删除课程。 为避免阅读它们,可以在数据库中配置级联删除。如果要删除的讲师被指派为任何系的管理员,则需从这些系中删除该讲师分配。
运行应用并测试“删除”页。
后续步骤
本教程演示如何更新相关数据。 如果遇到无法解决的问题,请下载或查看已完成的应用。下载说明。
下图显示了部分已完成页面。
查看并测试“创建”和“编辑”课程页。 创建新课程。 系按其主键(一个整数)进行选择,而不是按名称。 编辑新课程。 完成测试后,请删除新课程。
创建基类以共享通用代码
“课程/创建”和“课程/编辑”页分别需要一个系名称列表。 创建“创建”和“编辑”页的 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
。
自定义课程页
创建新的课程实体时,新实体必须与现有院系有关系。 若要在创建课程的同时添加系,则“创建”和“编辑”的基类必须包含用于选择系的下拉列表。 该下拉列表设置 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)。
更新“课程创建”页
使用以下代码更新 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。
更新“课程编辑”页。
将 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 添加到“详细信息”和“删除”页模型
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();
}
修改“删除”和“详细信息”页
使用以下标记更新“删除”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>
对“详细信息”页执行相同更改。
测试“课程”页
测试“创建”、“编辑”、“详细信息”和“删除”。
更新“讲师”页
以下各部分介绍更新“讲师”页。
添加办公室位置
编辑讲师记录时,可能希望更新讲师的办公室分配。 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
表中的相关行将会删除。
更新讲师“编辑”页
使用办公室位置更新 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");}
}
验证是否可以更改讲师办公室位置。
向“讲师编辑”页添加课程分配
讲师可能教授任意数量的课程。 本部分将添加更改课程分配的功能。 下图显示更新的讲师“编辑”页:
Course
和 Instructor
具有多对多关系。 若要添加和删除关系,可以从 CourseAssignments
联接实体集中添加和删除实体。
通过复选框可对分配给讲师的课程进行更改。 数据库中的每一门课程均有对应显示的复选框。 已分配给讲师的课程将会被勾选。 用户可以通过选择或清除复选框来更改课程分配。 如果课程数量过多:
- 可以使用其他用户界面显示课程。
- 使用联接实体创建或删除关系的方法不会发生更改。
添加类以支持“创建”和“编辑”讲师页
使用以下代码创建 Models/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 用于创建高效查找。
讲师“编辑”页模型
使用以下代码更新讲师“编辑”页模型:
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 和不同的更新方法会更实用和更高效。
更新讲师“创建”页
使用以下代码更新讲师“创建”页模型:
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");}
}
测试讲师“创建”页。
更新“删除”页
使用以下代码更新“删除”页模型:
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
,否则删除讲师时将不会删除课程。 为避免阅读它们,可以在数据库中配置级联删除。如果要删除的讲师被指派为任何系的管理员,则需从这些系中删除该讲师分配。