Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Varning
Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .
Viktigt!
Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.
För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .
Av Tom Dykstra, Jeremy Likness och Jon P Smith
Contoso University-webbappen visar hur du skapar webbappar för Razor Pages med hjälp av EF Core och Visual Studio. Information om självstudieserien finns i den första självstudien.
Om du stöter på problem som du inte kan lösa, ladda ner den färdiga appen och jämför den koden med den du skapade när du följde handledningen.
I den här självstudien granskas och anpassas crud-koden (skapa, läsa, uppdatera, ta bort).
Ingen lagringsplats
Vissa utvecklare använder ett mönster för tjänstlager eller lagringsplats för att skapa ett abstraktionslager mellan användargränssnittet (Razor sidor) och dataåtkomstskiktet. Den här handledningen gör inte det. För att minimera komplexiteten och hålla självstudiekursen fokuserad på EF Core:EF Core läggs kod till direkt i sidmodellklasserna.
Uppdatera informationssidan
Den kod som är kodad för studentsidorna innehåller inte registreringsdata. I det här avsnittet läggs registreringar till på Details sidan.
Läsa inskrivningar
Om du vill visa en elevs registreringsdata på sidan måste registreringsdata läsas. Den genererade koden i Pages/Students/Details.cshtml.cs läser endast Student data, inte 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 Ersätt metoden med följande kod för att läsa registreringsdata för den valda eleven. Ändringarna är markerade.
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();
}
Metoderna Include och ThenInclude gör att kontexten läser in navigeringsegenskapen Student.Enrollments och i varje registrering navigeringsegenskapen Enrollment.Course . Dessa metoder granskas i detalj i självstudien Läs relaterade data .
Metoden AsNoTracking förbättrar prestanda i scenarier där de entiteter som returneras inte uppdateras i den aktuella kontexten.
AsNoTracking beskrivs senare i den här handledningen.
Visa registreringar
Ersätt koden i Pages/Students/Details.cshtml med följande kod för att visa en lista över registreringar. Ändringarna är markerade.
@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>
Föregående kod loopar genom entiteterna i navigeringsegenskapen Enrollments . För varje registrering visas kurstiteln och betyget. Kurstiteln hämtas från entiteten Course som lagras i navigeringsegenskapen Course för entiteten Registreringar.
Kör appen, välj fliken Studenter och klicka på länken Information för en elev. Listan över kurser och betyg för den valda eleven visas.
Sätt att läsa en entitet
Den genererade koden använder FirstOrDefaultAsync för att läsa en entitet. Den här metoden returnerar null om inget hittas. annars returneras den första raden som uppfyller frågefiltervillkoren.
FirstOrDefaultAsync är i allmänhet ett bättre val än följande alternativ:
-
SingleOrDefaultAsync – Genererar ett undantag om det finns fler än en entitet som uppfyller frågefiltret. För att avgöra om mer än en rad kan returneras av frågan
SingleOrDefaultAsyncförsöker du hämta flera rader. Det här extra arbetet är onödigt om frågan bara kan returnera en entitet, som när den söker på en unik nyckel. -
FindAsync – Söker efter en entitet med primärnyckeln (PK). Om en entitet med PK spåras av kontexten returneras den utan en begäran till databasen. Den här metoden är optimerad för att slå upp en enda entitet, men du kan inte anropa
IncludemedFindAsync. Så om relaterade data behövs ärFirstOrDefaultAsyncdet bättre valet.
Routningsdata vs. frågesträng
URL:en för sidan Information är https://localhost:<port>/Students/Details?id=1. Entitetens primära nyckelvärde finns i frågesträngen. Vissa utvecklare föredrar att skicka nyckelvärdet i routningsdata: https://localhost:<port>/Students/Details/1. Mer information finns i Uppdatera den genererade koden.
Uppdatera sidan Skapa
Den genererade OnPostAsync koden för Skapa-sidan är sårbar för överpublicering.
OnPostAsync Ersätt metoden i Pages/Students/Create.cshtml.cs med följande kod.
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
Föregående kod skapar ett Student-objekt och använder sedan publicerade formulärfält för att uppdatera studentobjektets egenskaper. Metoden TryUpdateModelAsync :
- Använder de bokförda formulärvärdena från PageContext-egenskapen i PageModel.
- Uppdaterar endast de egenskaper som anges (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate). - Söker efter formulärfält med prefixet "student". Till exempel
Student.FirstMidName. Det är inte skiftlägeskänsligt. - Använder modellbindningssystemet för att konvertera formulärvärden från strängar till typerna
Studenti modellen. Till exempel konverterasEnrollmentDatetillDateTime.
Kör appen och skapa en studententitet för att testa sidan Skapa.
Överpublicering
Att använda TryUpdateModel för att uppdatera fält med publicerade värden är en säkerhetsmetod eftersom det förhindrar överpublicering. Anta till exempel att studententiteten innehåller en Secret egenskap som den här webbsidan inte ska uppdatera eller lägga till:
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; }
}
Även om appen inte har något Secret fält på sidan skapa eller uppdatera Razor kan en hackare ange Secret värdet genom överpublicering. En hackare kan använda ett verktyg som Fiddler eller skriva javascript för att publicera ett Secret formulärvärde. Den ursprungliga koden begränsar inte de fält som modellbindaren använder när den skapar en Student-instans.
Det värde som hackaren har angett för Secret formulärfältet uppdateras i databasen. Följande bild visar Fiddler-verktyget som lägger till Secret-fältet, med värdet "OverPost", till de postade formulärvärdena.
Värdet "OverPost" har framgångsrikt lagts till i egenskapen Secret för den infogade raden. Det händer även om appdesignern aldrig avsåg Secret att egenskapen skulle anges på sidan Skapa.
Visa modell
Visningsmodellerna är ett alternativt sätt att förhindra överpublicering.
Programmodellen kallas ofta för domänmodellen. Domänmodellen innehåller vanligtvis alla egenskaper som krävs av motsvarande entitet i databasen. Vymodellen innehåller endast de egenskaper som behövs för användargränssnittssidan, till exempel sidan Skapa.
Förutom vymodellen använder vissa appar en bindningsmodell eller indatamodell för att skicka data mellan sidmodellklassen Razor Pages och webbläsaren.
Överväg följande StudentVM vymodell:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Följande kod använder StudentVM vymodellen för att skapa en ny elev:
[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");
}
Metoden SetValues anger värdena för det här objektet genom att läsa värden från ett annat PropertyValues objekt.
SetValues använder matchning av egenskapsnamn. Typ av vymodell:
- Behöver inte vara relaterad till modelltypen.
- Måste ha egenskaper som matchar.
Använda StudentVM kräver att sidan Skapa använder StudentVM istället för 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");}
}
Uppdatera sidan Redigera
I Pages/Students/Edit.cshtml.csersätter du OnGetAsync metoderna och OnPostAsync med följande kod.
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();
}
Kodändringarna liknar sidan Skapa med några undantag:
-
FirstOrDefaultAsynchar ersatts med FindAsync. När du inte behöver inkludera relaterade data ärFindAsyncdet mer effektivt. -
OnPostAsynchar parameternid. - Den aktuella eleven hämtas från databasen i stället för att skapa en tom elev.
Kör appen och testa den genom att skapa och redigera en elev.
Entitetstillstånd
Databaskontexten håller reda på om entiteter i minnet är synkroniserade med motsvarande rader i databasen. Den här spårningsinformationen avgör vad som händer när SaveChangesAsync anropas. När till exempel en ny entitet skickas till AddAsync metoden anges den entitetens tillstånd till Added. När SaveChangesAsync anropas utfärdar databaskontexten ett SQL-kommando INSERT .
En entitet kan vara i något av följande tillstånd:
Added: Entiteten finns ännu inte i databasen. MetodenSaveChangesutfärdar enINSERT-instruktion.Unchanged: Inga ändringar behöver sparas med den här entiteten. En entitet har den här statusen när den läse från databasen.Modified: Vissa eller alla entitets egenskapsvärden har ändrats. MetodenSaveChangesutfärdar enUPDATE-instruktion.Deleted: Entiteten har markerats för borttagning. MetodenSaveChangesutfärdar enDELETE-instruktion.Detached: Entiteten spåras inte av databaskontexten.
I en skrivbordsapp ställs tillståndsändringar vanligtvis in automatiskt. En entitet läses, ändringar görs och entitetens tillstånd ändras automatiskt till Modified. Anrop SaveChanges genererar en SQL-instruktion UPDATE som endast uppdaterar de ändrade egenskaperna.
I en webbapp avlägsnas den DbContext som läser en entitet och visar data när sidan renderas. När en sidas OnPostAsync-metod anropas, görs en ny webbförfrågan och en ny instans av DbContext skapas. Omläsning av entiteten i den nya kontexten simulerar skrivbordsbearbetning.
Uppdatera sidan Ta bort
I det här avsnittet implementeras ett anpassat felmeddelande när anropet till SaveChanges misslyckas.
Ersätt koden i Pages/Students/Delete.cshtml.cs med följande kod:
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 });
}
}
}
}
Föregående kod:
- Lägger till loggning.
- Lägger till den valfria parametern
saveChangesErrori metodsignaturenOnGetAsync.saveChangesErroranger om metoden anropades efter ett misslyckande med att ta bort studentobjektet.
Borttagningsåtgärden kan misslyckas på grund av tillfälliga nätverksproblem. Tillfälliga nätverksfel är mer sannolika när databasen finns i molnet. Parametern saveChangesError är false när sidan OnGetAsync Ta bort anropas från användargränssnittet. När OnGetAsync anropas av OnPostAsync eftersom borttagningsåtgärden misslyckades är saveChangesErrorparametern true .
Metoden OnPostAsync hämtar den valda entiteten och anropar sedan metoden Ta bort för att ange entitetens status till Deleted. När SaveChanges anropas genereras ett SQL-kommando DELETE . Om Remove misslyckas:
- Databasfelet fångas.
- Metoden Ta bort sidor
OnGetAsyncanropas medsaveChangesError=true.
Lägg till ett felmeddelande i 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>
Kör appen och ta bort en elev för att testa sidan Ta bort.
Nästa steg
I den här självstudien granskas och anpassas crud-koden (skapa, läsa, uppdatera, ta bort).
Ingen lagringsplats
Vissa utvecklare använder ett mönster för tjänstlager eller lagringsplats för att skapa ett abstraktionslager mellan användargränssnittet (Razor sidor) och dataåtkomstskiktet. Den här handledningen gör inte det. För att minimera komplexiteten och hålla självstudiekursen fokuserad på EF Core:EF Core läggs kod till direkt i sidmodellklasserna.
Uppdatera informationssidan
Den kod som är kodad för studentsidorna innehåller inte registreringsdata. I det här avsnittet läggs registreringar till på Details sidan.
Läsa inskrivningar
Om du vill visa en elevs registreringsdata på sidan måste registreringsdata läsas. Den genererade koden i Pages/Students/Details.cshtml.cs läser endast Student data, inte 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 Ersätt metoden med följande kod för att läsa registreringsdata för den valda eleven. Ändringarna är markerade.
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();
}
Metoderna Include och ThenInclude gör att kontexten läser in navigeringsegenskapen Student.Enrollments och i varje registrering navigeringsegenskapen Enrollment.Course . Dessa metoder granskas i detalj i självstudien Läs relaterade data .
Metoden AsNoTracking förbättrar prestanda i scenarier där de entiteter som returneras inte uppdateras i den aktuella kontexten.
AsNoTracking beskrivs senare i den här handledningen.
Visa registreringar
Ersätt koden i Pages/Students/Details.cshtml med följande kod för att visa en lista över registreringar. Ändringarna är markerade.
@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>
Föregående kod loopar genom entiteterna i navigeringsegenskapen Enrollments . För varje registrering visas kurstiteln och betyget. Kurstiteln hämtas från entiteten Course som lagras i navigeringsegenskapen Course för entiteten Registreringar.
Kör appen, välj fliken Studenter och klicka på länken Information för en elev. Listan över kurser och betyg för den valda eleven visas.
Sätt att läsa en entitet
Den genererade koden använder FirstOrDefaultAsync för att läsa en entitet. Den här metoden returnerar null om inget hittas. annars returneras den första raden som uppfyller frågefiltervillkoren.
FirstOrDefaultAsync är i allmänhet ett bättre val än följande alternativ:
-
SingleOrDefaultAsync – Genererar ett undantag om det finns fler än en entitet som uppfyller frågefiltret. För att avgöra om mer än en rad kan returneras av frågan
SingleOrDefaultAsyncförsöker du hämta flera rader. Det här extra arbetet är onödigt om frågan bara kan returnera en entitet, som när den söker på en unik nyckel. -
FindAsync – Söker efter en entitet med primärnyckeln (PK). Om en entitet med PK spåras av kontexten returneras den utan en begäran till databasen. Den här metoden är optimerad för att slå upp en enda entitet, men du kan inte anropa
IncludemedFindAsync. Så om relaterade data behövs ärFirstOrDefaultAsyncdet bättre valet.
Routningsdata vs. frågesträng
URL:en för sidan Information är https://localhost:<port>/Students/Details?id=1. Entitetens primära nyckelvärde finns i frågesträngen. Vissa utvecklare föredrar att skicka nyckelvärdet i routningsdata: https://localhost:<port>/Students/Details/1. Mer information finns i Uppdatera den genererade koden.
Uppdatera sidan Skapa
Den genererade OnPostAsync koden för Skapa-sidan är sårbar för överpublicering.
OnPostAsync Ersätt metoden i Pages/Students/Create.cshtml.cs med följande kod.
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
Föregående kod skapar ett Student-objekt och använder sedan publicerade formulärfält för att uppdatera studentobjektets egenskaper. Metoden TryUpdateModelAsync :
- Använder de bokförda formulärvärdena från PageContext-egenskapen i PageModel.
- Uppdaterar endast de egenskaper som anges (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate). - Söker efter formulärfält med prefixet "student". Till exempel
Student.FirstMidName. Det är inte skiftlägeskänsligt. - Använder modellbindningssystemet för att konvertera formulärvärden från strängar till typerna
Studenti modellen. Till exempel konverterasEnrollmentDatetillDateTime.
Kör appen och skapa en studententitet för att testa sidan Skapa.
Överpublicering
Att använda TryUpdateModel för att uppdatera fält med publicerade värden är en säkerhetsmetod eftersom det förhindrar överpublicering. Anta till exempel att studententiteten innehåller en Secret egenskap som den här webbsidan inte ska uppdatera eller lägga till:
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; }
}
Även om appen inte har något Secret fält på sidan skapa eller uppdatera Razor kan en hackare ange Secret värdet genom överpublicering. En hackare kan använda ett verktyg som Fiddler eller skriva javascript för att publicera ett Secret formulärvärde. Den ursprungliga koden begränsar inte de fält som modellbindaren använder när den skapar en Student-instans.
Det värde som hackaren har angett för Secret formulärfältet uppdateras i databasen. Följande bild visar Fiddler-verktyget som lägger till Secret-fältet, med värdet "OverPost", till de postade formulärvärdena.
Värdet "OverPost" har framgångsrikt lagts till i egenskapen Secret för den infogade raden. Det händer även om appdesignern aldrig avsåg Secret att egenskapen skulle anges på sidan Skapa.
Visa modell
Visningsmodellerna är ett alternativt sätt att förhindra överpublicering.
Programmodellen kallas ofta för domänmodellen. Domänmodellen innehåller vanligtvis alla egenskaper som krävs av motsvarande entitet i databasen. Vymodellen innehåller endast de egenskaper som behövs för användargränssnittssidan, till exempel sidan Skapa.
Förutom vymodellen använder vissa appar en bindningsmodell eller indatamodell för att skicka data mellan sidmodellklassen Razor Pages och webbläsaren.
Överväg följande StudentVM vymodell:
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Följande kod använder StudentVM vymodellen för att skapa en ny elev:
[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");
}
Metoden SetValues anger värdena för det här objektet genom att läsa värden från ett annat PropertyValues objekt.
SetValues använder matchning av egenskapsnamn. Typ av vymodell:
- Behöver inte vara relaterad till modelltypen.
- Måste ha egenskaper som matchar.
Använda StudentVM kräver att sidan Skapa använder StudentVM istället för 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");}
}
Uppdatera sidan Redigera
I Pages/Students/Edit.cshtml.csersätter du OnGetAsync metoderna och OnPostAsync med följande kod.
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();
}
Kodändringarna liknar sidan Skapa med några undantag:
-
FirstOrDefaultAsynchar ersatts med FindAsync. När du inte behöver inkludera relaterade data ärFindAsyncdet mer effektivt. -
OnPostAsynchar parameternid. - Den aktuella eleven hämtas från databasen i stället för att skapa en tom elev.
Kör appen och testa den genom att skapa och redigera en elev.
Entitetstillstånd
Databaskontexten håller reda på om entiteter i minnet är synkroniserade med motsvarande rader i databasen. Den här spårningsinformationen avgör vad som händer när SaveChangesAsync anropas. När till exempel en ny entitet skickas till AddAsync metoden anges den entitetens tillstånd till Added. När SaveChangesAsync anropas utfärdar databaskontexten ett SQL-kommando INSERT .
En entitet kan vara i något av följande tillstånd:
Added: Entiteten finns ännu inte i databasen. MetodenSaveChangesutfärdar enINSERT-instruktion.Unchanged: Inga ändringar behöver sparas med den här entiteten. En entitet har den här statusen när den läse från databasen.Modified: Vissa eller alla entitets egenskapsvärden har ändrats. MetodenSaveChangesutfärdar enUPDATE-instruktion.Deleted: Entiteten har markerats för borttagning. MetodenSaveChangesutfärdar enDELETE-instruktion.Detached: Entiteten spåras inte av databaskontexten.
I en skrivbordsapp ställs tillståndsändringar vanligtvis in automatiskt. En entitet läses, ändringar görs och entitetens tillstånd ändras automatiskt till Modified. Anrop SaveChanges genererar en SQL-instruktion UPDATE som endast uppdaterar de ändrade egenskaperna.
I en webbapp avlägsnas den DbContext som läser en entitet och visar data när sidan renderas. När en sidas OnPostAsync-metod anropas, görs en ny webbförfrågan och en ny instans av DbContext skapas. Omläsning av entiteten i den nya kontexten simulerar skrivbordsbearbetning.
Uppdatera sidan Ta bort
I det här avsnittet implementeras ett anpassat felmeddelande när anropet till SaveChanges misslyckas.
Ersätt koden i Pages/Students/Delete.cshtml.cs med följande kod:
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 });
}
}
}
}
Föregående kod:
- Lägger till loggning.
- Lägger till den valfria parametern
saveChangesErrori metodsignaturenOnGetAsync.saveChangesErroranger om metoden anropades efter ett misslyckande med att ta bort studentobjektet.
Borttagningsåtgärden kan misslyckas på grund av tillfälliga nätverksproblem. Tillfälliga nätverksfel är mer sannolika när databasen finns i molnet. Parametern saveChangesError är false när sidan OnGetAsync Ta bort anropas från användargränssnittet. När OnGetAsync anropas av OnPostAsync eftersom borttagningsåtgärden misslyckades är saveChangesErrorparametern true .
Metoden OnPostAsync hämtar den valda entiteten och anropar sedan metoden Ta bort för att ange entitetens status till Deleted. När SaveChanges anropas genereras ett SQL-kommando DELETE . Om Remove misslyckas:
- Databasfelet fångas.
- Metoden Ta bort sidor
OnGetAsyncanropas medsaveChangesError=true.
Lägg till ett felmeddelande i 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>
Kör appen och ta bort en elev för att testa sidan Ta bort.
Nästa steg
I den här självstudien granskas och anpassas crud-koden (skapa, läsa, uppdatera, ta bort).
Ingen lagringsplats
Vissa utvecklare använder ett mönster för tjänstlager eller lagringsplats för att skapa ett abstraktionslager mellan användargränssnittet (Razor sidor) och dataåtkomstskiktet. Den här handledningen gör inte det. För att minimera komplexiteten och hålla självstudiekursen fokuserad på EF Core:EF Core läggs kod till direkt i sidmodellklasserna.
Uppdatera informationssidan
Den kod som är kodad för studentsidorna innehåller inte registreringsdata. I det här avsnittet läggs registreringar till på detaljsidan.
Läsa inskrivningar
Om du vill visa en elevs registreringsdata på sidan måste registreringsdata läsas. Den strukturerade koden i Pages/Students/Details.cshtml.cs läser endast Studentdata, utan Inskrivningsdata.
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 Ersätt metoden med följande kod för att läsa registreringsdata för den valda eleven. Ändringarna är markerade.
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();
}
Metoderna Include och ThenInclude gör att kontexten läser in navigeringsegenskapen Student.Enrollments och i varje registrering navigeringsegenskapen Enrollment.Course . Dessa metoder granskas i detalj i handledningen läsrelaterade data.
Metoden AsNoTracking förbättrar prestanda i scenarier där de entiteter som returneras inte uppdateras i den aktuella kontexten.
AsNoTracking beskrivs senare i den här handledningen.
Visa registreringar
Ersätt koden i Pages/Students/Details.cshtml med följande kod för att visa en lista över registreringar. Ändringarna är markerade.
@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>
Föregående kod loopar genom entiteterna i navigeringsegenskapen Enrollments . För varje registrering visas kurstiteln och betyget. Kurstiteln hämtas från entiteten Kurs som lagras i navigeringsegenskapen Course för entiteten Registreringar.
Kör appen, välj fliken Studenter och klicka på länken Information för en elev. Listan över kurser och betyg för den valda eleven visas.
Sätt att läsa en entitet
Den genererade koden använder FirstOrDefaultAsync för att läsa en entitet. Den här metoden returnerar null om inget hittas. annars returneras den första raden som uppfyller frågefiltervillkoren.
FirstOrDefaultAsync är i allmänhet ett bättre val än följande alternativ:
-
SingleOrDefaultAsync – Genererar ett undantag om det finns fler än en entitet som uppfyller frågefiltret. För att avgöra om mer än en rad kan returneras av frågan
SingleOrDefaultAsyncförsöker du hämta flera rader. Det här extra arbetet är onödigt om frågan bara kan returnera en entitet, som när den söker på en unik nyckel. -
FindAsync – Söker efter en entitet med primärnyckeln (PK). Om en entitet med PK spåras av kontexten returneras den utan en begäran till databasen. Den här metoden är optimerad för att slå upp en enda entitet, men du kan inte anropa
IncludemedFindAsync. Så om relaterade data behövs ärFirstOrDefaultAsyncdet bättre valet.
Routningsdata vs. frågesträng
URL:en för sidan Information är https://localhost:<port>/Students/Details?id=1. Entitetens primära nyckelvärde finns i frågesträngen. Vissa utvecklare föredrar att skicka nyckelvärdet i routningsdata: https://localhost:<port>/Students/Details/1. Mer information finns i Uppdatera den genererade koden.
Uppdatera sidan Skapa
Den genererade OnPostAsync koden för Skapa-sidan är sårbar för överpublicering.
OnPostAsync Ersätt metoden i Pages/Students/Create.cshtml.cs med följande kod.
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
Föregående kod skapar ett Student-objekt och använder sedan publicerade formulärfält för att uppdatera studentobjektets egenskaper. Metoden TryUpdateModelAsync :
- Använder de bokförda formulärvärdena från PageContext-egenskapen i PageModel.
- Uppdaterar endast de egenskaper som anges (
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate). - Söker efter formulärfält med prefixet "student". Till exempel
Student.FirstMidName. Det är inte skiftlägeskänsligt. - Använder modellbindningssystemet för att konvertera formulärvärden från strängar till typerna
Studenti modellen. Till exempelEnrollmentDatemåste konverteras till DateTime.
Kör appen och skapa en studententitet för att testa sidan Skapa.
Överpublicering
Att använda TryUpdateModel för att uppdatera fält med publicerade värden är en säkerhetsmetod eftersom det förhindrar överpublicering. Anta till exempel att studententiteten innehåller en Secret egenskap som den här webbsidan inte ska uppdatera eller lägga till:
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; }
}
Även om appen inte har något Secret fält på sidan skapa eller uppdatera Razor kan en hackare ange Secret värdet genom överpublicering. En hackare kan använda ett verktyg som Fiddler eller skriva javascript för att publicera ett Secret formulärvärde. Den ursprungliga koden begränsar inte de fält som modellbindaren använder när den skapar en Student-instans.
Det värde som hackaren har angett för Secret formulärfältet uppdateras i databasen. Följande bild visar Fiddler-verktyget som lägger till fältet Secret (med värdet "OverPost") i de skickade formulärvärdena.
Värdet "OverPost" har framgångsrikt lagts till i egenskapen Secret för den infogade raden. Det händer även om appdesignern aldrig avsåg Secret att egenskapen skulle anges på sidan Skapa.
Visa modell
Visningsmodellerna är ett alternativt sätt att förhindra överpublicering.
Programmodellen kallas ofta för domänmodellen. Domänmodellen innehåller vanligtvis alla egenskaper som krävs av motsvarande entitet i databasen. Vymodellen innehåller endast de egenskaper som behövs för användargränssnittet som den används för (till exempel sidan Skapa).
Förutom vymodellen använder vissa appar en bindningsmodell eller indatamodell för att skicka data mellan sidmodellklassen Razor Pages och webbläsaren.
Överväg följande Student vymodell:
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; }
}
}
Följande kod använder StudentVM vymodellen för att skapa en ny elev:
[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");
}
Metoden SetValues anger värdena för det här objektet genom att läsa värden från ett annat PropertyValues objekt.
SetValues använder matchning av egenskapsnamn. Vymodelltypen behöver inte vara relaterad till modelltypen, den behöver bara ha egenskaper som matchar.
Användning StudentVM kräver att Create.cshtml uppdateras för att använda StudentVM i stället Studentför .
Uppdatera sidan Redigera
I Pages/Students/Edit.cshtml.csersätter du OnGetAsync metoderna och OnPostAsync med följande kod.
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();
}
Kodändringarna liknar sidan Skapa med några undantag:
-
FirstOrDefaultAsynchar ersatts med FindAsync. När inkluderade relaterade data inte behövsFindAsyncär effektivare. -
OnPostAsynchar parameternid. - Den aktuella eleven hämtas från databasen i stället för att skapa en tom elev.
Kör appen och testa den genom att skapa och redigera en elev.
Entitetstillstånd
Databaskontexten håller reda på om entiteter i minnet är synkroniserade med motsvarande rader i databasen. Den här spårningsinformationen avgör vad som händer när SaveChangesAsync anropas. När till exempel en ny entitet skickas till AddAsync metoden anges den entitetens tillstånd till Added. När SaveChangesAsync anropas utfärdar databaskontexten ett SQL INSERT-kommando.
En entitet kan vara i något av följande tillstånd:
Added: Entiteten finns ännu inte i databasen. MetodenSaveChangesutfärdar en INSERT-instruktion.Unchanged: Inga ändringar behöver sparas med den här entiteten. En entitet har den här statusen när den läse från databasen.Modified: Vissa eller alla entitets egenskapsvärden har ändrats. MetodenSaveChangesutfärdar en UPDATE-instruktion.Deleted: Entiteten har markerats för borttagning. MetodenSaveChangesutfärdar en DELETE-instruktion.Detached: Entiteten spåras inte av databaskontexten.
I en skrivbordsapp ställs tillståndsändringar vanligtvis in automatiskt. En entitet läses, ändringar görs och entitetens tillstånd ändras automatiskt till Modified. Anrop SaveChanges genererar en SQL UPDATE-instruktion som endast uppdaterar de ändrade egenskaperna.
I en webbapp avlägsnas den DbContext som läser en entitet och visar data när sidan renderas. När en sidas OnPostAsync-metod anropas, görs en ny webbförfrågan och en ny instans av DbContext skapas. Omläsning av entiteten i den nya kontexten simulerar skrivbordsbearbetning.
Uppdatera sidan Ta bort
I det här avsnittet implementerar du ett anpassat felmeddelande när anropet till SaveChanges misslyckas.
Ersätt koden i Pages/Students/Delete.cshtml.cs med följande kod. Ändringarna är markerade (förutom rensning av using satser).
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 });
}
}
}
}
Föregående kod lägger till den valfria parametern saveChangesError i metodsignaturen OnGetAsync .
saveChangesError anger om metoden anropades efter ett misslyckande med att ta bort studentobjektet. Borttagningsåtgärden kan misslyckas på grund av tillfälliga nätverksproblem. Tillfälliga nätverksfel är mer sannolika när databasen finns i molnet. Parametern saveChangesError är falsk när sidan Radera OnGetAsync anropas från användargränssnittet. När OnGetAsync anropas av OnPostAsync (eftersom borttagningsåtgärden misslyckades) är parametern saveChangesError sann.
Metoden OnPostAsync hämtar den valda entiteten och anropar sedan metoden Ta bort för att ange entitetens status till Deleted. När SaveChanges anropas genereras ett SQL DELETE-kommando. Om Remove misslyckas:
- Databasfelet fångas.
- Ta bort-sidans
OnGetAsyncmetod anropas medsaveChangesError=true.
Lägg till ett felmeddelande på sidan Ta bort 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>
Kör appen och ta bort en elev för att testa sidan Ta bort.
Nästa steg
ASP.NET Core