Część 2, Razor strony z EF Core ASP.NET Core — CRUD
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz .NET i .NET Core Support Policy (Zasady obsługi platformy .NET Core). Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.
Przez Tom Dykstra, Jeremy Likness i Jon P Smith
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 kod CRUD (tworzenie, odczytywanie, aktualizowanie i usuwanie) jest przeglądany i dostosowywany.
Brak repozytorium
Niektórzy deweloperzy używają warstwy usługi lub wzorca repozytorium, aby utworzyć warstwę abstrakcji między interfejsem użytkownika (Razor stron) i warstwą dostępu do danych. Ten samouczek tego nie robi. Aby zminimalizować złożoność i skoncentrować się na EF Coresamouczku, EF Core kod jest dodawany bezpośrednio do klas modelu strony.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do Details
strony.
Rejestracje odczytu
Aby wyświetlić dane rejestracji ucznia na stronie, należy odczytać dane rejestracji. Kod szkieletowy w pliku Pages/Students/Details.cshtml.cs
odczytuje tylko Student
dane bez Enrollment
danych:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Zastąp metodę OnGetAsync
poniższym kodem, aby odczytać dane rejestracji dla wybranego ucznia. Zmiany są wyróżnione.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Include Metody i ThenInclude powodują załadowanie Student.Enrollments
właściwości nawigacji kontekstu i w ramach każdej rejestracji Enrollment.Course
właściwości nawigacji. Te metody zostały szczegółowo zbadane w samouczku Odczyt powiązanych danych .
Metoda AsNoTracking poprawia wydajność w scenariuszach, w których zwracane jednostki nie są aktualizowane w bieżącym kontekście. AsNoTracking
zostanie omówiony w dalszej części tego samouczka.
Wyświetlanie rejestracji
Zastąp kod poniższym Pages/Students/Details.cshtml
kodem, aby wyświetlić listę rejestracji. Zmiany są wyróżnione.
@page
@model ContosoUniversity.Pages.Students.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
Poprzedni kod przechodzi przez jednostki we Enrollments
właściwości nawigacji. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z Course
jednostki przechowywanej Course
we właściwości nawigacji jednostki Enrollments.
Uruchom aplikację, wybierz kartę Uczniowie , a następnie kliknij link Szczegóły dla ucznia. Zostanie wyświetlona lista kursów i ocen dla wybranego ucznia.
Sposoby odczytywania jednej jednostki
Wygenerowany kod używa metody FirstOrDefaultAsync do odczytywania jednej jednostki. Ta metoda zwraca wartość null, jeśli nic nie zostanie znalezione; W przeciwnym razie zwraca pierwszy wiersz znaleziony, który spełnia kryteria filtru zapytania. FirstOrDefaultAsync
ogólnie jest lepszym wyborem niż następujące alternatywy:
- SingleOrDefaultAsync — zgłasza wyjątek, jeśli istnieje więcej niż jedna jednostka, która spełnia filtr zapytania. Aby określić, czy zapytanie może zwrócić więcej niż jeden wiersz,
SingleOrDefaultAsync
spróbuje pobrać wiele wierszy. Ta dodatkowa praca jest niepotrzebna, jeśli zapytanie może zwrócić tylko jedną jednostkę, tak jak podczas wyszukiwania w unikatowym kluczu. - FindAsync — znajduje jednostkę z kluczem podstawowym (PK). Jeśli jednostka z kluczem PK jest śledzona przez kontekst, jest zwracana bez żądania do bazy danych. Ta metoda jest zoptymalizowana pod kątem wyszukiwania pojedynczej jednostki, ale nie można wywołać
Include
metody za pomocą poleceniaFindAsync
. Dlatego jeśli potrzebne są powiązane dane,FirstOrDefaultAsync
jest lepszym wyborem.
Kierowanie danych a ciąg zapytania
Adres URL strony Szczegóły to https://localhost:<port>/Students/Details?id=1
. Wartość klucza podstawowego jednostki znajduje się w ciągu zapytania. Niektórzy deweloperzy wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Aktualizowanie strony Tworzenie
Kod szkieletu OnPostAsync
strony Tworzenie jest podatny na przesłanie. Zastąp metodę OnPostAsync
w pliku Pages/Students/Create.cshtml.cs
poniższym kodem.
public async Task<IActionResult> OnPostAsync()
{
var emptyStudent = new Student();
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
TryUpdateModelAsync
Powyższy kod tworzy obiekt Student, a następnie używa pól formularza opublikowanego do aktualizowania właściwości obiektu Student. Metoda TryUpdateModelAsync:
- Używa opublikowanych PageContext wartości formularza z właściwości w obiekcie PageModel.
- Aktualizuje tylko właściwości wymienione (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Szuka pól formularza z prefiksem "student". Na przykład
Student.FirstMidName
. Nie uwzględnia wielkości liter. - Używa systemu powiązania modelu do konwertowania wartości formularzy z ciągów na typy w
Student
modelu. Na przykładEnrollmentDate
element jest konwertowany naDateTime
.
Uruchom aplikację i utwórz jednostkę ucznia, aby przetestować stronę Tworzenie.
Zastępowanie
Używanie TryUpdateModel
funkcji do aktualizowania pól za pomocą wartości opublikowanych jest najlepszym rozwiązaniem w zakresie zabezpieczeń, ponieważ zapobiega przesłonięciom. Załóżmy na przykład, że jednostka Student zawiera Secret
właściwość, którą ta strona sieci Web nie powinna aktualizować ani dodawać:
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}
Nawet jeśli aplikacja nie ma Secret
pola na stronie tworzenia lub aktualizowania Razor , haker może ustawić Secret
wartość przez przesłanie. Haker może użyć narzędzia takiego jak Fiddler lub napisać kod JavaScript, aby opublikować Secret
wartość formularza. Oryginalny kod nie ogranicza pól używanych przez powiązanie modelu podczas tworzenia wystąpienia ucznia.
Dowolna wartość określona przez hakera Secret
dla pola formularza jest aktualizowana w bazie danych. Na poniższej ilustracji przedstawiono narzędzie Fiddler dodające Secret
pole z wartością "OverPost" do opublikowanych wartości formularza.
Wartość "OverPost" została pomyślnie dodana do Secret
właściwości wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości na stronie Tworzenie.
Wyświetlanie modelu
Wyświetlanie modeli zapewnia alternatywny sposób zapobiegania przesłonięć.
Model aplikacji jest często nazywany modelem domeny. Model domeny zwykle zawiera wszystkie właściwości wymagane przez odpowiednią jednostkę w bazie danych. Model widoku zawiera tylko właściwości wymagane dla strony interfejsu użytkownika, na przykład strony Tworzenie.
Oprócz modelu wyświetlania niektóre aplikacje używają modelu powiązania lub modelu wejściowego do przekazywania danych między Razor klasą modelu stron a przeglądarką.
Rozważmy następujący StudentVM
model widoku:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Poniższy kod używa modelu wyświetlania StudentVM
do utworzenia nowego ucznia:
[BindProperty]
public StudentVM StudentVM { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Metoda SetValues ustawia wartości tego obiektu, odczytując wartości z innego PropertyValues obiektu. SetValues
używa dopasowania nazwy właściwości. Typ modelu widoku:
- Nie musi być powiązany z typem modelu.
- Musi mieć właściwości, które są zgodne.
Użycie StudentVM
polecenia wymaga użycia StudentVM
strony Create, a nie Student
:
@page
@model CreateVMModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Student</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="StudentVM.LastName" class="control-label"></label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="control-label"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control" />
<span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
<input asp-for="StudentVM.EnrollmentDate" class="form-control" />
<span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
</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 Edytuj
W Pages/Students/Edit.cshtml.cs
pliku zastąp OnGetAsync
metody i OnPostAsync
następującym kodem.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FindAsync(id);
if (Student == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var studentToUpdate = await _context.Students.FindAsync(id);
if (studentToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
Zmiany kodu są podobne do strony Tworzenie z kilkoma wyjątkami:
FirstOrDefaultAsync
element został zastąpiony ciągiem FindAsync. Jeśli nie musisz uwzględniać powiązanych danych,FindAsync
jest wydajniejszy.OnPostAsync
id
ma parametr .- Bieżący student jest pobierany z bazy danych zamiast tworzyć pustego ucznia.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany jednostek
Kontekst bazy danych śledzi, czy jednostki w pamięci są zsynchronizowane z odpowiednimi wierszami w bazie danych. Te informacje śledzenia określają, co się stanie po wywołaniu polecenia SaveChangesAsync . Na przykład po przekazaniu nowej jednostki do AddAsync metody stan tej jednostki ma wartość Added. Po SaveChangesAsync
wywołaniu kontekst bazy danych wystawia polecenie SQL INSERT
.
Jednostka może znajdować się w jednym z następujących stanów:
Added
: jednostka nie istnieje jeszcze w bazie danych. MetodaSaveChanges
wystawia instrukcjęINSERT
.Unchanged
: nie trzeba zapisywać żadnych zmian w tej jednostce. Jednostka ma ten stan, gdy jest odczytywany z bazy danych.Modified
: Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
wystawia instrukcjęUPDATE
.Deleted
: Jednostka została oznaczona do usunięcia. MetodaSaveChanges
wystawia instrukcjęDELETE
.Detached
: jednostka nie jest śledzona przez kontekst bazy danych.
W aplikacji klasycznej zmiany stanu są zwykle ustawiane automatycznie. Jednostka jest odczytywana, wprowadzana jest zmiana, a stan jednostki jest automatycznie zmieniany na Modified
. Wywołanie SaveChanges
generuje instrukcję SQL UPDATE
, która aktualizuje tylko zmienione właściwości.
W aplikacji internetowej element odczytujący DbContext
jednostkę i wyświetlający dane są usuwane po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
zostanie wykonane nowe żądanie internetowe i z nowym wystąpieniem .DbContext
Ponowne odczytanie jednostki w tym nowym kontekście symuluje przetwarzanie pulpitu.
Aktualizowanie strony Usuwanie
W tej sekcji jest implementowany niestandardowy komunikat o błędzie, gdy wywołanie zakończy się SaveChanges
niepowodzeniem.
Zastąp kod w pliku Pages/Students/Delete.cshtml.cs
następującym kodem:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<DeleteModel> _logger;
public DeleteModel(ContosoUniversity.Data.SchoolContext context,
ILogger<DeleteModel> logger)
{
_context = context;
_logger = logger;
}
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return NotFound();
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, ErrorMessage);
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}
Powyższy kod ma następujące działanie:
- Dodaje rejestrowanie.
- Dodaje opcjonalny parametr
saveChangesError
doOnGetAsync
podpisu metody.saveChangesError
wskazuje, czy metoda została wywołana po niepowodzeniu usunięcia obiektu ucznia.
Operacja usuwania może zakończyć się niepowodzeniem z powodu przejściowych problemów z siecią. Przejściowe błędy sieci są bardziej prawdopodobne, gdy baza danych znajduje się w chmurze. Parametr saveChangesError
jest wyświetlany false
, gdy strona OnGetAsync
Usuń jest wywoływana z interfejsu użytkownika. Gdy OnGetAsync
jest wywoływana przez OnPostAsync
element , ponieważ operacja usuwania nie powiodła się, saveChangesError
parametr ma true
wartość .
Metoda OnPostAsync
pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na Deleted
. Po SaveChanges
wywołaniu jest generowane polecenie SQL DELETE
. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda Delete pages
OnGetAsync
jest wywoływana za pomocąsaveChangesError=true
metody .
Dodaj komunikat o błędzie do :Pages/Students/Delete.cshtml
@page
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Student.ID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
Uruchom aplikację i usuń ucznia, aby przetestować stronę Usuń.
Następne kroki
W tym samouczku kod CRUD (tworzenie, odczytywanie, aktualizowanie i usuwanie) jest przeglądany i dostosowywany.
Brak repozytorium
Niektórzy deweloperzy używają warstwy usługi lub wzorca repozytorium, aby utworzyć warstwę abstrakcji między interfejsem użytkownika (Razor stron) i warstwą dostępu do danych. Ten samouczek tego nie robi. Aby zminimalizować złożoność i skoncentrować się na EF Coresamouczku, EF Core kod jest dodawany bezpośrednio do klas modelu strony.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do Details
strony.
Rejestracje odczytu
Aby wyświetlić dane rejestracji ucznia na stronie, należy odczytać dane rejestracji. Kod szkieletowy w pliku Pages/Students/Details.cshtml.cs
odczytuje tylko Student
dane bez Enrollment
danych:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Zastąp metodę OnGetAsync
poniższym kodem, aby odczytać dane rejestracji dla wybranego ucznia. Zmiany są wyróżnione.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Include Metody i ThenInclude powodują załadowanie Student.Enrollments
właściwości nawigacji kontekstu i w ramach każdej rejestracji Enrollment.Course
właściwości nawigacji. Te metody zostały szczegółowo zbadane w samouczku Odczyt powiązanych danych .
Metoda AsNoTracking poprawia wydajność w scenariuszach, w których zwracane jednostki nie są aktualizowane w bieżącym kontekście. AsNoTracking
zostanie omówiony w dalszej części tego samouczka.
Wyświetlanie rejestracji
Zastąp kod poniższym Pages/Students/Details.cshtml
kodem, aby wyświetlić listę rejestracji. Zmiany są wyróżnione.
@page
@model ContosoUniversity.Pages.Students.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
Poprzedni kod przechodzi przez jednostki we Enrollments
właściwości nawigacji. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z Course
jednostki przechowywanej Course
we właściwości nawigacji jednostki Enrollments.
Uruchom aplikację, wybierz kartę Uczniowie , a następnie kliknij link Szczegóły dla ucznia. Zostanie wyświetlona lista kursów i ocen dla wybranego ucznia.
Sposoby odczytywania jednej jednostki
Wygenerowany kod używa metody FirstOrDefaultAsync do odczytywania jednej jednostki. Ta metoda zwraca wartość null, jeśli nic nie zostanie znalezione; W przeciwnym razie zwraca pierwszy wiersz znaleziony, który spełnia kryteria filtru zapytania. FirstOrDefaultAsync
ogólnie jest lepszym wyborem niż następujące alternatywy:
- SingleOrDefaultAsync — zgłasza wyjątek, jeśli istnieje więcej niż jedna jednostka, która spełnia filtr zapytania. Aby określić, czy zapytanie może zwrócić więcej niż jeden wiersz,
SingleOrDefaultAsync
spróbuje pobrać wiele wierszy. Ta dodatkowa praca jest niepotrzebna, jeśli zapytanie może zwrócić tylko jedną jednostkę, tak jak podczas wyszukiwania w unikatowym kluczu. - FindAsync — znajduje jednostkę z kluczem podstawowym (PK). Jeśli jednostka z kluczem PK jest śledzona przez kontekst, jest zwracana bez żądania do bazy danych. Ta metoda jest zoptymalizowana pod kątem wyszukiwania pojedynczej jednostki, ale nie można wywołać
Include
metody za pomocą poleceniaFindAsync
. Dlatego jeśli potrzebne są powiązane dane,FirstOrDefaultAsync
jest lepszym wyborem.
Kierowanie danych a ciąg zapytania
Adres URL strony Szczegóły to https://localhost:<port>/Students/Details?id=1
. Wartość klucza podstawowego jednostki znajduje się w ciągu zapytania. Niektórzy deweloperzy wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Aktualizowanie strony Tworzenie
Kod szkieletu OnPostAsync
strony Tworzenie jest podatny na przesłanie. Zastąp metodę OnPostAsync
w pliku Pages/Students/Create.cshtml.cs
poniższym kodem.
public async Task<IActionResult> OnPostAsync()
{
var emptyStudent = new Student();
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
TryUpdateModelAsync
Powyższy kod tworzy obiekt Student, a następnie używa pól formularza opublikowanego do aktualizowania właściwości obiektu Student. Metoda TryUpdateModelAsync:
- Używa opublikowanych PageContext wartości formularza z właściwości w obiekcie PageModel.
- Aktualizuje tylko właściwości wymienione (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Szuka pól formularza z prefiksem "student". Na przykład
Student.FirstMidName
. Nie uwzględnia wielkości liter. - Używa systemu powiązania modelu do konwertowania wartości formularzy z ciągów na typy w
Student
modelu. Na przykładEnrollmentDate
element jest konwertowany naDateTime
.
Uruchom aplikację i utwórz jednostkę ucznia, aby przetestować stronę Tworzenie.
Zastępowanie
Używanie TryUpdateModel
funkcji do aktualizowania pól za pomocą wartości opublikowanych jest najlepszym rozwiązaniem w zakresie zabezpieczeń, ponieważ zapobiega przesłonięciom. Załóżmy na przykład, że jednostka Student zawiera Secret
właściwość, którą ta strona sieci Web nie powinna aktualizować ani dodawać:
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}
Nawet jeśli aplikacja nie ma Secret
pola na stronie tworzenia lub aktualizowania Razor , haker może ustawić Secret
wartość przez przesłanie. Haker może użyć narzędzia takiego jak Fiddler lub napisać kod JavaScript, aby opublikować Secret
wartość formularza. Oryginalny kod nie ogranicza pól używanych przez powiązanie modelu podczas tworzenia wystąpienia ucznia.
Dowolna wartość określona przez hakera Secret
dla pola formularza jest aktualizowana w bazie danych. Na poniższej ilustracji przedstawiono narzędzie Fiddler dodające Secret
pole z wartością "OverPost" do opublikowanych wartości formularza.
Wartość "OverPost" została pomyślnie dodana do Secret
właściwości wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości na stronie Tworzenie.
Wyświetlanie modelu
Wyświetlanie modeli zapewnia alternatywny sposób zapobiegania przesłonięć.
Model aplikacji jest często nazywany modelem domeny. Model domeny zwykle zawiera wszystkie właściwości wymagane przez odpowiednią jednostkę w bazie danych. Model widoku zawiera tylko właściwości wymagane dla strony interfejsu użytkownika, na przykład strony Tworzenie.
Oprócz modelu wyświetlania niektóre aplikacje używają modelu powiązania lub modelu wejściowego do przekazywania danych między Razor klasą modelu stron a przeglądarką.
Rozważmy następujący StudentVM
model widoku:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Poniższy kod używa modelu wyświetlania StudentVM
do utworzenia nowego ucznia:
[BindProperty]
public StudentVM StudentVM { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Metoda SetValues ustawia wartości tego obiektu, odczytując wartości z innego PropertyValues obiektu. SetValues
używa dopasowania nazwy właściwości. Typ modelu widoku:
- Nie musi być powiązany z typem modelu.
- Musi mieć właściwości, które są zgodne.
Użycie StudentVM
polecenia wymaga użycia StudentVM
strony Create, a nie Student
:
@page
@model CreateVMModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Student</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="StudentVM.LastName" class="control-label"></label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="control-label"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control" />
<span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
<input asp-for="StudentVM.EnrollmentDate" class="form-control" />
<span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
</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 Edytuj
W Pages/Students/Edit.cshtml.cs
pliku zastąp OnGetAsync
metody i OnPostAsync
następującym kodem.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FindAsync(id);
if (Student == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var studentToUpdate = await _context.Students.FindAsync(id);
if (studentToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
Zmiany kodu są podobne do strony Tworzenie z kilkoma wyjątkami:
FirstOrDefaultAsync
element został zastąpiony ciągiem FindAsync. Jeśli nie musisz uwzględniać powiązanych danych,FindAsync
jest wydajniejszy.OnPostAsync
id
ma parametr .- Bieżący student jest pobierany z bazy danych zamiast tworzyć pustego ucznia.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany jednostek
Kontekst bazy danych śledzi, czy jednostki w pamięci są zsynchronizowane z odpowiednimi wierszami w bazie danych. Te informacje śledzenia określają, co się stanie po wywołaniu polecenia SaveChangesAsync . Na przykład po przekazaniu nowej jednostki do AddAsync metody stan tej jednostki ma wartość Added. Po SaveChangesAsync
wywołaniu kontekst bazy danych wystawia polecenie SQL INSERT
.
Jednostka może znajdować się w jednym z następujących stanów:
Added
: jednostka nie istnieje jeszcze w bazie danych. MetodaSaveChanges
wystawia instrukcjęINSERT
.Unchanged
: nie trzeba zapisywać żadnych zmian w tej jednostce. Jednostka ma ten stan, gdy jest odczytywany z bazy danych.Modified
: Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
wystawia instrukcjęUPDATE
.Deleted
: Jednostka została oznaczona do usunięcia. MetodaSaveChanges
wystawia instrukcjęDELETE
.Detached
: jednostka nie jest śledzona przez kontekst bazy danych.
W aplikacji klasycznej zmiany stanu są zwykle ustawiane automatycznie. Jednostka jest odczytywana, wprowadzana jest zmiana, a stan jednostki jest automatycznie zmieniany na Modified
. Wywołanie SaveChanges
generuje instrukcję SQL UPDATE
, która aktualizuje tylko zmienione właściwości.
W aplikacji internetowej element odczytujący DbContext
jednostkę i wyświetlający dane są usuwane po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
zostanie wykonane nowe żądanie internetowe i z nowym wystąpieniem .DbContext
Ponowne odczytanie jednostki w tym nowym kontekście symuluje przetwarzanie pulpitu.
Aktualizowanie strony Usuwanie
W tej sekcji jest implementowany niestandardowy komunikat o błędzie, gdy wywołanie zakończy się SaveChanges
niepowodzeniem.
Zastąp kod w pliku Pages/Students/Delete.cshtml.cs
następującym kodem:
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<DeleteModel> _logger;
public DeleteModel(ContosoUniversity.Data.SchoolContext context,
ILogger<DeleteModel> logger)
{
_context = context;
_logger = logger;
}
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return NotFound();
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, ErrorMessage);
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}
Powyższy kod ma następujące działanie:
- Dodaje rejestrowanie.
- Dodaje opcjonalny parametr
saveChangesError
doOnGetAsync
podpisu metody.saveChangesError
wskazuje, czy metoda została wywołana po niepowodzeniu usunięcia obiektu ucznia.
Operacja usuwania może zakończyć się niepowodzeniem z powodu przejściowych problemów z siecią. Przejściowe błędy sieci są bardziej prawdopodobne, gdy baza danych znajduje się w chmurze. Parametr saveChangesError
jest wyświetlany false
, gdy strona OnGetAsync
Usuń jest wywoływana z interfejsu użytkownika. Gdy OnGetAsync
jest wywoływana przez OnPostAsync
element , ponieważ operacja usuwania nie powiodła się, saveChangesError
parametr ma true
wartość .
Metoda OnPostAsync
pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na Deleted
. Po SaveChanges
wywołaniu jest generowane polecenie SQL DELETE
. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda Delete pages
OnGetAsync
jest wywoływana za pomocąsaveChangesError=true
metody .
Dodaj komunikat o błędzie do :Pages/Students/Delete.cshtml
@page
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Student.ID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
Uruchom aplikację i usuń ucznia, aby przetestować stronę Usuń.
Następne kroki
W tym samouczku kod CRUD (tworzenie, odczytywanie, aktualizowanie i usuwanie) jest przeglądany i dostosowywany.
Brak repozytorium
Niektórzy deweloperzy używają warstwy usługi lub wzorca repozytorium, aby utworzyć warstwę abstrakcji między interfejsem użytkownika (Razor stron) i warstwą dostępu do danych. Ten samouczek tego nie robi. Aby zminimalizować złożoność i skoncentrować się na EF Coresamouczku, EF Core kod jest dodawany bezpośrednio do klas modelu strony.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do strony Szczegóły.
Rejestracje odczytu
Aby wyświetlić dane rejestracji ucznia na stronie, należy odczytać dane rejestracji. Kod szkieletowy w pliku Pages/Students/Details.cshtml.cs
odczytuje tylko dane ucznia bez danych rejestracji:
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Zastąp metodę OnGetAsync
poniższym kodem, aby odczytać dane rejestracji dla wybranego ucznia. Zmiany są wyróżnione.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Include Metody i ThenInclude powodują załadowanie Student.Enrollments
właściwości nawigacji kontekstu i w ramach każdej rejestracji Enrollment.Course
właściwości nawigacji. Te metody zostały szczegółowo zbadane w samouczku Dotyczącym danych związanych z czytaniem.
Metoda AsNoTracking poprawia wydajność w scenariuszach, w których zwracane jednostki nie są aktualizowane w bieżącym kontekście. AsNoTracking
zostanie omówiony w dalszej części tego samouczka.
Wyświetlanie rejestracji
Zastąp kod poniższym Pages/Students/Details.cshtml
kodem, aby wyświetlić listę rejestracji. Zmiany są wyróżnione.
@page
@model ContosoUniversity.Pages.Students.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
Poprzedni kod przechodzi przez jednostki we Enrollments
właściwości nawigacji. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z jednostki Course przechowywanej Course
we właściwości nawigacji jednostki Enrollments.
Uruchom aplikację, wybierz kartę Uczniowie , a następnie kliknij link Szczegóły dla ucznia. Zostanie wyświetlona lista kursów i ocen dla wybranego ucznia.
Sposoby odczytywania jednej jednostki
Wygenerowany kod używa metody FirstOrDefaultAsync do odczytywania jednej jednostki. Ta metoda zwraca wartość null, jeśli nic nie zostanie znalezione; W przeciwnym razie zwraca pierwszy wiersz znaleziony, który spełnia kryteria filtru zapytania. FirstOrDefaultAsync
ogólnie jest lepszym wyborem niż następujące alternatywy:
- SingleOrDefaultAsync — zgłasza wyjątek, jeśli istnieje więcej niż jedna jednostka, która spełnia filtr zapytania. Aby określić, czy zapytanie może zwrócić więcej niż jeden wiersz,
SingleOrDefaultAsync
spróbuje pobrać wiele wierszy. Ta dodatkowa praca jest niepotrzebna, jeśli zapytanie może zwrócić tylko jedną jednostkę, tak jak podczas wyszukiwania w unikatowym kluczu. - FindAsync — znajduje jednostkę z kluczem podstawowym (PK). Jeśli jednostka z kluczem PK jest śledzona przez kontekst, jest zwracana bez żądania do bazy danych. Ta metoda jest zoptymalizowana pod kątem wyszukiwania pojedynczej jednostki, ale nie można wywołać
Include
metody za pomocą poleceniaFindAsync
. Dlatego jeśli potrzebne są powiązane dane,FirstOrDefaultAsync
jest lepszym wyborem.
Kierowanie danych a ciąg zapytania
Adres URL strony Szczegóły to https://localhost:<port>/Students/Details?id=1
. Wartość klucza podstawowego jednostki znajduje się w ciągu zapytania. Niektórzy deweloperzy wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Aktualizowanie strony Tworzenie
Kod szkieletu OnPostAsync
strony Tworzenie jest podatny na przesłanie. Zastąp metodę OnPostAsync
w pliku Pages/Students/Create.cshtml.cs
poniższym kodem.
public async Task<IActionResult> OnPostAsync()
{
var emptyStudent = new Student();
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
TryUpdateModelAsync
Powyższy kod tworzy obiekt Student, a następnie używa pól formularza opublikowanego do aktualizowania właściwości obiektu Student. Metoda TryUpdateModelAsync:
- Używa opublikowanych PageContext wartości formularza z właściwości w obiekcie PageModel.
- Aktualizuje tylko właściwości wymienione (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Szuka pól formularza z prefiksem "student". Na przykład
Student.FirstMidName
. Nie uwzględnia wielkości liter. - Używa systemu powiązania modelu do konwertowania wartości formularzy z ciągów na typy w
Student
modelu. Na przykładEnrollmentDate
należy przekonwertować na datetime.
Uruchom aplikację i utwórz jednostkę ucznia, aby przetestować stronę Tworzenie.
Zastępowanie
Używanie TryUpdateModel
funkcji do aktualizowania pól za pomocą wartości opublikowanych jest najlepszym rozwiązaniem w zakresie zabezpieczeń, ponieważ zapobiega przesłonięciom. Załóżmy na przykład, że jednostka Student zawiera Secret
właściwość, którą ta strona sieci Web nie powinna aktualizować ani dodawać:
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}
Nawet jeśli aplikacja nie ma Secret
pola na stronie tworzenia lub aktualizowania Razor , haker może ustawić Secret
wartość przez przesłanie. Haker może użyć narzędzia takiego jak Fiddler lub napisać kod JavaScript, aby opublikować Secret
wartość formularza. Oryginalny kod nie ogranicza pól używanych przez powiązanie modelu podczas tworzenia wystąpienia ucznia.
Dowolna wartość określona przez hakera Secret
dla pola formularza jest aktualizowana w bazie danych. Na poniższej ilustracji przedstawiono narzędzie Fiddler dodające Secret
pole (z wartością "OverPost") do opublikowanych wartości formularza.
Wartość "OverPost" została pomyślnie dodana do Secret
właściwości wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości na stronie Tworzenie.
Wyświetlanie modelu
Wyświetlanie modeli zapewnia alternatywny sposób zapobiegania przesłonięć.
Model aplikacji jest często nazywany modelem domeny. Model domeny zwykle zawiera wszystkie właściwości wymagane przez odpowiednią jednostkę w bazie danych. Model widoku zawiera tylko właściwości wymagane dla interfejsu użytkownika, dla którego jest używany (na przykład strona Tworzenie).
Oprócz modelu wyświetlania niektóre aplikacje używają modelu powiązania lub modelu wejściowego do przekazywania danych między Razor klasą modelu stron a przeglądarką.
Rozważmy następujący Student
model widoku:
using System;
namespace ContosoUniversity.Models
{
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}
Poniższy kod używa modelu wyświetlania StudentVM
do utworzenia nowego ucznia:
[BindProperty]
public StudentVM StudentVM { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Metoda SetValues ustawia wartości tego obiektu, odczytując wartości z innego PropertyValues obiektu. SetValues
używa dopasowania nazwy właściwości. Typ modelu widoku nie musi być powiązany z typem modelu, ale musi mieć zgodne właściwości.
Użycie StudentVM
polecenia wymaga zaktualizowania pliku Create.cshtml do użycia StudentVM
, a nie Student
.
Aktualizowanie strony Edytuj
W Pages/Students/Edit.cshtml.cs
pliku zastąp OnGetAsync
metody i OnPostAsync
następującym kodem.
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FindAsync(id);
if (Student == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var studentToUpdate = await _context.Students.FindAsync(id);
if (studentToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
Zmiany kodu są podobne do strony Tworzenie z kilkoma wyjątkami:
FirstOrDefaultAsync
element został zastąpiony ciągiem FindAsync. Jeśli dołączone powiązane dane nie są potrzebne,FindAsync
jest bardziej wydajne.OnPostAsync
id
ma parametr .- Bieżący student jest pobierany z bazy danych zamiast tworzyć pustego ucznia.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany jednostek
Kontekst bazy danych śledzi, czy jednostki w pamięci są zsynchronizowane z odpowiednimi wierszami w bazie danych. Te informacje śledzenia określają, co się stanie po wywołaniu polecenia SaveChangesAsync . Na przykład po przekazaniu nowej jednostki do AddAsync metody stan tej jednostki ma wartość Added. Po SaveChangesAsync
wywołaniu kontekst bazy danych wystawia polecenie SQL INSERT.
Jednostka może znajdować się w jednym z następujących stanów:
Added
: jednostka nie istnieje jeszcze w bazie danych. MetodaSaveChanges
wystawia instrukcję INSERT.Unchanged
: nie trzeba zapisywać żadnych zmian w tej jednostce. Jednostka ma ten stan, gdy jest odczytywany z bazy danych.Modified
: Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
wystawia instrukcję UPDATE.Deleted
: Jednostka została oznaczona do usunięcia. MetodaSaveChanges
wystawia instrukcję DELETE.Detached
: jednostka nie jest śledzona przez kontekst bazy danych.
W aplikacji klasycznej zmiany stanu są zwykle ustawiane automatycznie. Jednostka jest odczytywana, wprowadzana jest zmiana, a stan jednostki jest automatycznie zmieniany na Modified
. Wywołanie SaveChanges
generuje instrukcję SQL UPDATE, która aktualizuje tylko zmienione właściwości.
W aplikacji internetowej element odczytujący DbContext
jednostkę i wyświetlający dane są usuwane po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
zostanie wykonane nowe żądanie internetowe i z nowym wystąpieniem .DbContext
Ponowne odczytanie jednostki w tym nowym kontekście symuluje przetwarzanie pulpitu.
Aktualizowanie strony Usuwanie
W tej sekcji zaimplementujesz niestandardowy komunikat o błędzie, gdy wywołanie zakończy się SaveChanges
niepowodzeniem.
Zamień kod w pliku Pages/Students/Delete.cshtml.cs
na następujący kod. Zmiany są wyróżnione (inne niż czyszczenie instrukcji using
).
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public DeleteModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = "Delete failed. Try again";
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return NotFound();
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}
Powyższy kod dodaje opcjonalny parametr saveChangesError
do OnGetAsync
podpisu metody. saveChangesError
wskazuje, czy metoda została wywołana po niepowodzeniu usunięcia obiektu ucznia. Operacja usuwania może zakończyć się niepowodzeniem z powodu przejściowych problemów z siecią. Przejściowe błędy sieci są bardziej prawdopodobne, gdy baza danych znajduje się w chmurze. Parametr saveChangesError
ma wartość false, gdy strona OnGetAsync
Usuń jest wywoływana z interfejsu użytkownika. Gdy OnGetAsync
parametr jest wywoływany przez OnPostAsync
(ponieważ operacja usuwania nie powiodła się), saveChangesError
parametr ma wartość true.
Metoda OnPostAsync
pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na Deleted
. Po SaveChanges
wywołaniu jest generowane polecenie SQL DELETE. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda strony Delete jest wywoływana
OnGetAsync
za pomocąsaveChangesError=true
metody .
Dodaj komunikat o błędzie do strony usuwania Razor (Pages/Students/Delete.cshtml
):
@page
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Student.ID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
Uruchom aplikację i usuń ucznia, aby przetestować stronę Usuń.