Bagian 2, Razor Halaman dengan EF Core inti ASP.NET - CRUD
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Oleh Tom Dykstra, Jeremy Likness, dan Jon P Smith
Aplikasi web Contoso University menunjukkan cara membuat Razor aplikasi web Pages menggunakan EF Core dan Visual Studio. Untuk informasi tentang seri tutorial, lihat tutorial pertama.
Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai dan bandingkan kode tersebut dengan apa yang Anda buat dengan mengikuti tutorial.
Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.
Tidak ada repositori
Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.
Memperbarui halaman Detail
Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke Details
halaman.
Membaca pendaftaran
Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran harus dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs
hanya Student
membaca data, tanpa Enrollment
data:
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();
}
OnGetAsync
Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.
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();
}
Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments
properti navigasi, dan dalam setiap pendaftaran Enrollment.Course
properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.
Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking
dibahas nanti dalam tutorial ini.
Tampilkan pendaftaran
Ganti kode dengan Pages/Students/Details.cshtml
kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.
@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>
Kode sebelumnya mengulangi entitas di Enrollments
properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari Course
entitas yang disimpan di Course
properti navigasi entitas Pendaftaran.
Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.
Cara membaca satu entitas
Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync
umumnya adalah pilihan yang lebih baik daripada alternatif berikut:
- SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri,
SingleOrDefaultAsync
coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik. - FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil
Include
denganFindAsync
. Jadi jika data terkait diperlukan,FirstOrDefaultAsync
adalah pilihan yang lebih baik.
Merutekan data vs. string kueri
URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1
. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1
. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.
Memperbarui halaman Buat
Kode perancah OnPostAsync
untuk halaman Buat rentan terhadap overposting. OnPostAsync
Ganti metode dengan Pages/Students/Create.cshtml.cs
kode berikut.
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
Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :
- Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
- Memperbarui hanya properti yang tercantum (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Mencari bidang formulir dengan awalan "siswa". Contohnya,
Student.FirstMidName
. Ini tidak peka huruf besar/kecil. - Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam
Student
model. Misalnya,EnrollmentDate
dikonversi keDateTime
.
Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.
Overposting
Menggunakan TryUpdateModel
untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret
properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:
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; }
}
Bahkan jika aplikasi tidak memiliki Secret
bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret
melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret
nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.
Nilai apa pun yang ditentukan peretas untuk Secret
bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret
bidang, dengan nilai "OverPost", ke nilai formulir yang diposting.
Nilai "OverPost" berhasil ditambahkan ke Secret
properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret
properti diatur dengan halaman Buat.
Lihat model
Model tampilan menyediakan cara alternatif untuk mencegah overposting.
Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk halaman UI, misalnya, halaman Buat.
Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.
Pertimbangkan model tampilan berikut StudentVM
:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Kode berikut menggunakan StudentVM
model tampilan untuk membuat siswa baru:
[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");
}
Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues
menggunakan pencocokan nama properti. Jenis model tampilan:
- Tidak perlu terkait dengan jenis model.
- Harus memiliki properti yang cocok.
Menggunakan StudentVM
memerlukan penggunaan StudentVM
halaman Buat daripada 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");}
}
Memperbarui halaman Edit
Di Pages/Students/Edit.cshtml.cs
, ganti OnGetAsync
metode dan OnPostAsync
dengan kode berikut.
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();
}
Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:
FirstOrDefaultAsync
telah diganti dengan FindAsync. Ketika Anda tidak perlu menyertakan data terkait,FindAsync
lebih efisien.OnPostAsync
memilikiid
parameter .- Siswa saat ini diambil dari database, daripada membuat siswa kosong.
Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.
Status Entitas
Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync
dipanggil, konteks database mengeluarkan perintah SQL INSERT
.
Entitas mungkin berada di salah satu status berikut:
Added
: Entitas belum ada di database. Metode mengeluarkanSaveChanges
INSERT
pernyataan.Unchanged
: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.Modified
: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode mengeluarkanSaveChanges
UPDATE
pernyataan.Deleted
: Entitas telah ditandai untuk penghapusan. Metode mengeluarkanSaveChanges
DELETE
pernyataan.Detached
: Entitas tidak dilacak oleh konteks database.
Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified
. SaveChanges
Panggilan menghasilkan pernyataan SQL UPDATE
yang hanya memperbarui properti yang diubah.
Di aplikasi web, DbContext
yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync
dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext
. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.
Memperbarui halaman Hapus
Di bagian ini, pesan kesalahan kustom diimplementasikan saat panggilan gagal SaveChanges
.
Ganti kode di Pages/Students/Delete.cshtml.cs
dengan kode berikut:
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 });
}
}
}
}
Kode sebelumnya:
- Menambahkan Pengelogan.
- Menambahkan parameter
saveChangesError
opsional keOnGetAsync
tanda tangan metode.saveChangesError
menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa.
Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameternya saveChangesError
adalah false
ketika halaman OnGetAsync
Hapus dipanggil dari UI. Ketika OnGetAsync
dipanggil oleh OnPostAsync
karena operasi penghapusan gagal, saveChangesError
parameternya adalah true
.
Metode mengambil OnPostAsync
entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted
. Ketika SaveChanges
dipanggil, perintah SQL DELETE
dihasilkan. Jika Remove
gagal:
- Pengecualian database tertangkap.
- Metode Hapus halaman
OnGetAsync
dipanggil dengansaveChangesError=true
.
Tambahkan pesan kesalahan ke 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>
Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.
Langkah berikutnya
Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.
Tidak ada repositori
Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.
Memperbarui halaman Detail
Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke Details
halaman.
Membaca pendaftaran
Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran harus dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs
hanya Student
membaca data, tanpa Enrollment
data:
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();
}
OnGetAsync
Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.
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();
}
Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments
properti navigasi, dan dalam setiap pendaftaran Enrollment.Course
properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.
Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking
dibahas nanti dalam tutorial ini.
Tampilkan pendaftaran
Ganti kode dengan Pages/Students/Details.cshtml
kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.
@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>
Kode sebelumnya mengulangi entitas di Enrollments
properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari Course
entitas yang disimpan di Course
properti navigasi entitas Pendaftaran.
Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.
Cara membaca satu entitas
Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync
umumnya adalah pilihan yang lebih baik daripada alternatif berikut:
- SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri,
SingleOrDefaultAsync
coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik. - FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil
Include
denganFindAsync
. Jadi jika data terkait diperlukan,FirstOrDefaultAsync
adalah pilihan yang lebih baik.
Merutekan data vs. string kueri
URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1
. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1
. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.
Memperbarui halaman Buat
Kode perancah OnPostAsync
untuk halaman Buat rentan terhadap overposting. OnPostAsync
Ganti metode dengan Pages/Students/Create.cshtml.cs
kode berikut.
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
Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :
- Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
- Memperbarui hanya properti yang tercantum (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Mencari bidang formulir dengan awalan "siswa". Contohnya,
Student.FirstMidName
. Ini tidak peka huruf besar/kecil. - Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam
Student
model. Misalnya,EnrollmentDate
dikonversi keDateTime
.
Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.
Overposting
Menggunakan TryUpdateModel
untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret
properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:
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; }
}
Bahkan jika aplikasi tidak memiliki Secret
bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret
melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret
nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.
Nilai apa pun yang ditentukan peretas untuk Secret
bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret
bidang, dengan nilai "OverPost", ke nilai formulir yang diposting.
Nilai "OverPost" berhasil ditambahkan ke Secret
properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret
properti diatur dengan halaman Buat.
Lihat model
Model tampilan menyediakan cara alternatif untuk mencegah overposting.
Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk halaman UI, misalnya, halaman Buat.
Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.
Pertimbangkan model tampilan berikut StudentVM
:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Kode berikut menggunakan StudentVM
model tampilan untuk membuat siswa baru:
[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");
}
Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues
menggunakan pencocokan nama properti. Jenis model tampilan:
- Tidak perlu terkait dengan jenis model.
- Harus memiliki properti yang cocok.
Menggunakan StudentVM
memerlukan penggunaan StudentVM
halaman Buat daripada 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");}
}
Memperbarui halaman Edit
Di Pages/Students/Edit.cshtml.cs
, ganti OnGetAsync
metode dan OnPostAsync
dengan kode berikut.
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();
}
Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:
FirstOrDefaultAsync
telah diganti dengan FindAsync. Ketika Anda tidak perlu menyertakan data terkait,FindAsync
lebih efisien.OnPostAsync
memilikiid
parameter .- Siswa saat ini diambil dari database, daripada membuat siswa kosong.
Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.
Status Entitas
Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync
dipanggil, konteks database mengeluarkan perintah SQL INSERT
.
Entitas mungkin berada di salah satu status berikut:
Added
: Entitas belum ada di database. Metode mengeluarkanSaveChanges
INSERT
pernyataan.Unchanged
: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.Modified
: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode mengeluarkanSaveChanges
UPDATE
pernyataan.Deleted
: Entitas telah ditandai untuk penghapusan. Metode mengeluarkanSaveChanges
DELETE
pernyataan.Detached
: Entitas tidak dilacak oleh konteks database.
Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified
. SaveChanges
Panggilan menghasilkan pernyataan SQL UPDATE
yang hanya memperbarui properti yang diubah.
Di aplikasi web, DbContext
yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync
dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext
. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.
Memperbarui halaman Hapus
Di bagian ini, pesan kesalahan kustom diimplementasikan saat panggilan gagal SaveChanges
.
Ganti kode di Pages/Students/Delete.cshtml.cs
dengan kode berikut:
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 });
}
}
}
}
Kode sebelumnya:
- Menambahkan Pengelogan.
- Menambahkan parameter
saveChangesError
opsional keOnGetAsync
tanda tangan metode.saveChangesError
menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa.
Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameternya saveChangesError
adalah false
ketika halaman OnGetAsync
Hapus dipanggil dari UI. Ketika OnGetAsync
dipanggil oleh OnPostAsync
karena operasi penghapusan gagal, saveChangesError
parameternya adalah true
.
Metode mengambil OnPostAsync
entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted
. Ketika SaveChanges
dipanggil, perintah SQL DELETE
dihasilkan. Jika Remove
gagal:
- Pengecualian database tertangkap.
- Metode Hapus halaman
OnGetAsync
dipanggil dengansaveChangesError=true
.
Tambahkan pesan kesalahan ke 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>
Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.
Langkah berikutnya
Dalam tutorial ini, kode CRUD (buat, baca, perbarui, hapus) perancah ditinjau dan disesuaikan.
Tidak ada repositori
Beberapa pengembang menggunakan lapisan layanan atau pola repositori untuk membuat lapisan abstraksi antara UI (Razor Pages) dan lapisan akses data. Tutorial ini tidak melakukan itu. Untuk meminimalkan kompleksitas dan menjaga tutorial tetap fokus pada EF Core, EF Core kode ditambahkan langsung ke kelas model halaman.
Memperbarui halaman Detail
Kode perancah untuk halaman Siswa tidak menyertakan data pendaftaran. Di bagian ini, pendaftaran ditambahkan ke halaman Detail.
Membaca pendaftaran
Untuk menampilkan data pendaftaran siswa di halaman, data pendaftaran perlu dibaca. Kode perancah dalam Pages/Students/Details.cshtml.cs
hanya membaca data Siswa, tanpa data Pendaftaran:
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();
}
OnGetAsync
Ganti metode dengan kode berikut untuk membaca data pendaftaran untuk siswa yang dipilih. Perubahan disorot.
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();
}
Metode Include dan ThenInclude menyebabkan konteks memuat Student.Enrollments
properti navigasi, dan dalam setiap pendaftaran Enrollment.Course
properti navigasi. Metode ini diperiksa secara rinci dalam tutorial Membaca data terkait.
Metode ini AsNoTracking meningkatkan performa dalam skenario di mana entitas yang dikembalikan tidak diperbarui dalam konteks saat ini. AsNoTracking
dibahas nanti dalam tutorial ini.
Tampilkan pendaftaran
Ganti kode dengan Pages/Students/Details.cshtml
kode berikut untuk menampilkan daftar pendaftaran. Perubahan disorot.
@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>
Kode sebelumnya mengulangi entitas di Enrollments
properti navigasi. Untuk setiap pendaftaran, ini menampilkan judul kursus dan nilai. Judul kursus diambil dari entitas Kursus yang disimpan di Course
properti navigasi entitas Pendaftaran.
Jalankan aplikasi, pilih tab Siswa , dan klik tautan Detail untuk siswa. Daftar kursus dan nilai untuk siswa yang dipilih ditampilkan.
Cara membaca satu entitas
Kode yang dihasilkan menggunakan FirstOrDefaultAsync untuk membaca satu entitas. Metode ini mengembalikan null jika tidak ada yang ditemukan; jika tidak, ini mengembalikan baris pertama yang ditemukan yang memenuhi kriteria filter kueri. FirstOrDefaultAsync
umumnya adalah pilihan yang lebih baik daripada alternatif berikut:
- SingleOrDefaultAsync - Melemparkan pengecualian jika ada lebih dari satu entitas yang memenuhi filter kueri. Untuk menentukan apakah lebih dari satu baris dapat dikembalikan oleh kueri,
SingleOrDefaultAsync
coba ambil beberapa baris. Pekerjaan tambahan ini tidak perlu jika kueri hanya dapat mengembalikan satu entitas, seperti ketika mencari pada kunci unik. - FindAsync - Menemukan entitas dengan kunci primer (PK). Jika entitas dengan PK sedang dilacak oleh konteks, entitas dikembalikan tanpa permintaan ke database. Metode ini dioptimalkan untuk mencari satu entitas, tetapi Anda tidak dapat memanggil
Include
denganFindAsync
. Jadi jika data terkait diperlukan,FirstOrDefaultAsync
adalah pilihan yang lebih baik.
Merutekan data vs. string kueri
URL untuk halaman Detail adalah https://localhost:<port>/Students/Details?id=1
. Nilai kunci utama entitas berada dalam string kueri. Beberapa pengembang lebih suka meneruskan nilai kunci dalam data rute: https://localhost:<port>/Students/Details/1
. Untuk informasi selengkapnya, lihat Memperbarui kode yang dihasilkan.
Memperbarui halaman Buat
Kode perancah OnPostAsync
untuk halaman Buat rentan terhadap overposting. OnPostAsync
Ganti metode dengan Pages/Students/Create.cshtml.cs
kode berikut.
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
Kode sebelumnya membuat objek Siswa lalu menggunakan bidang formulir yang diposting untuk memperbarui properti objek Siswa. Metode TryUpdateModelAsync :
- Menggunakan nilai formulir yang diposting dari PageContext properti di PageModel.
- Memperbarui hanya properti yang tercantum (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate
). - Mencari bidang formulir dengan awalan "siswa". Contohnya,
Student.FirstMidName
. Ini tidak peka huruf besar/kecil. - Menggunakan sistem pengikatan model untuk mengonversi nilai formulir dari string ke jenis dalam
Student
model. Misalnya,EnrollmentDate
harus dikonversi ke DateTime.
Jalankan aplikasi, dan buat entitas siswa untuk menguji halaman Buat.
Overposting
Menggunakan TryUpdateModel
untuk memperbarui bidang dengan nilai yang diposting adalah praktik terbaik keamanan karena mencegah overposting. Misalnya, entitas Siswa menyertakan Secret
properti yang tidak boleh diperbarui atau ditambahkan halaman web ini:
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; }
}
Bahkan jika aplikasi tidak memiliki Secret
bidang di Halaman buat atau perbarui Razor , peretas dapat mengatur nilai dengan Secret
melakukan overposting. Peretas dapat menggunakan alat seperti Fiddler, atau menulis beberapa JavaScript, untuk memposting Secret
nilai formulir. Kode asli tidak membatasi bidang yang digunakan pengikat model saat membuat instans Siswa.
Nilai apa pun yang ditentukan peretas untuk Secret
bidang formulir diperbarui dalam database. Gambar berikut menunjukkan alat Fiddler yang menambahkan Secret
bidang (dengan nilai "OverPost") ke nilai formulir yang diposting.
Nilai "OverPost" berhasil ditambahkan ke Secret
properti baris yang disisipkan. Itu terjadi meskipun perancang aplikasi tidak pernah menginginkan Secret
properti diatur dengan halaman Buat.
Lihat model
Model tampilan menyediakan cara alternatif untuk mencegah overposting.
Model aplikasi sering disebut model domain. Model domain biasanya berisi semua properti yang diperlukan oleh entitas yang sesuai dalam database. Model tampilan hanya berisi properti yang diperlukan untuk UI yang digunakannya (misalnya, halaman Buat).
Selain model tampilan, beberapa aplikasi menggunakan model pengikatan atau model input untuk meneruskan data antara Razor kelas model halaman Halaman dan browser.
Pertimbangkan model tampilan berikut Student
:
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; }
}
}
Kode berikut menggunakan StudentVM
model tampilan untuk membuat siswa baru:
[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");
}
Metode SetValues mengatur nilai objek ini dengan membaca nilai dari objek lain PropertyValues . SetValues
menggunakan pencocokan nama properti. Jenis model tampilan tidak perlu terkait dengan jenis model, hanya perlu memiliki properti yang cocok.
Menggunakan StudentVM
mengharuskan Create.cshtml diperbarui untuk digunakan StudentVM
daripada Student
.
Memperbarui halaman Edit
Di Pages/Students/Edit.cshtml.cs
, ganti OnGetAsync
metode dan OnPostAsync
dengan kode berikut.
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();
}
Perubahan kode mirip dengan halaman Buat dengan beberapa pengecualian:
FirstOrDefaultAsync
telah diganti dengan FindAsync. Ketika data terkait yang disertakan tidak diperlukan,FindAsync
lebih efisien.OnPostAsync
memilikiid
parameter .- Siswa saat ini diambil dari database, daripada membuat siswa kosong.
Jalankan aplikasi, dan uji dengan membuat dan mengedit siswa.
Status Entitas
Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database. Informasi pelacakan ini menentukan apa yang terjadi ketika SaveChangesAsync dipanggil. Misalnya, ketika entitas baru diteruskan ke AddAsync metode , status entitas tersebut diatur ke Added. Ketika SaveChangesAsync
dipanggil, konteks database mengeluarkan perintah SQL INSERT.
Entitas mungkin berada di salah satu status berikut:
Added
: Entitas belum ada di database. Metode mengeluarkanSaveChanges
pernyataan INSERT.Unchanged
: Tidak ada perubahan yang perlu disimpan dengan entitas ini. Entitas memiliki status ini saat dibaca dari database.Modified
: Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode iniSaveChanges
mengeluarkan pernyataan UPDATE.Deleted
: Entitas telah ditandai untuk penghapusan. Metode mengeluarkanSaveChanges
pernyataan DELETE.Detached
: Entitas tidak dilacak oleh konteks database.
Di aplikasi desktop, perubahan status biasanya diatur secara otomatis. Entitas dibaca, perubahan dilakukan, dan status entitas secara otomatis diubah menjadi Modified
. SaveChanges
Panggilan menghasilkan pernyataan PEMBARUAN SQL yang hanya memperbarui properti yang diubah.
Di aplikasi web, DbContext
yang membaca entitas dan menampilkan data dibuang setelah halaman dirender. Ketika metode halaman OnPostAsync
dipanggil, permintaan web baru dibuat dan dengan instans baru dari DbContext
. Pembacaan ulang entitas dalam konteks baru mensimulasikan pemrosesan desktop.
Memperbarui halaman Hapus
Di bagian ini, Anda menerapkan pesan kesalahan kustom saat panggilan gagal SaveChanges
.
Ganti kode dengan Pages/Students/Delete.cshtml.cs
kode berikut. Perubahan disorot (selain pembersihan using
pernyataan).
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 });
}
}
}
}
Kode sebelumnya menambahkan parameter saveChangesError
opsional ke OnGetAsync
tanda tangan metode. saveChangesError
menunjukkan apakah metode dipanggil setelah kegagalan untuk menghapus objek siswa. Operasi penghapusan mungkin gagal karena masalah jaringan sementara. Kesalahan jaringan sementara lebih mungkin terjadi ketika database berada di cloud. Parameter saveChangesError
salah ketika halaman OnGetAsync
Hapus dipanggil dari UI. Ketika OnGetAsync
dipanggil oleh OnPostAsync
(karena operasi penghapusan gagal), saveChangesError
parameter benar.
Metode mengambil OnPostAsync
entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted
. Ketika SaveChanges
dipanggil, perintah SQL DELETE dihasilkan. Jika Remove
gagal:
- Pengecualian database tertangkap.
- Metode Hapus halaman
OnGetAsync
dipanggil dengansaveChangesError=true
.
Tambahkan pesan kesalahan ke Halaman Hapus 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>
Jalankan aplikasi dan hapus siswa untuk menguji halaman Hapus.
Langkah berikutnya
ASP.NET Core