Część 7, Razor strony z EF Core ASP.NET Core — aktualizowanie powiązanych danych
Przez Tom Dykstra, Jon P Smith i Rick Anderson
Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje internetowe stron przy użyciu programu EF Core Visual Studio. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek.
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację i porównaj ten kod z utworzonymi elementami, wykonując czynności opisane w samouczku.
W tym samouczku pokazano, jak zaktualizować powiązane dane. Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.
Aktualizowanie stron tworzenia i edytowania kursu
Kod szkieletowy dla stron Tworzenie i edytowanie kursu zawiera listę rozwijaną Dział z listą rozwijaną , DepartmentID
na przykład int
. Na liście rozwijanej powinna być wyświetlana nazwa działu, więc obie te strony wymagają listy nazw działów. Aby podać listę, użyj klasy bazowej dla stron Tworzenie i edytowanie.
Tworzenie klasy bazowej na potrzeby tworzenia i edytowania kursu
Pages/Courses/DepartmentNamePageModel.cs
Utwórz plik z następującym kodem:
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);
}
}
}
Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment
zostanie określony, ten dział zostanie wybrany w elemecie SelectList
.
Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel
.
Aktualizowanie modelu strony Tworzenie kursu
Kurs jest przypisywany do działu. Klasa bazowa dla stron Tworzenie i Edytowanie udostępnia SelectList
element do wybierania działu. Lista rozwijana, która używa SelectList
właściwości klucza obcego Course.DepartmentID
(FK). EF Core używa klucza Course.DepartmentID
FK do załadowania Department
właściwości nawigacji.
Zaktualizuj Pages/Courses/Create.cshtml.cs
za pomocą następującego kodu:
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();
}
}
}
Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.
Powyższy kod ma następujące działanie:
- Pochodzi z klasy
DepartmentNamePageModel
. - Używa TryUpdateModelAsync metody , aby zapobiec przesłonięć.
- Usuwa element
ViewData["DepartmentID"]
. JestDepartmentNameSL
SelectList
to silnie typowany model i będzie używany przez Razor stronę. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).
Aktualizowanie strony Tworzenie Razor kursu
Zaktualizuj Pages/Courses/Create.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod wprowadza następujące zmiany:
- Zmienia podpis z Identyfikator działu na Dział.
- Zamienia wartość
"ViewBag.DepartmentID"
naDepartmentNameSL
(z klasy bazowej). - Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie pozycji "Wybierz dział" na liście rozwijanej, gdy żaden dział nie został jeszcze wybrany, a nie pierwszy dział.
- Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.
Strona Razor używa pomocnika Wybierz tag:
<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>
Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.
Aktualizowanie modelu strony Edycja kursu
Zaktualizuj Pages/Courses/Edit.cshtml.cs
za pomocą następującego kodu:
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();
}
}
}
Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList
przekazuje identyfikator działu, który wybiera ten dział na liście rozwijanej.
Aktualizowanie strony Edytowanie Razor kursu
Zaktualizuj Pages/Courses/Edit.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod wprowadza następujące zmiany:
- Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
- Zmienia podpis listy rozwijanej Dział z Identyfikator działu na Dział.
"ViewBag.DepartmentID"
Zastępuje elementDepartmentNameSL
, który znajduje się w klasie bazowej.
Strona zawiera ukryte pole (<input type="hidden">
) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID"
tagów <label>
w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden">
jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik wybierze pozycję Zapisz.
Aktualizowanie modeli strony Kurs
AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane.
Zaktualizuj Pages/Courses/Delete.cshtml.cs
metodę i Pages/Courses/Details.cshtml.cs
dodając AsNoTracking
je do OnGetAsync
następujących metod:
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();
}
Aktualizowanie stron kursu Razor
Zaktualizuj Pages/Courses/Delete.cshtml
za pomocą następującego kodu:
@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>
Wprowadź te same zmiany na stronie Szczegóły.
@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>
Testowanie stron kursu
Przetestuj strony tworzenia, edytowania, szczegółów i usuwania.
Aktualizowanie stron tworzenia i edytowania instruktora
Instruktorzy mogą uczyć dowolną liczbę kursów. Na poniższej ilustracji przedstawiono stronę edycji instruktora z tablicą pól wyboru oczywiście.
Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Wybrane są kursy przypisane przez instruktora. Użytkownik może zaznaczyć lub wyczyścić pola wyboru, aby zmienić przydziały kursu. Jeśli liczba kursów była znacznie większa, inny interfejs użytkownika może działać lepiej. Jednak metoda zarządzania relacją wiele do wielu pokazana tutaj nie uległa zmianie. Aby utworzyć lub usunąć relacje, manipulujesz jednostką sprzężenia.
Tworzenie klasy dla przypisanych danych kursów
Utwórz Models/SchoolViewModels/AssignedCourseData.cs
za pomocą następującego kodu:
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
Klasa AssignedCourseData
zawiera dane służące do tworzenia pól wyboru dla kursów przypisanych do instruktora.
Tworzenie klasy bazowej modelu strony instruktora
Utwórz klasę bazową 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)
});
}
}
}
}
Jest InstructorCoursesPageModel
to klasa bazowa dla modeli stron Edycja i Tworzenie. PopulateAssignedCourseData
odczytuje wszystkie Course
jednostki w celu wypełnienia AssignedCourseDataList
. Dla każdego kursu kod ustawia CourseID
tytuł , i czy instruktor jest przypisany do kursu. Zestaw hashset służy do wydajnego wyszukiwania.
Obsługa lokalizacji biura
Inną relacją, którą strona edycji musi obsłużyć, jest relacja "jeden do zera" lub "jeden", którą jednostka Instruktor ma z jednostką OfficeAssignment
. Kod edycji instruktora musi obsługiwać następujące scenariusze:
- Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń
OfficeAssignment
jednostkę. - Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową
OfficeAssignment
jednostkę. - Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj
OfficeAssignment
jednostkę.
Aktualizowanie modelu strony Edycji instruktora
Zaktualizuj Pages/Instructors/Edit.cshtml.cs
za pomocą następującego kodu:
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);
}
}
}
}
}
}
Powyższy kod ma następujące działanie:
- Pobiera bieżącą
Instructor
jednostkę z bazy danych przy użyciu chętnegoOfficeAssignment
ładowania dla właściwości nawigacji iCourses
. - Aktualizuje pobraną
Instructor
jednostkę przy użyciu wartości z powiązania modelu. TryUpdateModelAsync zapobiega przesłonięć. - Jeśli lokalizacja biura jest pusta, ustawia wartość
Instructor.OfficeAssignment
null. GdyInstructor.OfficeAssignment
ma wartość null, powiązany wiersz wOfficeAssignment
tabeli zostanie usunięty. - Wywołuje
PopulateAssignedCourseData
metodę wOnGetAsync
celu podania informacji dotyczących pól wyboru przy użyciuAssignedCourseData
klasy modelu widoku. - Wywołuje metodę
UpdateInstructorCourses
wOnPostAsync
celu zastosowania informacji z pól wyboru do edytowanej jednostki Instruktor. - Wywołania
PopulateAssignedCourseData
iUpdateInstructorCourses
w przypadkuOnPostAsync
TryUpdateModelAsync niepowodzenia. Ta metoda wywołuje metodę przywracania przypisanych danych kursów wprowadzonych na stronie, gdy jest ona odtwarzana ponownie z komunikatem o błędzie.
Razor Ponieważ strona nie ma kolekcji jednostek Course, powiązanie modelu nie może automatycznie zaktualizować Courses
właściwości nawigacji. Zamiast używać powiązania modelu do aktualizowania Courses
właściwości nawigacji, odbywa się to w nowej UpdateInstructorCourses
metodzie. W związku z tym należy wykluczyć Courses
właściwość z powiązania modelu. Nie wymaga to żadnej zmiany w kodzie wywołującym, TryUpdateModelAsync ponieważ używasz przeciążenia z zadeklarowanymi właściwościami i Courses
nie znajduje się na liście dołączania.
Jeśli nie zaznaczono żadnych pól wyboru, kod inicjuje UpdateInstructorCourses
instructorToUpdate.Courses
element z pustą kolekcją i zwraca następujące elementy:
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}
Następnie kod przechodzi przez wszystkie kursy w bazie danych i sprawdza każdy kurs względem aktualnie przypisanych do instruktora w porównaniu z tymi, które zostały wybrane na stronie. Aby ułatwić wydajne wyszukiwanie, dwie ostatnie kolekcje są przechowywane w HashSet
obiektach.
Jeśli pole wyboru kursu jest zaznaczone, ale kurs nie znajduje się we Instructor.Courses
właściwości nawigacji, kurs zostanie dodany do kolekcji we właściwości nawigacji.
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
Jeśli pole wyboru kursu nie jest zaznaczone, ale kurs znajduje się we Instructor.Courses
właściwości nawigacji, kurs zostanie usunięty z właściwości nawigacji.
else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove = instructorToUpdate.Courses.Single(
c => c.CourseID == course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}
Aktualizowanie strony Edytowanie Razor instruktora
Zaktualizuj Pages/Instructors/Edit.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID
. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID
wartości tylko zaznaczonych pól wyboru.
Gdy pola wyboru są początkowo renderowane, wybrane są kursy przypisane do instruktora.
Uwaga: Podejście podjęte tutaj do edytowania danych kursu instruktora działa dobrze, gdy istnieje ograniczona liczba kursów. W przypadku kolekcji, które są znacznie większe, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.
Uruchom aplikację i przetestuj zaktualizowaną stronę Edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane na stronie Indeks.
Aktualizowanie strony Tworzenie instruktora
Zaktualizuj model strony Tworzenie instruktora i za pomocą kodu podobnego do strony Edytuj:
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();
}
}
}
Powyższy kod ma następujące działanie:
Dodaje rejestrowanie dla komunikatów ostrzegawczych i komunikatów o błędach.
Wywołuje metodę Load, która pobiera wszystkie kursy w jednym wywołaniu bazy danych. W przypadku małych kolekcji jest to optymalizacja w przypadku korzystania z programu FindAsync.
FindAsync
Zwraca śledzonej jednostki bez żądania do bazy danych.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)
Tworzy noweInstructor
przy użyciu relacji wiele-do-wielu bez jawnego mapowania tabeli sprzężenia. W programie EF 5.0 dodano wiele do wielu.
Przetestuj stronę Tworzenie instruktora.
Zaktualizuj stronę Tworzenie Razor instruktora przy użyciu kodu podobnego do strony Edytuj:
@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");}
}
Aktualizowanie strony Usuwanie instruktora
Zaktualizuj Pages/Instructors/Delete.cshtml.cs
za pomocą następującego kodu:
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");
}
}
}
Powyższy kod wprowadza następujące zmiany:
Używa chętnego
Courses
ładowania dla właściwości nawigacji.Courses
muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj usuwanie kaskadowe w bazie danych.Jeśli instruktor do usunięcia zostanie przypisany jako administrator jakichkolwiek działów, usuwa przydział instruktora z tych działów.
Uruchom aplikację i przetestuj stronę Usuń.
Następne kroki
W tym samouczku pokazano, jak zaktualizować powiązane dane. Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.
Aktualizowanie stron tworzenia i edytowania kursu
Kod szkieletowy dla stron Tworzenie i edytowanie kursu zawiera listę rozwijaną Dział zawierającą identyfikator działu (liczbę całkowitą). Na liście rozwijanej powinna być wyświetlana nazwa działu, więc obie te strony wymagają listy nazw działów. Aby podać listę, użyj klasy bazowej dla stron Tworzenie i edytowanie.
Tworzenie klasy bazowej na potrzeby tworzenia i edytowania kursu
Pages/Courses/DepartmentNamePageModel.cs
Utwórz plik z następującym kodem:
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);
}
}
}
Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment
zostanie określony, ten dział zostanie wybrany w elemecie SelectList
.
Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel
.
Aktualizowanie modelu strony Tworzenie kursu
Kurs jest przypisywany do działu. Klasa bazowa dla stron Tworzenie i Edytowanie udostępnia SelectList
element do wybierania działu. Lista rozwijana, która używa SelectList
właściwości klucza obcego Course.DepartmentID
(FK). EF Core używa klucza Course.DepartmentID
FK do załadowania Department
właściwości nawigacji.
Zaktualizuj Pages/Courses/Create.cshtml.cs
za pomocą następującego kodu:
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();
}
}
}
Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.
Powyższy kod ma następujące działanie:
- Pochodzi z klasy
DepartmentNamePageModel
. - Używa
TryUpdateModelAsync
metody , aby zapobiec przesłonięć. - Usuwa element
ViewData["DepartmentID"]
.DepartmentNameSL
z klasy bazowej jest silnie typizowanego modelu i będzie używany przez Razor stronę. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).
Aktualizowanie strony Tworzenie Razor kursu
Zaktualizuj Pages/Courses/Create.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod wprowadza następujące zmiany:
- Zmienia podpis z Identyfikator działu na Dział.
- Zamienia wartość
"ViewBag.DepartmentID"
naDepartmentNameSL
(z klasy bazowej). - Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie pozycji "Wybierz dział" na liście rozwijanej, gdy żaden dział nie został jeszcze wybrany, a nie pierwszy dział.
- Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.
Strona Razor używa pomocnika Wybierz tag:
<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>
Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.
Aktualizowanie modelu strony Edycja kursu
Zaktualizuj Pages/Courses/Edit.cshtml.cs
za pomocą następującego kodu:
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();
}
}
}
Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList
przekazuje identyfikator działu, który wybiera ten dział na liście rozwijanej.
Aktualizowanie strony Edytowanie Razor kursu
Zaktualizuj Pages/Courses/Edit.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod wprowadza następujące zmiany:
- Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
- Zmienia podpis listy rozwijanej Dział z Identyfikator działu na Dział.
- Zamienia wartość
"ViewBag.DepartmentID"
naDepartmentNameSL
(z klasy bazowej).
Strona zawiera ukryte pole (<input type="hidden">
) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID"
tagów <label>
w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden">
jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz.
Aktualizowanie stron szczegółów kursu i usuwania
AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane.
Aktualizowanie modeli strony Kurs
Zaktualizuj Pages/Courses/Delete.cshtml.cs
element za pomocą następującego kodu, aby dodać AsNoTracking
polecenie :
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");
}
}
}
Wprowadź tę samą zmianę Pages/Courses/Details.cshtml.cs
w pliku:
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();
}
}
}
Aktualizowanie stron kursu Razor
Zaktualizuj Pages/Courses/Delete.cshtml
za pomocą następującego kodu:
@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>
Wprowadź te same zmiany na stronie Szczegóły.
@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>
Testowanie stron kursu
Przetestuj strony tworzenia, edytowania, szczegółów i usuwania.
Aktualizowanie stron tworzenia i edytowania instruktora
Instruktorzy mogą uczyć dowolną liczbę kursów. Na poniższej ilustracji przedstawiono stronę edycji instruktora z tablicą pól wyboru oczywiście.
Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Wybrane są kursy przypisane przez instruktora. Użytkownik może zaznaczyć lub wyczyścić pola wyboru, aby zmienić przydziały kursu. Jeśli liczba kursów była znacznie większa, inny interfejs użytkownika może działać lepiej. Jednak metoda zarządzania relacją wiele do wielu pokazana tutaj nie uległa zmianie. Aby utworzyć lub usunąć relacje, manipulujesz jednostką sprzężenia.
Tworzenie klasy dla przypisanych danych kursów
Utwórz Models/SchoolViewModels/AssignedCourseData.cs
za pomocą następującego kodu:
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
Klasa AssignedCourseData
zawiera dane służące do tworzenia pól wyboru dla kursów przypisanych do instruktora.
Tworzenie klasy bazowej modelu strony instruktora
Utwórz klasę bazową 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);
}
}
}
}
}
}
Jest InstructorCoursesPageModel
to klasa bazowa, która będzie używana dla modeli stron Edytowanie i tworzenie. PopulateAssignedCourseData
odczytuje wszystkie Course
jednostki w celu wypełnienia AssignedCourseDataList
. Dla każdego kursu kod ustawia CourseID
tytuł , i czy instruktor jest przypisany do kursu. Zestaw hashset służy do wydajnego wyszukiwania.
Razor Ponieważ strona nie ma kolekcji jednostek Course, powiązanie modelu nie może automatycznie zaktualizować CourseAssignments
właściwości nawigacji. Zamiast używać powiązania modelu do aktualizowania CourseAssignments
właściwości nawigacji, należy to zrobić w nowej UpdateInstructorCourses
metodzie. W związku z tym należy wykluczyć CourseAssignments
właściwość z powiązania modelu. Nie wymaga to żadnej zmiany w kodzie wywołującym, TryUpdateModel
ponieważ używasz przeciążenia z zadeklarowanymi właściwościami i CourseAssignments
nie znajduje się na liście dołączania.
Jeśli nie zaznaczono żadnych pól wyboru, kod inicjuje UpdateInstructorCourses
CourseAssignments
właściwość nawigacji z pustą kolekcją i zwraca następujące elementy:
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}
Następnie kod przechodzi przez wszystkie kursy w bazie danych i sprawdza każdy kurs względem aktualnie przypisanych do instruktora w porównaniu z tymi, które zostały wybrane na stronie. Aby ułatwić wydajne wyszukiwanie, dwie ostatnie kolekcje są przechowywane w HashSet
obiektach.
Jeśli pole wyboru kursu zostało wybrane, ale kurs nie znajduje się we Instructor.CourseAssignments
właściwości nawigacji, kurs zostanie dodany do kolekcji we właściwości nawigacji.
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(
new CourseAssignment
{
InstructorID = instructorToUpdate.ID,
CourseID = course.CourseID
});
}
}
Jeśli pole wyboru kursu nie zostało zaznaczone, ale kurs znajduje się we Instructor.CourseAssignments
właściwości nawigacji, kurs zostanie usunięty z właściwości nawigacji.
else
{
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove
= instructorToUpdate
.CourseAssignments
.SingleOrDefault(i => i.CourseID == course.CourseID);
context.Remove(courseToRemove);
}
}
Obsługa lokalizacji biura
Inną relacją, którą strona edycji musi obsłużyć, jest relacja "jeden do zera" lub "jeden", którą jednostka Instruktor ma z jednostką OfficeAssignment
. Kod edycji instruktora musi obsługiwać następujące scenariusze:
- Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń
OfficeAssignment
jednostkę. - Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową
OfficeAssignment
jednostkę. - Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj
OfficeAssignment
jednostkę.
Aktualizowanie modelu strony Edycji instruktora
Zaktualizuj Pages/Instructors/Edit.cshtml.cs
za pomocą następującego kodu:
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();
}
}
}
Powyższy kod ma następujące działanie:
- Pobiera bieżącą
Instructor
jednostkę z bazy danych przy użyciu chętnegoOfficeAssignment
ładowania dla właściwości nawigacji ,CourseAssignment
iCourseAssignment.Course
. - Aktualizuje pobraną
Instructor
jednostkę przy użyciu wartości z powiązania modelu.TryUpdateModel
zapobiega przesłonięć. - Jeśli lokalizacja biura jest pusta, ustawia wartość
Instructor.OfficeAssignment
null. GdyInstructor.OfficeAssignment
ma wartość null, powiązany wiersz wOfficeAssignment
tabeli zostanie usunięty. - Wywołuje
PopulateAssignedCourseData
metodę wOnGetAsync
celu podania informacji dotyczących pól wyboru przy użyciuAssignedCourseData
klasy modelu widoku. - Wywołuje metodę
UpdateInstructorCourses
wOnPostAsync
celu zastosowania informacji z pól wyboru do edytowanej jednostki Instruktor. - Wywołania
PopulateAssignedCourseData
iUpdateInstructorCourses
w przypadkuOnPostAsync
TryUpdateModel
niepowodzenia. Ta metoda wywołuje metodę przywracania przypisanych danych kursów wprowadzonych na stronie, gdy jest ona odtwarzana ponownie z komunikatem o błędzie.
Aktualizowanie strony Edytowanie Razor instruktora
Zaktualizuj Pages/Instructors/Edit.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID
. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID
wartości tylko zaznaczonych pól wyboru.
Gdy pola wyboru są początkowo renderowane, wybrane są kursy przypisane do instruktora.
Uwaga: Podejście podjęte tutaj do edytowania danych kursu instruktora działa dobrze, gdy istnieje ograniczona liczba kursów. W przypadku kolekcji, które są znacznie większe, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.
Uruchom aplikację i przetestuj zaktualizowaną stronę Edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane na stronie Indeks.
Aktualizowanie strony Tworzenie instruktora
Zaktualizuj model Razor strony i stronę Tworzenie instruktora przy użyciu kodu podobnego do strony Edycja:
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");}
}
Przetestuj stronę Tworzenie instruktora.
Aktualizowanie strony Usuwanie instruktora
Zaktualizuj Pages/Instructors/Delete.cshtml.cs
za pomocą następującego kodu:
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");
}
}
}
Powyższy kod wprowadza następujące zmiany:
Używa chętnego
CourseAssignments
ładowania dla właściwości nawigacji.CourseAssignments
muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj usuwanie kaskadowe w bazie danych.Jeśli instruktor do usunięcia zostanie przypisany jako administrator jakichkolwiek działów, usuwa przydział instruktora z tych działów.
Uruchom aplikację i przetestuj stronę Usuń.
Następne kroki
W tym samouczku przedstawiono aktualizowanie powiązanych danych. Jeśli napotkasz problemy, nie możesz rozwiązać, pobierz lub wyświetl ukończoną aplikację. Pobierz instrukcje.
Na poniższych ilustracjach przedstawiono niektóre z ukończonych stron.
Sprawdź i przetestuj strony Tworzenie i edytowanie kursu. Utwórz nowy kurs. Dział jest wybierany przez jego klucz podstawowy (liczbę całkowitą), a nie jego nazwę. Edytuj nowy kurs. Po zakończeniu testowania usuń nowy kurs.
Tworzenie klasy bazowej do współużytkowania wspólnego kodu
Strony Courses/Create i Courses/Edit (Kursy/Edytuj) wymagają listy nazw działów. Utwórz klasę bazową Pages/Courses/DepartmentNamePageModel.cshtml.cs
dla stron Tworzenie i edytowanie:
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);
}
}
}
Powyższy kod tworzy obiekt , SelectList aby zawierać listę nazw działów. Jeśli selectedDepartment
zostanie określony, ten dział zostanie wybrany w elemecie SelectList
.
Klasy modelu tworzenia i edytowania stron będą pochodzić z klasy DepartmentNamePageModel
.
Dostosowywanie stron kursów
Po utworzeniu nowej jednostki kursu musi ona mieć relację z istniejącym działem. Aby dodać dział podczas tworzenia kursu, klasa podstawowa tworzenia i edytowania zawiera listę rozwijaną do wybierania działu. Lista rozwijana ustawia właściwość klucza obcego Course.DepartmentID
(FK). EF Core używa klucza Course.DepartmentID
FK do załadowania Department
właściwości nawigacji.
Zaktualizuj model tworzenia strony przy użyciu następującego kodu:
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();
}
}
}
Powyższy kod ma następujące działanie:
- Pochodzi z klasy
DepartmentNamePageModel
. - Używa
TryUpdateModelAsync
metody , aby zapobiec przesłonięć. - Zamienia wartość
ViewData["DepartmentID"]
naDepartmentNameSL
(z klasy bazowej).
ViewData["DepartmentID"]
element jest zastępowany silnie typizowane DepartmentNameSL
. Silnie typizowane modele są preferowane w przypadku słabych typów. Aby uzyskać więcej informacji, zobacz Weakly typed data (ViewData and ViewBag).
Aktualizowanie strony Tworzenie kursów
Zaktualizuj Pages/Courses/Create.cshtml
za pomocą następującego kodu:
@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");}
}
Powyższy znacznik wprowadza następujące zmiany:
- Zmienia podpis z Identyfikator działu na Dział.
- Zamienia wartość
"ViewBag.DepartmentID"
naDepartmentNameSL
(z klasy bazowej). - Dodaje opcję "Wybierz dział". Ta zmiana powoduje renderowanie "Wybierz dział" zamiast pierwszego działu.
- Dodaje komunikat weryfikacji, gdy dział nie jest wybrany.
Strona Razor używa pomocnika Wybierz tag:
<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>
Przetestuj stronę Tworzenie. Na stronie Tworzenie zostanie wyświetlona nazwa działu, a nie identyfikator działu.
Zaktualizuj stronę Edycja kursów.
Zastąp kod w pliku Pages/Courses/Edit.cshtml.cs
następującym kodem:
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();
}
}
}
Zmiany są podobne do tych wprowadzonych w modelu tworzenia strony. W poprzednim kodzie PopulateDepartmentsDropDownList
przekazuje identyfikator działu, który wybierze dział określony na liście rozwijanej.
Zaktualizuj Pages/Courses/Edit.cshtml
za pomocą następującego znacznika:
@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");}
}
Powyższy znacznik wprowadza następujące zmiany:
- Wyświetla identyfikator kursu. Ogólnie klucz podstawowy (PK) jednostki nie jest wyświetlany. Pakiety PKs są zwykle bez znaczenia dla użytkowników. W takim przypadku klucz PK jest numerem kursu.
- Zmienia podpis z Identyfikator działu na Dział.
- Zamienia wartość
"ViewBag.DepartmentID"
naDepartmentNameSL
(z klasy bazowej).
Strona zawiera ukryte pole (<input type="hidden">
) dla numeru kursu. Dodanie pomocnika asp-for="Course.CourseID"
tagów <label>
w programie nie eliminuje potrzeby pola ukrytego. <input type="hidden">
jest wymagany, aby numer kursu został uwzględniony w opublikowanych danych, gdy użytkownik kliknie przycisk Zapisz.
Przetestuj zaktualizowany kod. Tworzenie, edytowanie i usuwanie kursu.
Dodawanie funkcji AsNoTracking do modeli stron Szczegóły i Usuwanie
AsNoTracking może zwiększyć wydajność, gdy śledzenie nie jest wymagane. Dodaj AsNoTracking
do modelu strony Usuń i Szczegóły. Poniższy kod przedstawia zaktualizowany model strony Usuń:
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");
}
}
Zaktualizuj metodę OnGetAsync
Pages/Courses/Details.cshtml.cs
w pliku:
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();
}
Modyfikowanie stron Usuwanie i Szczegóły
Zaktualizuj stronę Usuń Razor przy użyciu następującego znacznika:
@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>
Wprowadź te same zmiany na stronie Szczegóły.
Testowanie stron kursu
Przetestuj tworzenie, edytowanie, szczegóły i usuwanie.
Aktualizowanie stron instruktora
Poniższe sekcje aktualizują strony instruktora.
Dodawanie lokalizacji biura
Podczas edytowania rekordu instruktora możesz zaktualizować przydział biura instruktora. Jednostka Instructor
ma relację jeden do zera lub jednego z jednostką OfficeAssignment
. Kod instruktora musi obsługiwać:
- Jeśli użytkownik wyczyści przypisanie pakietu Office, usuń
OfficeAssignment
jednostkę. - Jeśli użytkownik wprowadzi przypisanie pakietu Office i był pusty, utwórz nową
OfficeAssignment
jednostkę. - Jeśli użytkownik zmieni przypisanie pakietu Office, zaktualizuj
OfficeAssignment
jednostkę.
Zaktualizuj model strony edycji instruktorów przy użyciu następującego kodu:
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");
}
}
Powyższy kod ma następujące działanie:
- Pobiera bieżącą
Instructor
jednostkę z bazy danych przy użyciu chętnegoOfficeAssignment
ładowania dla właściwości nawigacji. - Aktualizuje pobraną
Instructor
jednostkę przy użyciu wartości z powiązania modelu.TryUpdateModel
zapobiega przesłonięć. - Jeśli lokalizacja biura jest pusta, ustawia wartość
Instructor.OfficeAssignment
null. GdyInstructor.OfficeAssignment
ma wartość null, powiązany wiersz wOfficeAssignment
tabeli zostanie usunięty.
Aktualizowanie strony edycji instruktora
Zaktualizuj Pages/Instructors/Edit.cshtml
za pomocą lokalizacji biura:
@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");}
}
Sprawdź, czy możesz zmienić lokalizację biura instruktorów.
Dodawanie przypisań kursu do strony Edycja instruktora
Instruktorzy mogą uczyć dowolną liczbę kursów. W tej sekcji dodasz możliwość zmiany przypisań kursu. Na poniższej ilustracji przedstawiono zaktualizowaną stronę edycji instruktora:
Course
i Instructor
ma relację wiele do wielu. Aby dodawać i usuwać relacje, należy dodawać i usuwać jednostki z CourseAssignments
zestawu jednostek sprzężenia.
Pola wyboru umożliwiają zmianę kursów, do których jest przypisany instruktor. Pole wyboru jest wyświetlane dla każdego kursu w bazie danych. Kursy przypisane przez instruktora są sprawdzane. Użytkownik może zaznaczyć lub wyczyścić pola wyboru, aby zmienić przydziały kursu. Jeśli liczba kursów była znacznie większa:
- Prawdopodobnie użyjesz innego interfejsu użytkownika do wyświetlania kursów.
- Metoda manipulowania jednostką sprzężenia w celu utworzenia lub usunięcia relacji nie uległa zmianie.
Dodawanie zajęć do obsługi tworzenia i edytowania stron instruktora
Utwórz Models/SchoolViewModels/AssignedCourseData.cs
za pomocą następującego kodu:
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
Klasa AssignedCourseData
zawiera dane służące do tworzenia pól wyboru dla przypisanych kursów przez instruktora.
Utwórz klasę bazową 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);
}
}
}
}
}
}
Jest InstructorCoursesPageModel
to klasa bazowa, która będzie używana dla modeli stron Edytowanie i tworzenie. PopulateAssignedCourseData
odczytuje wszystkie Course
jednostki w celu wypełnienia AssignedCourseDataList
. Dla każdego kursu kod ustawia CourseID
tytuł , i czy instruktor jest przypisany do kursu. Zestaw skrótów służy do tworzenia wydajnych odnośników.
Instruktorzy Edytuj model strony
Zaktualizuj model strony edycji instruktora przy użyciu następującego kodu:
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();
}
}
Powyższy kod obsługuje zmiany przypisania pakietu Office.
Zaktualizuj widok instruktora 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");}
}
Uwaga
Podczas wklejania kodu w programie Visual Studio podziały wierszy są zmieniane w sposób, który przerywa kod. Naciśnij Ctrl+Z raz, aby cofnąć automatyczne formatowanie. Ctrl+Z naprawia podziały wierszy tak, aby wyglądały jak to, co widzisz tutaj. Wcięcie nie musi być idealne, ale @:</tr><tr>
wiersze , @:<td>
, @:</td>
i @:</tr>
muszą znajdować się w jednym wierszu, jak pokazano. Po wybraniu bloku nowego kodu naciśnij Tab trzy razy, aby ustawić nowy kod przy użyciu istniejącego kodu. Zagłosuj lub przejrzyj stan tej usterki za pomocą tego linku.
Powyższy kod tworzy tabelę HTML zawierającą trzy kolumny. Każda kolumna ma pole wyboru i podpis zawierający numer kursu i tytuł. Wszystkie pola wyboru mają taką samą nazwę ("selectedCourses"). Użycie tej samej nazwy informuje powiązanie modelu, aby traktować je jako grupę. Atrybut wartości każdego pola wyboru jest ustawiony na CourseID
. Po opublikowaniu strony powiązanie modelu przekazuje tablicę składającą się z CourseID
wartości tylko zaznaczonych pól wyboru.
Gdy pola wyboru są początkowo renderowane, kursy przypisane do instruktora mają zaznaczone atrybuty.
Uruchom aplikację i przetestuj zaktualizowaną stronę edycji instruktorów. Zmień niektóre przydziały kursu. Zmiany są odzwierciedlane na stronie Indeks.
Uwaga: Podejście podjęte tutaj do edytowania danych kursu instruktora działa dobrze, gdy istnieje ograniczona liczba kursów. W przypadku kolekcji, które są znacznie większe, inny interfejs użytkownika i inna metoda aktualizacji byłyby bardziej opłacalne i wydajne.
Aktualizowanie strony Tworzenie instruktorów
Zaktualizuj model strony Tworzenie instruktora przy użyciu następującego kodu:
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();
}
}
}
Powyższy kod jest podobny do Pages/Instructors/Edit.cshtml.cs
kodu.
Zaktualizuj stronę Tworzenie Razor instruktora przy użyciu następującego znacznika:
@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");}
}
Przetestuj stronę Tworzenie instruktora.
Aktualizowanie strony Usuwanie
Zaktualizuj model strony Usuń przy użyciu następującego kodu:
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");
}
}
}
Powyższy kod wprowadza następujące zmiany:
Używa chętnego
CourseAssignments
ładowania dla właściwości nawigacji.CourseAssignments
muszą być dołączone lub nie są usuwane po usunięciu instruktora. Aby uniknąć konieczności ich odczytywania, skonfiguruj usuwanie kaskadowe w bazie danych.Jeśli instruktor do usunięcia zostanie przypisany jako administrator jakichkolwiek działów, usuwa przydział instruktora z tych działów.