Sdílet prostřednictvím


Část 2, Razor Stránky s využitím EF Core v ASP.NET Core – CRUD

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi tohoto článku pro .NET 9 najdete zde.

Varování

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální vydání najdete v článku o verzi .NET 9.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální vydání tohoto článku najdete ve verzi .NET 9.

Tom Dykstra, Jeremy Likness a Jon P Smith

Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace typu Pages pomocí EF Core a sady Visual Studio. Informace o sérii kurzů najdete v prvním kurzu.

Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili podle kurzu.

V tomto kurzu se reviduje a přizpůsobuje základní kód CRUD (vytvoření, čtení, aktualizace, odstranění).

Žádné úložiště

Někteří vývojáři používají model vrstvy služby nebo úložiště k vytvoření abstraktní vrstvy mezi uživatelským rozhraním (Razor stránkami) a vrstvou přístupu k datům. Tento kurz to nedělá. Aby se minimalizovala složitost a kurz se zaměřil na EF Core, EF Core přidá se kód přímo do tříd modelu stránky.

Aktualizace stránky Podrobnosti

Vygenerovaný kód pro stránky Studentů neobsahuje data registrace. V této části jsou přihlášky přidány na stránku Details.

Čtení přihlášek

Pokud chcete na stránce zobrazit data o zápisu studenta, musí se načíst data o registraci. Vygenerovaný kód v Pages/Students/Details.cshtml.cs čte pouze Student data, bez Enrollment dat.

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();
}

Nahraďte metodu OnGetAsync následujícím kódem pro čtení dat zápisu pro vybraného studenta. Změny jsou zvýrazněné.

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();
}

Metody IncludeThenInclude způsobují, že kontext má načíst Student.Enrollments navigační vlastnost a v rámci každé registrace také Enrollment.Course navigační vlastnost. Tyto metody jsou podrobně prozkoumány v kurzu čtení souvisejících dat .

Metoda AsNoTracking zlepšuje výkon ve scénářích, kdy se vrácené entity neaktualizují v aktuálním kontextu. AsNoTracking je popsáno dále v tomto kurzu.

Zobrazení registrací

Nahraďte kód Pages/Students/Details.cshtml následujícím kódem, aby se zobrazil seznam registrací. Změny jsou zvýrazněné.

@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>

Předchozí smyčka kódu prochází entitami v Enrollments navigační vlastnosti. Pro každou registraci se zobrazí název kurzu a známka. Název kurzu se načte z Course entity, která je uložená v Course navigační vlastnosti entity Enrollments.

Spusťte aplikaci, vyberte kartu Studenti a klikněte na odkaz Podrobnosti pro studenta. Zobrazí se seznam kurzů a známek pro vybraného studenta.

Způsoby čtení jedné entity

Vygenerovaný kód používá metodu FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud se nic nenajde; v opačném případě vrátí první řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je obecně lepší volbou než následující alternativy:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která splňuje filtr dotazu. Chcete-li zjistit, zda dotaz může vrátit více než jeden řádek, SingleOrDefaultAsync pokusí se načíst více řádků. Tato nadbytečná práce není nutná, pokud dotaz může vrátit pouze jednu entitu, jako když hledá jedinečný klíč.
  • FindAsync – Najde entitu s primárním klíčem (PK). Pokud je entita s PK sledována kontextem, vrátí se bez požadavku na databázi. Tato metoda je optimalizovaná pro vyhledání jedné entity, ale nemůžete volat Include s FindAsync. Takže pokud potřebujete související data, FirstOrDefaultAsync je lepší volbou.

Data trasy vs. řetězec dotazu

Adresa URL stránky Podrobnosti je https://localhost:<port>/Students/Details?id=1. Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři raději předávají hodnotu klíče ve směrovacích datech: https://localhost:<port>/Students/Details/1. Další informace najdete v tématu Aktualizace vygenerovaného kódu.

Aktualizace stránky Vytvořit

Vygenerovaný OnPostAsync kód pro stránku Vytvořit je zranitelný vůči nadměrnému umístění. Nahraďte metodu OnPostAsync v Pages/Students/Create.cshtml.cs následujícím kódem.

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

Předchozí kód vytvoří objekt Student a pak použije pole publikovaného formuláře k aktualizaci vlastností objektu Student. Metoda TryUpdateModelAsync :

  • Používá hodnoty odeslaného formuláře z vlastnosti PageContext v PageModel objektu.
  • Aktualizuje pouze uvedené vlastnosti (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Vyhledá pole formuláře s předponou student. Například Student.FirstMidName. Nerozlišuje se malá a velká písmena.
  • Používá systém vazeb modelu k převodu hodnot formulářů z řetězců na typy v Student modelu. Například EnrollmentDate je převeden na DateTime.

Spusťte aplikaci a vytvořte entitu studenta, která otestuje stránku Vytvořit.

Nadbytečné zveřejňování

Použití TryUpdateModel k aktualizaci polí s publikovanými hodnotami je osvědčeným postupem zabezpečení, protože brání nadměrnému publikování. Předpokládejme například, že entita Student obsahuje Secret vlastnost, kterou by tato webová stránka neměla aktualizovat nebo přidat:

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; }
}

I když na stránce pro vytvoření nebo aktualizaci aplikace neexistuje pole Secret, hacker může nastavit hodnotu Secret pomocí nadměrného odesílání. Hacker může použít nástroj, jako je Fiddler nebo napsat nějaký JavaScript, k publikování Secret hodnoty formuláře. Původní kód neomezuje pole, která binder modelu používá při vytváření instance Student.

V databázi se aktualizuje jakákoli hodnota, kterou hacker zadal pro Secret pole formuláře. Následující obrázek ukazuje nástroj Fiddler, který přidává Secret pole s hodnotou OverPost k publikovaným hodnotám formuláře.

Fiddler přidává pole Tajný kód

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází, i když návrhář aplikace nikdy nezamýšlel, aby byla vlastnost Secret nastavena prostřednictvím stránky Vytvoření.

Zobrazit model

Modely zobrazení poskytují alternativní způsob, jak zabránit nadměrnému vkládání dat.

Aplikační model se často nazývá doménový model. Doménový model obvykle obsahuje všechny vlastnosti vyžadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti potřebné pro stránku uživatelského rozhraní, například vytvořit stránku.

Kromě modelu zobrazení používají některé aplikace k předávání dat mezi Razor třídou modelu stránky Stránky a prohlížečem model vazby nebo vstupní model.

Představte si následující StudentVM model zobrazení:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Následující kód používá StudentVM model zobrazení k vytvoření nového studenta:

[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");
}

SetValues metoda nastaví hodnoty tohoto objektu čtením hodnot z jiného PropertyValues objektu. SetValues používá porovnávání názvů vlastností. Typ modelu zobrazení:

  • Nemusí souviset s typem modelu.
  • Musí mít vlastnosti, které odpovídají.

Použití StudentVM vyžaduje, aby se použila StudentVM na stránce Vytvořit místo 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");}
}

Aktualizace stránky Editace

V Pages/Students/Edit.cshtml.cspříkazu nahraďte OnGetAsync a OnPostAsync metody následujícím kódem.

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();
}

Změny kódu se podobají stránce Vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byla nahrazena znakem FindAsync. Pokud nepotřebujete zahrnout související data, FindAsync je efektivnější.
  • OnPostAsync má parametr id.
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji vytvořením a úpravou studenta.

Stavy entit

Kontext databáze sleduje, jestli se entity v paměti synchronizují s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane při volání SaveChangesAsync . Pokud je například do metody předána AddAsync nová entita, stav dané entity je nastaven na Added. Když se zavolá SaveChangesAsync, kontext databáze vydá příkaz SQL INSERT.

Entita může být v jednom z následujících stavů:

  • Added: Entita ještě v databázi neexistuje. Metoda SaveChanges vydá INSERT příkaz.

  • Unchanged: U této entity není nutné ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Některé nebo všechny hodnoty vlastností entity byly změněny. Metoda SaveChanges vydá UPDATE příkaz.

  • Deleted: Entita byla označena k odstranění. Metoda SaveChanges vydá DELETE příkaz.

  • Detached: Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. Entita je přečtená, provede se změny a stav entity se automaticky změní na Modified. Volání SaveChanges generuje příkaz SQL UPDATE , který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci, DbContext která čte entitu a zobrazí data, se odstraní po vykreslení stránky. Při zavolání metody stránky OnPostAsync se vytvoří nový webový požadavek a vytvoří se nová instance DbContext. Přečtení entity znovu v novém kontextu simuluje zpracování na pracovní ploše.

Aktualizovat stránku Odstranit

Při selhání volání SaveChanges je v této části implementována vlastní chybová zpráva.

Nahraďte kód v souboru Pages/Students/Delete.cshtml.cs následujícím kódem:

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 });
            }
        }
    }
}

Předchozí kód:

  • Přidá protokolování.
  • Přidá volitelný parametr saveChangesError do OnGetAsync podpisu metody. saveChangesError označuje, zda byla metoda volána po neúspěšném pokusu o odstranění objektu studenta.

Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. Parametr saveChangesError je false když se stránka OnGetAsync Delete volá z uživatelského rozhraní. Pokud OnPostAsync volá OnGetAsync kvůli selhání operace odstranění, parametr saveChangesError je true.

Metoda OnPostAsync načte vybranou entitu a potom zavolá metodu Remove , která nastaví stav entity na Deleted. Při SaveChanges volání se vygeneruje příkaz SQL DELETE . Pokud Remove selže:

  • Byla zachycena výjimka databáze.
  • Metoda Delete pages OnGetAsync je volána s saveChangesError=true.

Přidejte chybovou zprávu do Pages/Students/Delete.cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Spusťte aplikaci, odstraňte studenta a otestujte tak stránku Odstranit.

Další kroky

V tomto kurzu se reviduje a přizpůsobí výchozí kód CRUD (vytvoření, čtení, aktualizace, odstranění).

Žádné úložiště

Někteří vývojáři používají model vrstvy služby nebo úložiště k vytvoření abstraktní vrstvy mezi uživatelským rozhraním (Razor stránkami) a vrstvou přístupu k datům. Tento kurz to nedělá. Aby se minimalizovala složitost a výuka byla zaměřena na EF Core, přidá se EF Core kód přímo do tříd modelu stránky.

Aktualizace stránky Podrobnosti

Strukturovaný kód pro stránky studentů neobsahuje data o zápisu. V této části se registrace přidávají na stránku Details.

Čtení registrací

Pokud chcete na stránce zobrazit data o zápisu studenta, musí se načíst data o registraci. Kód vytvořený v rámci Pages/Students/Details.cshtml.cs čte pouze Student data, bez Enrollment dat.

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();
}

Nahraďte metodu OnGetAsync následujícím kódem pro čtení dat zápisu pro vybraného studenta. Změny jsou zvýrazněné.

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();
}

Metody Include a ThenInclude způsobí, že kontext načte Student.Enrollments navigační vlastnost a v každém zápisu Enrollment.Course navigační vlastnost. Tyto metody jsou podrobně prozkoumány v kurzu čtení souvisejících dat .

Metoda AsNoTracking zlepšuje výkon ve scénářích, kdy se vrácené entity neaktualizují v aktuálním kontextu. AsNoTracking je popsáno dále v tomto kurzu.

Zobrazení registrací

Nahraďte kód Pages/Students/Details.cshtml následujícím kódem, aby se zobrazil seznam registrací. Změny jsou zvýrazněné.

@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>

Předchozí smyčka kódu prochází entitami v Enrollments navigační vlastnosti. Pro každou registraci se zobrazí název kurzu a známka. Název kurzu se načte z Course entity, která je uložená v Course navigační vlastnosti entity Enrollments.

Spusťte aplikaci, vyberte kartu Studenti a klikněte na odkaz Podrobnosti pro studenta. Zobrazí se seznam kurzů a známek pro vybraného studenta.

Způsoby čtení jedné entity

Vygenerovaný kód používá metodu FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud se nic nenajde; v opačném případě vrátí první řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je obecně lepší volbou než následující alternativy:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která splňuje filtr dotazu. Chcete-li zjistit, zda dotaz může vrátit více než jeden řádek, SingleOrDefaultAsync pokusí se načíst více řádků. Tato nadbytečná práce není nutná, pokud dotaz může vrátit pouze jednu entitu, jako když hledá jedinečný klíč.
  • FindAsync – Najde entitu s primárním klíčem (PK). Pokud je entita s PK sledována kontextem, vrátí se bez požadavku na databázi. Tato metoda je optimalizovaná pro vyhledání jedné entity, ale Include nelze volat s FindAsync. Takže pokud potřebujete související data, FirstOrDefaultAsync je lepší volbou.

Směrování dat vs. řetězec dotazu

Adresa URL stránky Podrobnosti je https://localhost:<port>/Students/Details?id=1. Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři raději předávají hodnotu klíče ve směrovacích datech: https://localhost:<port>/Students/Details/1. Další informace najdete v tématu Aktualizace vygenerovaného kódu.

Aktualizujte stránku vytvořit

Vygenerovaný OnPostAsync kód pro stránku Vytvořit je zranitelný vůči nadměrnému umístění. Nahraďte metodu OnPostAsync v Pages/Students/Create.cshtml.cs následujícím kódem.

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

Předchozí kód vytvoří objekt Student a pak použije pole publikovaného formuláře k aktualizaci vlastností objektu Student. Metoda TryUpdateModelAsync :

  • Používá hodnoty zadané ve formuláři z vlastnosti PageContext v PageModel.
  • Aktualizuje pouze uvedené vlastnosti (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Vyhledá pole formuláře s předponou student. Například Student.FirstMidName. Nerozlišuje se malá a velká písmena.
  • Používá systém vazeb modelu k převodu hodnot formulářů z řetězců na typy v Student modelu. Například EnrollmentDate je převeden na DateTime.

Spusťte aplikaci a vytvořte entitu studenta, která otestuje stránku Vytvořit.

Nadměrné zveřejňování

Použití TryUpdateModel k aktualizaci polí s publikovanými hodnotami je osvědčeným postupem zabezpečení, protože brání nadměrnému publikování. Předpokládejme například, že entita Student obsahuje Secret vlastnost, kterou by tato webová stránka neměla aktualizovat nebo přidat:

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; }
}

I když aplikace na stránce pro vytvoření nebo aktualizaci nemá pole Secret, hacker může nastavovat hodnotu Secret prostřednictvím nadměrného zadávání údajů. Hacker může použít nástroj, jako je Fiddler nebo napsat nějaký JavaScript, k publikování Secret hodnoty formuláře. Původní kód neomezuje pole, která binder modelu používá při vytváření instance Student.

V databázi se aktualizuje jakákoli hodnota, kterou hacker zadal pro Secret pole formuláře. Následující obrázek ukazuje nástroj Fiddler, který přidává Secret pole s hodnotou OverPost k publikovaným hodnotám formuláře.

Fiddler přidává pole Tajný kód

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází, i když návrhář aplikace nikdy nezamýšlel, aby byla vlastnost Secret nastavena s pomocí stránky Vytvořit.

Zobrazit model

Modely zobrazení poskytují alternativní způsob, jak zabránit nadměrnému umístění.

Aplikační model se často nazývá doménový model. Doménový model obvykle obsahuje všechny vlastnosti vyžadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti potřebné pro stránku uživatelského rozhraní, například vytvořit stránku.

Kromě modelu zobrazení používají některé aplikace k předávání dat mezi Razor třídou modelu stránky Stránky a prohlížečem model vazby nebo vstupní model.

Představte si následující StudentVM model zobrazení:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Následující kód používá StudentVM model zobrazení k vytvoření nového studenta:

[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");
}

SetValues metoda nastaví hodnoty tohoto objektu čtením hodnot z jiného PropertyValues objektu. SetValues používá porovnávání názvů vlastností. Typ modelu zobrazení:

  • Nemusí souviset s typem modelu.
  • Musí mít vlastnosti, které odpovídají.

Použití StudentVM vyžaduje, aby stránka Vytvořit použila StudentVM místo 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");}
}

Aktualizace stránky Upravit

V Pages/Students/Edit.cshtml.cspříkazu nahraďte OnGetAsync a OnPostAsync metody následujícím kódem.

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();
}

Změny kódu se podobají stránce Vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byla nahrazena znakem FindAsync. Pokud nepotřebujete zahrnout související data, FindAsync je efektivnější.
  • OnPostAsync má parametr id.
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji tím, že vytvoříte a upravíte studenta.

Stavy entit

Kontext databáze sleduje, jestli se entity v paměti synchronizují s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane při volání SaveChangesAsync . Pokud je například do metody předána AddAsync nová entita, stav dané entity je nastaven na Added. Když se volá SaveChangesAsync, kontext databáze vydá příkaz SQL INSERT.

Entita může být v jednom z následujících stavů:

  • Added: Entita ještě v databázi neexistuje. Metoda SaveChanges vydá INSERT příkaz.

  • Unchanged: U této entity není nutné ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Některé nebo všechny hodnoty vlastností entity byly změněny. Metoda SaveChanges vydá UPDATE příkaz.

  • Deleted: Entita byla označena k odstranění. Metoda SaveChanges vydá DELETE příkaz.

  • Detached: Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. Entita je přečtená, provede se změny a stav entity se automaticky změní na Modified. Volání SaveChanges generuje příkaz SQL UPDATE , který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci, DbContext která čte entitu a zobrazí data, se odstraní po vykreslení stránky. Při zavolání metody stránky OnPostAsync se vytvoří nový webový požadavek a s novou instancí DbContext. Opětovné přečtení entity v tomto novém kontextu simuluje zpracování na pracovní ploše.

Aktualizace stránky Odstranit

V této části je při selhání volání SaveChanges implementována vlastní chybová zpráva.

Nahraďte kód v souboru Pages/Students/Delete.cshtml.cs následujícím kódem:

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 });
            }
        }
    }
}

Předchozí kód:

  • Přidá protokolování.
  • Přidá volitelný parametr saveChangesError do OnGetAsync podpisu metody. saveChangesError označuje, zda byla metoda volána po selhání odstranění objektu studenta.

Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. Parametr saveChangesError je false, když je stránka Smazat OnGetAsync vyvolána z uživatelského rozhraní. Pokud je OnGetAsync volána OnPostAsync, protože odstranění selhalo, parametr saveChangesError je true.

Metoda OnPostAsync načte vybranou entitu a potom zavolá metodu Remove , která nastaví stav entity na Deleted. Při SaveChanges volání se vygeneruje příkaz SQL DELETE . Pokud Remove selže:

  • Výjimka databáze se zachytí.
  • Metoda Delete pages OnGetAsync je volána s saveChangesError=true.

Přidejte chybovou zprávu do Pages/Students/Delete.cshtml:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Spusťte aplikaci a odstraňte studenta a otestujte stránku Odstranit.

Další kroky

V tomto návodu se reviduje a upraví základní generovaný kód CRUD (vytvoření, čtení, aktualizace, odstranění).

Žádné úložiště

Někteří vývojáři používají model vrstvy služby nebo úložiště k vytvoření abstraktní vrstvy mezi uživatelským rozhraním (Razor stránkami) a vrstvou přístupu k datům. Tento tutoriál to nedělá. Aby se minimalizovala složitost a kurz se zaměřil na EF Core, je kód EF Core přidán přímo do tříd modelu stránky.

Aktualizace stránky Podrobnosti

Kód strukturovaný pro studentské stránky neobsahuje údaje o zápisu. V této části se registrace přidají na stránku Podrobnosti.

Čtení registrací

Pokud chcete na stránce zobrazit data o zápisu studenta, je třeba přečíst tato data. Vygenerovaný kód v Pages/Students/Details.cshtml.cs čte pouze data studenta, a nikoliv data o registraci.

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();
}

Nahraďte metodu OnGetAsync následujícím kódem pro čtení dat zápisu pro vybraného studenta. Změny jsou zvýrazněné.

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();
}

Metody IncludeThenInclude způsobují, že kontext načte Student.Enrollments navigační vlastnost a v rámci každého zápisu Enrollment.Course navigační vlastnost. Tyto metody jsou podrobně prozkoumány v kurzu čtení souvisejících dat .

Metoda AsNoTracking zlepšuje výkon ve scénářích, kdy se vrácené entity neaktualizují v aktuálním kontextu. AsNoTracking je popsáno dále v tomto kurzu.

Zobrazení registrací

Nahraďte kód Pages/Students/Details.cshtml následujícím kódem, aby se zobrazil seznam registrací. Změny jsou zvýrazněné.

@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>

Předchozí smyčka kódu prochází entitami v Enrollments navigační vlastnosti. Pro každou registraci se zobrazí název kurzu a známka. Název kurzu se načte z entity kurzu, která je uložená v Course navigační vlastnosti entity Registrace.

Spusťte aplikaci, vyberte kartu Studenti a klikněte na odkaz Podrobnosti pro studenta. Zobrazí se seznam kurzů a známek pro vybraného studenta.

Způsoby čtení jedné entity

Vygenerovaný kód používá metodu FirstOrDefaultAsync ke čtení jedné entity. Tato metoda vrátí hodnotu null, pokud se nic nenajde; v opačném případě vrátí první řádek, který splňuje kritéria filtru dotazu. FirstOrDefaultAsync je obecně lepší volbou než následující alternativy:

  • SingleOrDefaultAsync – vyvolá výjimku, pokud existuje více než jedna entita, která splňuje filtr dotazu. Chcete-li zjistit, zda dotaz může vrátit více než jeden řádek, SingleOrDefaultAsync pokusí se načíst více řádků. Tato nadbytečná práce není nutná, pokud dotaz může vrátit pouze jednu entitu, jako když hledá jedinečný klíč.
  • FindAsync – Najde entitu s primárním klíčem (PK). Pokud je entita s PK sledována kontextem, vrátí se bez požadavku na databázi. Tato metoda je optimalizovaná pro vyhledání jedné entity, ale nemůžete ji Includevolat FindAsync . Takže pokud potřebujete související data, FirstOrDefaultAsync je lepší volbou.

Směrování dat vs. řetězec dotazu

Adresa URL stránky Podrobnosti je https://localhost:<port>/Students/Details?id=1. Hodnota primárního klíče entity je v řetězci dotazu. Někteří vývojáři raději předávají hodnotu klíče ve směrovacích datech: https://localhost:<port>/Students/Details/1. Další informace najdete v tématu Aktualizace vygenerovaného kódu.

Aktualizace stránky Vytvořit

Vygenerovaný OnPostAsync kód pro stránku Vytvořit je zranitelný vůči nadměrnému umístění. Nahraďte metodu OnPostAsync v Pages/Students/Create.cshtml.cs následujícím kódem.

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

Předchozí kód vytvoří objekt Student a pak použije pole publikovaného formuláře k aktualizaci vlastností objektu Student. Metoda TryUpdateModelAsync :

  • Používá hodnoty odeslaného formuláře z vlastnosti PageContext v objektu PageModel.
  • Aktualizuje pouze uvedené vlastnosti (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Vyhledá pole formuláře s předponou student. Například Student.FirstMidName. Nerozlišuje se malá a velká písmena.
  • Používá systém vazeb modelu k převodu hodnot formulářů z řetězců na typy v Student modelu. Například EnrollmentDate je třeba převést na DateTime.

Spusťte aplikaci a vytvořte entitu studenta, která otestuje stránku Vytvořit.

Nadměrné zveřejňování

Použití TryUpdateModel k aktualizaci polí s publikovanými hodnotami je osvědčeným postupem zabezpečení, protože brání nadměrnému publikování. Předpokládejme například, že entita Student obsahuje Secret vlastnost, kterou by tato webová stránka neměla aktualizovat nebo přidat:

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; }
}

I když na stránce pro vytvoření nebo aktualizaci nemá aplikace pole Secret, hacker může hodnotu Secret nastavit přenastavením. Hacker může použít nástroj, jako je Fiddler nebo napsat nějaký JavaScript, k publikování Secret hodnoty formuláře. Původní kód neomezuje pole, která binder modelu používá při vytváření instance Student.

V databázi se aktualizuje jakákoli hodnota, kterou hacker zadal pro Secret pole formuláře. Následující obrázek ukazuje nástroj Fiddler, který přidává Secret pole (s hodnotou OverPost) do odeslaných hodnot formuláře.

Fiddler přidává pole Tajný kód

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází i přesto, že návrhář aplikace nikdy nezamýšlel, aby byla vlastnost nastavena na stránce Vytvořit.

Zobrazit model

Modely zobrazení poskytují alternativní způsob, jak zabránit nadměrnému umístění.

Aplikační model se často nazývá doménový model. Doménový model obvykle obsahuje všechny vlastnosti vyžadované odpovídající entitou v databázi. Model zobrazení obsahuje pouze vlastnosti potřebné pro uživatelské rozhraní, pro které se používá (například stránka Vytvořit).

Kromě modelu zobrazení používají některé aplikace k předávání dat mezi Razor třídou modelu stránky Stránky a prohlížečem model vazby nebo vstupní model.

Představte si následující Student model zobrazení:

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; }
    }
}

Následující kód používá StudentVM model zobrazení k vytvoření nového studenta:

[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");
}

SetValues metoda nastaví hodnoty tohoto objektu čtením hodnot z jiného PropertyValues objektu. SetValues používá porovnávání názvů vlastností. Typ modelu zobrazení nemusí souviset s typem modelu, ale musí mít jenom vlastnosti, které odpovídají.

Použití StudentVM vyžaduje, aby Create.cshtml byl aktualizován tak, aby používal StudentVM místo Student.

Aktualizace stránky Upravit

V Pages/Students/Edit.cshtml.cspříkazu nahraďte OnGetAsync a OnPostAsync metody následujícím kódem.

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();
}

Změny kódu se podobají stránce Vytvořit s několika výjimkami:

  • FirstOrDefaultAsync byla nahrazena znakem FindAsync. Pokud nejsou zahrnutá související data potřeba, FindAsync je efektivnější.
  • OnPostAsync id má parametr.
  • Aktuální student se načte z databáze místo vytvoření prázdného studenta.

Spusťte aplikaci a otestujte ji tím, že vytvoříte a upravíte studenta.

Stavy entit

Kontext databáze sleduje, jestli se entity v paměti synchronizují s odpovídajícími řádky v databázi. Tyto informace o sledování určují, co se stane při volání SaveChangesAsync . Pokud je například do metody předána AddAsync nová entita, stav dané entity je nastaven na Added. Když se volá SaveChangesAsync, kontext databáze vydá příkaz SQL INSERT.

Entita může být v jednom z následujících stavů:

  • Added: Entita ještě v databázi neexistuje. Metoda SaveChanges vydá příkaz INSERT.

  • Unchanged: U této entity není nutné ukládat žádné změny. Entita má tento stav při čtení z databáze.

  • Modified: Některé nebo všechny hodnoty vlastností entity byly změněny. Metoda SaveChanges vydává příkaz UPDATE.

  • Deleted: Entita byla označena k odstranění. Metoda SaveChanges vydá příkaz DELETE.

  • Detached: Entita není sledována kontextem databáze.

V desktopové aplikaci se změny stavu obvykle nastaví automaticky. Entita je přečtená, provede se změny a stav entity se automaticky změní na Modified. Volání SaveChanges generuje příkaz SQL UPDATE, který aktualizuje pouze změněné vlastnosti.

Ve webové aplikaci, DbContext která čte entitu a zobrazí data, se odstraní po vykreslení stránky. Při zavolání metody stránky OnPostAsync se vytvoří nový webový požadavek a zároveň nová instance DbContext. Opětovné přečtení entity v tomto novém kontextu simuluje desktopové zpracování.

Aktualizace stránky Odstranit

V této části implementujete vlastní chybovou zprávu, když volání SaveChanges selže.

Nahraďte kód v Pages/Students/Delete.cshtml.cs následujícím kódem. Změny jsou zvýrazněny (s výjimkou vyčištění výrazů using).

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = "Delete failed. Try again";
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Předchozí kód přidá volitelný parametr saveChangesError do OnGetAsync podpisu metody. saveChangesError označuje, zda byla metoda volána po neúspěšném pokusu o odstranění objektu studenta. Operace odstranění může selhat kvůli přechodným problémům se sítí. Přechodné chyby sítě jsou pravděpodobnější, když je databáze v cloudu. Parametr saveChangesError má hodnotu false, když je stránka Delete OnGetAsync volána z uživatelského rozhraní. Když OnGetAsync je volán OnPostAsync (protože operace odstranění selhala), parametr saveChangesError je pravdivý.

Metoda OnPostAsync načte vybranou entitu a potom zavolá metodu Remove , která nastaví stav entity na Deleted. Při SaveChanges volání se vygeneruje příkaz SQL DELETE. Pokud Remove selže:

  • Výjimka databáze se zachytí.
  • Metoda Delete stránky OnGetAsync je volána s saveChangesError=true.

Přidejte chybovou zprávu na stránku Odstranit 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>

Spusťte aplikaci a odstraňte studenta, abyste otestovali stránku Odstranit.

Další kroky