Část 2, Razor Stránky s EF Core ASP.NET Core – CRUD

Tom Dykstra, Jeremy Likness a Jon P Smith

Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace Pages pomocí EF Core 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ůsobí 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 se registrace přidají na Details stránku.

Čtení registrací

Pokud chcete na stránce zobrazit data o zápisu studenta, musí se načíst data o registraci. Vygenerovaný kód vygenerovaný v Pages/Students/Details.cshtml.cs datech č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 ThenInclude způsobují, že kontext načte Student.Enrollments navigační vlastnost a v rámci každé registrace 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 ji FindAsyncvolat Include . 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 OnPostAsyncPages/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 publikovaného formuláře z PageContext vlastnosti v objektu PageModel.
  • Aktualizace pouze vlastnosti uvedené (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.

Přesunutí

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 nemá Secret na stránce pro vytvoření nebo aktualizaci Razor pole, hacker může hodnotu nastavit Secret tak, že ji přeloží. 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 adding Secret field

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel Secret , aby byla vlastnost nastavená 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 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 Studentstránka Vytvořit místoStudentVM:

@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ší.
  • OnPostAsyncid 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 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ž SaveChangesAsync se volá, 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í plochy.

Aktualizace stránky Odstranit

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

Nahraďte kód 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ž se stránka OnGetAsync Delete volá z uživatelského rozhraní. Pokud OnGetAsync je volána OnPostAsync , protože operace odstranění selhala, saveChangesError parametr 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 kurzu se reviduje a přizpůsobí 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 se registrace přidají na Details stránku.

Čtení registrací

Pokud chcete na stránce zobrazit data o zápisu studenta, musí se načíst data o registraci. Vygenerovaný kód vygenerovaný v Pages/Students/Details.cshtml.cs datech č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 ThenInclude způsobují, že kontext načte Student.Enrollments navigační vlastnost a v rámci každé registrace 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 ji FindAsyncvolat Include . 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 OnPostAsyncPages/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 publikovaného formuláře z PageContext vlastnosti v objektu PageModel.
  • Aktualizace pouze vlastnosti uvedené (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.

Přesunutí

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 nemá Secret na stránce pro vytvoření nebo aktualizaci Razor pole, hacker může hodnotu nastavit Secret tak, že ji přeloží. 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 adding Secret field

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel Secret , aby byla vlastnost nastavená 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 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 Studentstránka Vytvořit místoStudentVM:

@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ší.
  • OnPostAsyncid 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 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ž SaveChangesAsync se volá, 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í plochy.

Aktualizace stránky Odstranit

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

Nahraďte kód 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ž se stránka OnGetAsync Delete volá z uživatelského rozhraní. Pokud OnGetAsync je volána OnPostAsync , protože operace odstranění selhala, saveChangesError parametr 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 kurzu se reviduje a přizpůsobí 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 se registrace přidají na stránku Podrobnosti.

Čtení registrací

Pokud chcete na stránce zobrazit data o zápisu studenta, musí být data o registraci čtená. Vygenerovaný kód vygenerovaný v Pages/Students/Details.cshtml.cs datech studenta čte pouze data studenta bez dat 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 Include ThenInclude způsobují, že kontext načte Student.Enrollments navigační vlastnost a v rámci každé registrace 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 FindAsyncvolat Include . 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 OnPostAsyncPages/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 publikovaného formuláře z PageContext vlastnosti v objektu PageModel.
  • Aktualizace pouze vlastnosti uvedené (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.

Přesunutí

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 nemá Secret na stránce pro vytvoření nebo aktualizaci Razor pole, hacker může hodnotu nastavit Secret tak, že ji přeloží. 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 adding Secret field

Hodnota OverPost je úspěšně přidána do Secret vlastnosti vloženého řádku. K tomu dochází i v případě, že návrhář aplikace nikdy nezamýšlel Secret , aby byla vlastnost nastavená 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 aktualizaci Create.cshtml, Studentaby místo StudentVM .

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ší.
  • OnPostAsyncid 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 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ž SaveChangesAsync se volá, 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 s novou instancí DbContext. Opětovné přečtení entity v tomto novém kontextu simuluje zpracování plochy.

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ěné (kromě vyčištění using příkazů).

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 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ž se stránka OnGetAsync Delete volá z uživatelského rozhraní. Když OnGetAsync je volána ( OnPostAsync protože operace odstranění selhala), saveChangesError je parametr 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řidání chybové zprávy 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 a otestujte stránku Odstranit.

Další kroky