Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz artykuł w wersji .NET 9.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz artykuł w wersji .NET 9.
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ą, zobacz artykuł w wersji .NET 9.
Przez Tom Dykstra, Jeremy Likness i Jon P Smith
Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje webowe za pomocą 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 przeglądany i dostosowywany jest kod CRUD (tworzenie, odczytywanie, aktualizacja, usuwanie) przygotowany wstępnie.
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 samouczku EF Core, kod EF Core dodaje się bezpośrednio do klas strony modelu.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do strony Details
.
Przeglądanie zapisów
Aby wyświetlić dane rejestracji ucznia na stronie, należy odczytać dane rejestracji. Kod szablonowy w Pages/Students/Details.cshtml.cs
odczytuje tylko dane Student
, bez danych Enrollment
.
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 i ThenInclude powodują, że kontekst ładuje właściwość nawigacji Student.Enrollments
oraz w ramach każdej rejestracji właściwość nawigacji Enrollment.Course
. 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 w Pages/Students/Details.cshtml
poniższym kodem, aby wyświetlić listę zapisów. 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 wchodzi w pętlę przez encje w właściwości nawigacyjnej Enrollments
. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z encji Course
, która jest przechowywana we właściwości nawigacyjnej Course
encji 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 programiści wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Zaktualizuj stronę tworzenia
Kod wygenerowany OnPostAsync
dla strony Tworzenie jest podatny na nadmierne przesyłanie danych. 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 przesłanych wartości formularza z właściwości PageContext w 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 jest wrażliwe na wielkość liter. -
Używa systemu powiązania modelu do konwertowania wartości formularzy z ciągów na typy w
Student
modelu. Na przykładEnrollmentDate
jest konwertowane naDateTime
.
Uruchom aplikację i utwórz obiekt ucznia, aby przetestować stronę tworzenia.
Nadmierne publikowanie
Używanie TryUpdateModel
do aktualizowania pól wartościami zamieszczonymi jest najlepszym rozwiązaniem w zakresie bezpieczeństwa, ponieważ zapobiega nadpisywaniu danych. 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 pola Secret
na stronie tworzenia lub aktualizowania Razor, haker może ustawić wartość Secret
przez nadpisanie. 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 model binder podczas tworzenia wystąpienia klasy Student.
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 właściwości Secret
wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości za pomocą strony 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 widoku 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 dopasowywania nazw 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.
Wymagane jest użycie StudentVM
na stronie Create zamiast StudentVM
:
@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");}
}
Zaktualizuj stronę edycji
W Pages/Students/Edit.cshtml.cs
zastąp metody OnGetAsync
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
został zastąpiony przez FindAsync. Jeśli nie musisz uwzględniać powiązanych danych,FindAsync
jest wydajniejszy. -
OnPostAsync
ma parametrid
. - Bieżący student jest pobierany z bazy danych zamiast tworzenia pustego studenta.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany podmiotów
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
wydaje oświadczenieINSERT
.Unchanged
: nie trzeba zapisywać żadnych zmian w tej jednostce. Jednostka ma ten stan, gdy jest odczytywana z bazy danych.Modified
: Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
wydaje oświadczenieUPDATE
.Deleted
: Jednostka została oznaczona do usunięcia. MetodaSaveChanges
wystawia oświadczenieDELETE
.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, który odczytuje jednostkę DbContext
i wyświetla dane, jest usuwany po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
, zostanie wykonane nowe żądanie internetowe z nowym wystąpieniem DbContext
. Ponowne odczytanie danych w tym nowym kontekście symuluje przetwarzanie przez komputer stacjonarny.
Zaktualizuj stronę usuwania
W tej sekcji implementowany jest niestandardowy komunikat o błędzie, gdy wywołanie SaveChanges
nie powiedzie się.
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 });
}
}
}
}
Poprzedni kod:
- 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 false
, gdy strona Usuń OnGetAsync
jest wywoływana z interfejsu użytkownika. Gdy OnGetAsync
jest wywoływane przez OnPostAsync
, ponieważ operacja usuwania nie powiodła się, wartość parametru saveChangesError
jest true
.
Metoda OnPostAsync
pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na Deleted
. Po wywołaniu SaveChanges
, generowane jest polecenie SQL DELETE
. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda
OnGetAsync
Delete pages jest wywoływana zsaveChangesError=true
.
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 przeglądany i dostosowywany jest kod CRUD (tworzenie, odczytywanie, aktualizacja, usuwanie) przygotowany wstępnie.
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 samouczku EF Core, kod EF Core dodaje się bezpośrednio do klas strony modelu.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do strony Details
.
Przeglądanie zapisów
Aby wyświetlić dane rejestracji ucznia na stronie, należy odczytać dane rejestracji. Kod szablonowy w Pages/Students/Details.cshtml.cs
odczytuje tylko dane Student
, bez danych Enrollment
.
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 i ThenInclude powodują, że kontekst ładuje właściwość nawigacji Student.Enrollments
oraz w ramach każdej rejestracji właściwość nawigacji Enrollment.Course
. 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 w Pages/Students/Details.cshtml
poniższym kodem, aby wyświetlić listę zapisów. 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 wchodzi w pętlę przez encje w właściwości nawigacyjnej Enrollments
. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z encji Course
, która jest przechowywana we właściwości nawigacyjnej Course
encji 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 programiści wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Zaktualizuj stronę tworzenia
Kod wygenerowany OnPostAsync
dla strony Tworzenie jest podatny na nadmierne przesyłanie danych. 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 przesłanych wartości formularza z właściwości PageContext w 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 jest wrażliwe na wielkość liter. -
Używa systemu powiązania modelu do konwertowania wartości formularzy z ciągów na typy w
Student
modelu. Na przykładEnrollmentDate
jest konwertowane naDateTime
.
Uruchom aplikację i utwórz obiekt ucznia, aby przetestować stronę tworzenia.
Nadmierne publikowanie
Używanie TryUpdateModel
do aktualizowania pól wartościami zamieszczonymi jest najlepszym rozwiązaniem w zakresie bezpieczeństwa, ponieważ zapobiega nadpisywaniu danych. 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 pola Secret
na stronie tworzenia lub aktualizowania Razor, haker może ustawić wartość Secret
przez nadpisanie. 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 model binder podczas tworzenia wystąpienia klasy Student.
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 właściwości Secret
wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości za pomocą strony 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 widoku 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 dopasowywania nazw 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.
Wymagane jest użycie StudentVM
na stronie Create zamiast StudentVM
:
@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");}
}
Zaktualizuj stronę edycji
W Pages/Students/Edit.cshtml.cs
zastąp metody OnGetAsync
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
został zastąpiony przez FindAsync. Jeśli nie musisz uwzględniać powiązanych danych,FindAsync
jest wydajniejszy. -
OnPostAsync
ma parametrid
. - Bieżący student jest pobierany z bazy danych zamiast tworzenia pustego studenta.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany podmiotów
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
wydaje oświadczenieINSERT
.Unchanged
: nie trzeba zapisywać żadnych zmian w tej jednostce. Jednostka ma ten stan, gdy jest odczytywana z bazy danych.Modified
: Niektóre lub wszystkie wartości właściwości jednostki zostały zmodyfikowane. MetodaSaveChanges
wydaje oświadczenieUPDATE
.Deleted
: Jednostka została oznaczona do usunięcia. MetodaSaveChanges
wystawia oświadczenieDELETE
.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, który odczytuje jednostkę DbContext
i wyświetla dane, jest usuwany po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
, zostanie wykonane nowe żądanie internetowe z nowym wystąpieniem DbContext
. Ponowne odczytanie danych w tym nowym kontekście symuluje przetwarzanie przez komputer stacjonarny.
Zaktualizuj stronę usuwania
W tej sekcji implementowany jest niestandardowy komunikat o błędzie, gdy wywołanie SaveChanges
nie powiedzie się.
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 });
}
}
}
}
Poprzedni kod:
- 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 false
, gdy strona Usuń OnGetAsync
jest wywoływana z interfejsu użytkownika. Gdy OnGetAsync
jest wywoływane przez OnPostAsync
, ponieważ operacja usuwania nie powiodła się, wartość parametru saveChangesError
jest true
.
Metoda OnPostAsync
pobiera wybraną jednostkę, a następnie wywołuje metodę Remove , aby ustawić stan jednostki na Deleted
. Po wywołaniu SaveChanges
, generowane jest polecenie SQL DELETE
. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda
OnGetAsync
Delete pages jest wywoływana zsaveChangesError=true
.
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 przeglądany i dostosowywany jest kod CRUD (tworzenie, odczytywanie, aktualizacja, usuwanie) przygotowany wstępnie.
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 samouczku EF Core, kod EF Core dodaje się bezpośrednio do klas strony modelu.
Aktualizowanie strony Szczegóły
Kod szkieletowy stron Uczniów nie zawiera danych rejestracji. W tej sekcji rejestracje są dodawane do strony Szczegóły.
Przeglądanie zapisów
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 i ThenInclude powodują, że kontekst ładuje właściwość nawigacji Student.Enrollments
oraz w ramach każdej rejestracji właściwość nawigacji Enrollment.Course
. 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 w Pages/Students/Details.cshtml
poniższym kodem, aby wyświetlić listę zapisów. 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 wchodzi w pętlę przez encje w właściwości nawigacyjnej Enrollments
. Dla każdej rejestracji wyświetla tytuł kursu i ocenę. Tytuł kursu jest pobierany z jednostki Kursu, która jest przechowywana we właściwości nawigacyjnej 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 programiści wolą przekazywać wartość klucza w danych tras: https://localhost:<port>/Students/Details/1
. Aby uzyskać więcej informacji, zobacz Aktualizowanie wygenerowanego kodu.
Zaktualizuj stronę tworzenia
Kod wygenerowany OnPostAsync
dla strony Tworzenie jest podatny na nadmierne przesyłanie danych. 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 przesłanych wartości formularza z właściwości PageContext w 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 jest wrażliwe na wielkość 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 obiekt ucznia, aby przetestować stronę tworzenia.
Nadmierne publikowanie
Używanie TryUpdateModel
do aktualizowania pól wartościami zamieszczonymi jest najlepszym rozwiązaniem w zakresie bezpieczeństwa, ponieważ zapobiega nadpisywaniu danych. 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 pola Secret
na stronie tworzenia lub aktualizowania Razor, haker może ustawić wartość Secret
przez nadpisanie. 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 model binder podczas tworzenia wystąpienia klasy Student.
Dowolna wartość określona przez hakera Secret
dla pola formularza jest aktualizowana w bazie danych. Na poniższym obrazie pokazano, jak narzędzie Fiddler dodaje pole Secret
(z wartością "OverPost") do wysłanych wartości formularza.
Wartość "OverPost" została pomyślnie dodana do właściwości Secret
wstawionego wiersza. Dzieje się tak, mimo że projektant aplikacji nigdy nie zamierzał Secret
ustawiać właściwości za pomocą strony 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 widoku 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 dopasowywania nazw 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
wymaga zaktualizowania pliku Create.cshtml, aby korzystać z StudentVM
zamiast Student
.
Zaktualizuj stronę edycji
W Pages/Students/Edit.cshtml.cs
zastąp metody OnGetAsync
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
został zastąpiony przez FindAsync. Jeśli dołączone powiązane dane nie są potrzebne,FindAsync
jest bardziej wydajne. -
OnPostAsync
ma parametrid
. - Bieżący student jest pobierany z bazy danych zamiast tworzenia pustego studenta.
Uruchom aplikację i przetestuj ją, tworząc i edytując ucznia.
Stany podmiotów
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 odczytywana 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, który odczytuje jednostkę DbContext
i wyświetla dane, jest usuwany po renderowaniu strony. Po wywołaniu metody strony OnPostAsync
, zostanie wykonane nowe żądanie internetowe z nowym wystąpieniem DbContext
. Ponowne odczytanie danych w tym nowym kontekście symuluje przetwarzanie przez komputer stacjonarny.
Zaktualizuj stronę usuwania
Niestandardowy komunikat o błędzie zaimplementujesz w tej sekcji, gdy wywołanie do SaveChanges
nie powiedzie się.
Zamień kod w pliku Pages/Students/Delete.cshtml.cs
na następujący kod. Zmiany są wyróżnione (inne niż czyszczenie oświadczeń 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 Usuń OnGetAsync
jest wywoływana z poziomu 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 wywołaniu SaveChanges
generowane jest polecenie SQL DELETE. W przypadku Remove
niepowodzenia:
- Przechwycono wyjątek bazy danych.
- Metoda Delete strony jest wywoływana
OnGetAsync
przy użyciusaveChangesError=true
.
Dodaj komunikat o błędzie do strony Usuń 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ń.