Esercitazione: Aggiornare i dati correlati - ASP.NET MVC con EF Core
Nell'esercitazione precedente sono stati visualizzati dati correlati. In questa esercitazione i dati correlati verranno aggiornati tramite l'aggiornamento di campi di chiave esterna e proprietà di navigazione.
Le figure seguenti illustrano alcune delle pagine che verranno usate.
In questa esercitazione:
- Personalizzare le pagine dei corsi
- Aggiungere la pagina Edit per gli insegnanti
- Aggiungere corsi alla pagina Edit
- Aggiornare la pagina Delete
- Aggiungere posizione dell'ufficio e corsi alla pagina Create
Prerequisiti
Personalizzare le pagine dei corsi
Quando viene creata una nuova Course
entità, deve avere una relazione con un reparto esistente. Per semplificare il raggiungimento di questo obiettivo, il codice con scaffolding include i metodi del controller e le visualizzazioni di creazione e modifica includono un elenco a discesa per la selezione del dipartimento. L'elenco a discesa imposta la Course.DepartmentID
proprietà di chiave esterna ed è tutto ciò che è necessario per caricare la Department
proprietà di navigazione con l'entità appropriata Department
. Verrà usato il codice con scaffolding, che però verrà modificato leggermente per aggiungere la gestione degli errori e l'ordinamento dell'elenco a discesa.
In CoursesController.cs
eliminare i quattro metodi Create e Edit e sostituirli con il codice seguente:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var courseToUpdate = await _context.Courses
.FirstOrDefaultAsync(c => c.CourseID == id);
if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}
Dopo il metodo Edit
HttpPost, creare un nuovo metodo che carichi le informazioni di dipartimento per l'elenco a discesa.
private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(), "DepartmentID", "Name", selectedDepartment);
}
Il metodo PopulateDepartmentsDropDownList
ottiene un elenco di tutti i dipartimenti ordinato per nome, crea una raccolta SelectList
per un elenco a discesa e passa tale raccolta alla visualizzazione in ViewBag
. Il metodo accetta il parametro facoltativo selectedDepartment
, che consente al codice chiamante di specificare l'elemento che deve essere selezionato quando viene eseguito il rendering dell'elenco a discesa. La visualizzazione passerà il nome "DepartmentID" all'helper tag <select>
, che quindi saprà di dover cercare nell'oggetto ViewBag
una raccolta SelectList
denominata "DepartmentID".
Il metodo Create
HttpGet chiama il metodo PopulateDepartmentsDropDownList
senza impostare l'elemento selezionato, perché per un nuovo corso il dipartimento non è ancora stabilito:
public IActionResult Create()
{
PopulateDepartmentsDropDownList();
return View();
}
Il metodo Edit
HttpGet imposta l'elemento selezionato, in base all'ID del dipartimento già assegnato al corso in fase di modifica:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}
I metodi HttpPost sia per Create
che per Edit
includono anche codice che imposta l'elemento selezionato quando la pagina viene visualizzata di nuovo dopo un errore. Ciò garantisce che, quando la pagina viene visualizzata di nuovo per mostrare il messaggio di errore, il dipartimento selezionato rimane selezionato.
Aggiungere .AsNoTracking ai metodi Details e Delete
Per ottimizzare le prestazioni delle pagine dei dettagli e di eliminazione del corso, aggiungere chiamate a AsNoTracking
nei metodi Details
e Delete
HttpGet.
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
return View(course);
}
Modificare le visualizzazioni dei corsi
In Views/Courses/Create.cshtml
aggiungere un'opzione "Seleziona reparto" all'elenco a discesa Reparto, modificare il didascalia da DepartmentID a Department e aggiungere un messaggio di convalida.
<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />
</div>
In Views/Courses/Edit.cshtml
apportare la stessa modifica per il campo Reparto appena fatto in Create.cshtml
.
Views/Courses/Edit.cshtml
In aggiungere anche un campo numero corso prima del campo Titolo. Poiché il numero di corso è la chiave primaria, viene visualizzato, ma non può essere modificato.
<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>
Per il numero di corso è già presente un campo nascosto (<input type="hidden">
) nella visualizzazione di modifica. L'aggiunta di un helper tag <label>
non elimina la necessità del campo nascosto, poiché senza di questo il numero di corso non viene incluso nei dati inviati quando l'utente fa clic su Save (Salva) nella pagina Edit (Modifica).
In Views/Courses/Delete.cshtml
aggiungere un campo numero corso nella parte superiore e modificare l'ID reparto in nome del reparto.
@model ContosoUniversity.Models.Course
@{
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.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>
<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
In Views/Courses/Details.cshtml
apportare la stessa modifica appena apportata per Delete.cshtml
.
Testare le pagine del corso
Eseguire l'app, selezionare la scheda Courses (Corsi), fare clic su Create New (Crea nuovo) e immettere i dati per un nuovo corso:
Fai clic su Crea. La pagina di indice dei corsi viene visualizzata con il nuovo corso aggiunto all'elenco. Il nome del dipartimento nell'elenco della pagina di indice deriva dalla proprietà di navigazione, che mostra che la relazione è stata stabilita correttamente.
Fare clic su Edit (Modifica) per un corso nella pagina di indice dei corsi.
Modificare i dati nella pagina e fare clic su Save (Salva). La pagina di indice dei corsi verrà visualizzata con i dati del corso aggiornati.
Aggiungere la pagina Edit per gli insegnanti
Quando si modifica il record di un insegnante, è necessario essere in grado di aggiornare l'assegnazione dell'ufficio. L'entità Instructor
ha una relazione uno-a-zero-o-uno con l'entità OfficeAssignment
, il che significa che il codice deve gestire le situazioni seguenti:
Se l'utente cancella l'assegnazione dell'ufficio e originariamente ha un valore, eliminare l'entità
OfficeAssignment
.Se l'utente immette un valore di assegnazione dell'ufficio e originariamente era vuoto, creare una nuova
OfficeAssignment
entità.Se l'utente modifica il valore di un'assegnazione di ufficio, modificare il valore in un'entità esistente
OfficeAssignment
.
Aggiornare il controller Instructors
In InstructorsController.cs
modificare il codice nel metodo HttpGet Edit
in modo che carichi la proprietà di navigazione dell'entità OfficeAssignment
Instructor e chiami AsNoTracking
:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}
Sostituire il metodo Edit
HttpPost con il codice seguente per gestire gli aggiornamenti delle assegnazioni di ufficio:
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}
Il codice esegue le seguenti attività:
Modifica il nome del metodo in
EditPost
perché ora la firma è la stessa del metodoEdit
HttpGet (l'attributoActionName
specifica che l'URL/Edit/
viene ancora usato).Ottiene l'entità
Instructor
corrente dal database tramite il caricamento eager per la proprietà di navigazioneOfficeAssignment
. Ciò corrisponde a quanto effettuato nel metodoEdit
HttpGet.Aggiorna l'entità
Instructor
recuperata con valori dallo strumento di associazione di modelli. L'overloadTryUpdateModel
consente di dichiarare le proprietà da includere. In questo modo è possibile evitare l'overposting, come illustrato nella seconda esercitazione.if (await TryUpdateModelAsync<Instructor>( instructorToUpdate, "", i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
Se la posizione dell'ufficio è vuota, imposta la
Instructor.OfficeAssignment
proprietà su Null in modo che la riga correlata nellaOfficeAssignment
tabella venga eliminata.if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location)) { instructorToUpdate.OfficeAssignment = null; }
Salva le modifiche nel database.
Aggiornare la visualizzazione di modifica dell'insegnante
In Views/Instructors/Edit.cshtml
aggiungere un nuovo campo per modificare la posizione dell'ufficio, alla fine prima del pulsante Salva :
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
Eseguire l'app, selezionare la scheda Instructors (Insegnanti) e quindi fare clic su Edit (Modifica) per un insegnante. Modificare Office Location (Posizione ufficio) e fare clic su Save (Salva).
Aggiungere corsi alla pagina Edit
Gli insegnanti possono tenere un numero qualsiasi di corsi. A questo punto si migliorerà la pagina Modifica insegnante aggiungendo la possibilità di modificare le assegnazioni dei corsi usando un gruppo di caselle di controllo, come illustrato nella schermata seguente:
La relazione tra le Course
entità e Instructor
è molti-a-molti. Per aggiungere e rimuovere relazioni, aggiungere e rimuovere entità da e verso il CourseAssignments
set di entità di join.
L'interfaccia utente che consente di modificare i corsi a cui viene assegnato un insegnante è un gruppo di caselle di controllo. Viene visualizzata una casella di controllo per ogni corso del database e quelle a cui è attualmente assegnato l'insegnante sono selezionate. L'utente può selezionare o deselezionare le caselle di controllo per modificare le assegnazioni dei corsi. Se il numero di corsi fosse molto superiore, sarebbe probabilmente consigliabile usare un altro metodo di presentazione dei dati nella visualizzazione, ma si userebbe lo stesso metodo di modifica di un'entità di join per creare o eliminare relazioni.
Aggiornare il controller Instructors
Per fornire dati alla visualizzazione per l'elenco di caselle di controllo, si userà una classe del modello di visualizzazione.
Creare AssignedCourseData.cs
nella cartella SchoolViewModels e sostituire il codice esistente con il codice seguente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
In InstructorsController.cs
sostituire il metodo HttpGet Edit
con il codice seguente. Le modifiche sono evidenziate.
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var instructor = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = _context.Courses;
var instructorCourses = new HashSet<int>(instructor.CourseAssignments.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewData["Courses"] = viewModel;
}
Il codice aggiunge il caricamento eager per la Courses
proprietà di navigazione e chiama il nuovo PopulateAssignedCourseData
metodo per fornire informazioni per la matrice di caselle di controllo usando la classe del AssignedCourseData
modello di visualizzazione.
Il codice nel PopulateAssignedCourseData
metodo legge tutte le Course
entità per caricare un elenco di corsi usando la classe del modello di visualizzazione. Per ogni corso, il codice verifica se è presente nella proprietà di navigazione Courses
dell'insegnante. Per creare un ricerca efficiente per la verifica dell'assegnazione di un corso all'insegnante, i corsi assegnati all'insegnante vengono inseriti in una raccolta HashSet
. La proprietà Assigned
è impostata su true per i corsi assegnati all'insegnante. La vista userà questa proprietà per determinare quali caselle di controllo devono essere visualizzate come selezionate. L'elenco, infine, viene passato alla visualizzazione in ViewData
.
Aggiungere quindi il codice che viene eseguito quando l'utente fa clic su Save (Salva). Sostituire il metodo EditPost
con il codice seguente e aggiungere un nuovo metodo che aggiorni la proprietà di navigazione Courses
dell'entità Instructor.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}
var instructorToUpdate = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.FirstOrDefaultAsync(m => m.ID == id);
if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}
private void UpdateInstructorCourses(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.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
La firma del metodo è ora diversa dal metodo Edit
HttpGet, quindi il nome del metodo cambia di nuovo da EditPost
a Edit
.
Poiché la visualizzazione non ha una raccolta di entità Course, lo strumento di associazione di modelli non può aggiornare automaticamente la proprietà di navigazione CourseAssignments
. Anziché usare lo strumento di associazione di modelli per aggiornare la proprietà di navigazione CourseAssignments
, questa operazione viene eseguita nel nuovo metodo UpdateInstructorCourses
. Pertanto, è necessario escludere la proprietà dall'associazione CourseAssignments
di modelli. Ciò non richiede alcuna modifica al codice che chiama TryUpdateModel
perché si usa l'overload che richiede l'approvazione esplicita e CourseAssignments
non è incluso nell'elenco di inclusioni.
Se non sono state selezionate caselle di controllo, il codice in UpdateInstructorCourses
inizializza la CourseAssignments
proprietà di navigazione con una raccolta vuota e restituisce:
private void UpdateInstructorCourses(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.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Il codice quindi esegue il ciclo di tutti i corsi nel database e controlla ogni corso a fronte di quelli assegnati all'insegnante rispetto a quelli selezionati nella visualizzazione. Per facilitare l'esecuzione di ricerche efficienti, le ultime due raccolte sono archiviate all'interno di oggetti HashSet
.
Se la casella di controllo per un corso è stata selezionata, ma il corso non si trova nella Instructor.CourseAssignments
proprietà di navigazione, il corso viene aggiunto alla raccolta nella proprietà di navigazione.
private void UpdateInstructorCourses(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.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Se la casella di controllo per un corso non è stata selezionata, ma il corso si trova nella Instructor.CourseAssignments
proprietà di navigazione, il corso viene rimosso dalla proprietà di navigazione.
private void UpdateInstructorCourses(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.FirstOrDefault(i => i.CourseID == course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
Aggiornare le visualizzazioni dell'insegnante
In Views/Instructors/Edit.cshtml
aggiungere un campo Courses con una matrice di caselle di controllo aggiungendo il codice seguente immediatamente dopo gli div
elementi per il campo di Office e prima dell'elemento div
per il pulsante Salva .
Nota
Quando si incolla il codice in Visual Studio, è possibile che le interruzioni di riga vengano modificate in un modo che danneggia il codice. Se il codice ha un aspetto diverso dopo aver incollato, premere CTRL+Z una volta per annullare la formattazione automatica. Ciò corregge le interruzioni di riga, che vengono visualizzate come illustrato qui. Il rientro non deve necessariamente essere perfetto, ma le righe @:</tr><tr>
, @:<td>
, @:</td>
e @:</tr>
devono trovarsi in una sola riga, come illustrato. In caso contrario, viene visualizzato un errore di runtime. Dopo aver selezionato il blocco di nuovo codice, premere Tab tre volte per allineare il nuovo codice con il codice esistente. Questo problema è stato risolto in Visual Studio 2019.
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
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>
Questo codice crea una tabella HTML con tre colonne. In ogni colonna è presente una casella di controllo seguita da un didascalia costituito dal numero e dal titolo del corso. Le caselle di controllo hanno tutti lo stesso nome ("selectedCourses"), che informa il gestore di associazione di modelli che devono essere considerati come un gruppo. L'attributo value di ogni casella di controllo è impostato sul valore di CourseID
. Quando viene pubblicata la pagina, il gestore di associazione di modelli passa una matrice al controller costituito dai valori solo per le caselle di CourseID
controllo selezionate.
Quando viene inizialmente eseguito il rendering delle caselle di controllo, quelle destinate ai corsi assegnati all'insegnante hanno attributi selezionati, che li seleziona (visualizzali selezionati).
Eseguire l'app, selezionare la scheda Instructors (Insegnanti) e fare clic su Edit (Modifica) per un insegnante per visualizzare la pagina Edit (Modifica).
Modificare alcune assegnazioni di corsi e fare clic su Save (Salva). Le modifiche effettuate si riflettono nella pagina di indice.
Nota
L'approccio qui adottato per la modifica dei dati dei corsi degli insegnanti funziona bene quando è presente un numero limitato di corsi. Per raccolte molto più grandi, sarebbero necessari un'interfaccia utente diversa e un altro metodo di aggiornamento.
Aggiornare la pagina Delete
In InstructorsController.cs
eliminare il DeleteConfirmed
metodo e inserire il codice seguente al suo posto.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(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 RedirectToAction(nameof(Index));
}
Questo codice apporta le modifiche seguenti:
Esegue il caricamento eager per la proprietà di navigazione
CourseAssignments
. È necessario includere questa proprietà o EF non sarà a conoscenza delle entitàCourseAssignment
correlate e non le eliminerà. Per evitare la necessità di leggerle qui, è possibile configurare l'eliminazione a catena nel database.Se l'insegnante da eliminare è assegnato come responsabile di un dipartimento, tale assegnazione viene rimossa dal dipartimento.
Aggiungere posizione dell'ufficio e corsi alla pagina Create
In InstructorsController.cs
eliminare i metodi HttpGet e HttpPost Create
e quindi aggiungere il codice seguente al loro posto:
public IActionResult Create()
{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}
// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID = instructor.ID, CourseID = int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
Questo codice è simile a quello dei metodi Edit
, ad eccezione del fatto che inizialmente non è selezionato alcun corso. Il metodo Create
HttpGet chiama il metodo PopulateAssignedCourseData
non perché possono essere presenti corsi selezionati, ma per fornire una raccolta vuota per il ciclo foreach
nella visualizzazioni (in caso contrario il codice di visualizzazione genera un'eccezione di riferimento Null).
Il metodo Create
HttpPost aggiunge i corsi selezionati alla proprietà di navigazione CourseAssignments
prima di controllare la presenza di errori di convalida e aggiungere il nuovo insegnante al database. I corsi vengono aggiunti anche in caso di errori di modello. Quindi se si verificano errori di questo tipo (ad esempio se l'utente digita una data non valida) e la pagina viene visualizzata di nuovo con un messaggio di errore, tutte le selezioni relative ai corsi effettuate vengono ripristinate automaticamente.
Si noti che, perché sia possibile aggiungere corsi alla proprietà di navigazione CourseAssignments
, è necessario inizializzare la proprietà come raccolta vuota:
instructor.CourseAssignments = new List<CourseAssignment>();
In alternativa a questa operazione nel codice del controller, è possibile farlo nel Instructor
modello modificando il getter della proprietà per creare automaticamente la raccolta se non esiste, come illustrato nell'esempio seguente:
private ICollection<CourseAssignment> _courseAssignments;
public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}
Se si modifica la proprietà CourseAssignments
in questo modo, è possibile rimuovere il codice di inizializzazione esplicita della proprietà nel controller.
In Views/Instructor/Create.cshtml
aggiungere una casella di testo office location e caselle di controllo per i corsi prima del pulsante Invia. Come nel caso della pagina Edit (Modifica), correggere la formattazione se Visual Studio riformatta il codice quando lo si incolla.
<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label"></label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;
List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses = ViewBag.Courses;
foreach (var course in courses)
{
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>
Eseguire il test eseguendo l'app e creando un insegnante.
Gestione delle transazioni
Come spiegato nell' esercitazione su CRUD, per impostazione predefinita Entity Framework implementa in modo implicito le transazioni. Per gli scenari in cui è necessario un maggior controllo, ad esempio per includere le operazioni eseguite all'esterno di Entity Framework in una transazione, vedere Transazioni.
Ottenere il codice
Scaricare o visualizzare l'applicazione completata.
Passaggi successivi
In questa esercitazione:
- Personalizzare le pagine dei corsi
- Aggiungere la pagina Edit per gli insegnanti
- Aggiungere corsi alla pagina Edit
- Aggiornare la pagina Delete
- Aggiungere posizione dell'ufficio e corsi alla pagina Create
Passare all'esercitazione successiva per informazioni su come gestire i conflitti di concorrenza.