Razor Pages con EF Core in ASP.NET Core - CRUD - 2 di 8Razor Pages with EF Core in ASP.NET Core - CRUD - 2 of 8

Tom Dykstra, Jon P Smith e Rick AndersonBy Tom Dykstra, Jon P Smith, and Rick Anderson

Nell'app Web di Contoso University viene illustrato come creare app Web Razor Pages usando Entity Framework Core e Visual Studio.The Contoso University web app demonstrates how to create Razor Pages web apps using EF Core and Visual Studio. Per informazioni sulla serie di esercitazioni, vedere la prima esercitazione.For information about the tutorial series, see the first tutorial.

Se si verificano problemi che non è possibile risolvere, scaricare l'app completata e confrontare tale codice con quello creato seguendo questa esercitazione.If you run into problems you can't solve, download the completed app and compare that code to what you created by following the tutorial.

In questa esercitazione viene esaminato e personalizzato il codice CRUD (Create, Read, Update, Delete) con scaffolding.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Nessun repositoryNo repository

Alcuni sviluppatori usano un livello di servizio o uno schema di repository per creare un livello di astrazione tra l'interfaccia utente (Razor Pages) e il livello di accesso ai dati.Some developers use a service layer or repository pattern to create an abstraction layer between the UI (Razor Pages) and the data access layer. Questa esercitazione non segue questo approccio.This tutorial doesn't do that. Per ridurre la complessità e mantenere questa esercitazione incentrata su EF Core, il codice EF Core viene aggiunto direttamente alle classi dei modelli di pagina.To minimize complexity and keep the tutorial focused on EF Core, EF Core code is added directly to the page model classes.

Aggiornare la pagina DetailsUpdate the Details page

Il codice con scaffolding per le pagine Students non include i dati di iscrizione.The scaffolded code for the Students pages doesn't include enrollment data. In questa sezione vengono aggiunte le iscrizioni alla pagina Details.In this section, you add enrollments to the Details page.

Leggere le iscrizioniRead enrollments

Per visualizzare i dati di iscrizione di uno studente nella pagina, è necessario leggerli.To display a student's enrollment data on the page, you need to read it. Il codice con scaffolding in Pages/Students/Details.cshtml.cs legge solo i dati degli studenti, senza i dati di iscrizione:The scaffolded code in Pages/Students/Details.cshtml.cs reads only the Student data, without the Enrollment data:

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

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

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

Sostituire il metodo OnGetAsync con il codice seguente per leggere i dati di iscrizione per lo studente selezionato.Replace the OnGetAsync method with the following code to read enrollment data for the selected student. Le modifiche sono evidenziate.The changes are highlighted.

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

I metodi Include e ThenInclude fanno in modo che il contesto carichi la proprietà di navigazione Student.Enrollments e la proprietà di navigazione Enrollment.Course all'interno di ogni iscrizione.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Questi metodi vengono esaminati in dettaglio nell'esercitazione Lettura dei dati correlati.These methods are examined in detail in the Reading related data tutorial.

Il metodo AsNoTracking migliora le prestazioni negli scenari in cui le entità restituite non vengono aggiornate nel contesto corrente.The AsNoTracking method improves performance in scenarios where the entities returned are not updated in the current context. AsNoTracking è descritto più avanti in questa esercitazione.AsNoTracking is discussed later in this tutorial.

Visualizzare le iscrizioniDisplay enrollments

Sostituire il codice in Pages/Students/Details.cshtml con il codice seguente per visualizzare un elenco di iscrizioni.Replace the code in Pages/Students/Details.cshtml with the following code to display a list of enrollments. Le modifiche sono evidenziate.The changes are highlighted.

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

Il codice precedente esegue il ciclo nelle entità nella proprietà di navigazione Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Per ogni registrazione, il codice visualizza il titolo del corso e il voto.For each enrollment, it displays the course title and the grade. Il titolo del corso viene recuperato dall'entità Course memorizzata nella proprietà di navigazione Course dell'entità Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Eseguire l'app, selezionare la scheda Students e fare clic sul collegamento Details relativo a uno studente.Run the app, select the Students tab, and click the Details link for a student. Viene visualizzato l'elenco dei corsi e dei voti dello studente selezionato.The list of courses and grades for the selected student is displayed.

Modalità di lettura di un'entitàWays to read one entity

Il codice generato usa FirstOrDefaultAsync per leggere un'entità.The generated code uses FirstOrDefaultAsync to read one entity. Questo metodo restituisce Null se non viene trovato alcun elemento. In caso contrario, viene restituita la prima riga trovata che soddisfa i criteri di filtro della query.This method returns null if nothing is found; otherwise, it returns the first row found that satisfies the query filter criteria. FirstOrDefaultAsync è in genere una scelta migliore rispetto alle alternative seguenti:FirstOrDefaultAsync is generally a better choice than the following alternatives:

  • SingleOrDefaultAsync - Genera un'eccezione se è presente più di un'entità che soddisfa il filtro di query.SingleOrDefaultAsync - Throws an exception if there's more than one entity that satisfies the query filter. Per determinare se la query può restituire più di una riga, SingleOrDefaultAsync tenta di recuperare più righe.To determine if more than one row could be returned by the query, SingleOrDefaultAsync tries to fetch multiple rows. Questa operazione aggiuntiva non è necessario se la query può restituire solo un'entità, ad esempio quando esegue la ricerca in base a una chiave univoca.This extra work is unnecessary if the query can only return one entity, as when it searches on a unique key.
  • FindAsync - Trova un'entità con la chiave primaria.FindAsync - Finds an entity with the primary key (PK). Se il contesto rileva un'entità con la chiave primaria, l'entità viene restituita senza una richiesta al database.If an entity with the PK is being tracked by the context, it's returned without a request to the database. Questo metodo è ottimizzato per la ricerca di una singola entità, ma non è possibile chiamare Include con FindAsync.This method is optimized to look up a single entity, but you can't call Include with FindAsync. Se sono necessari dati correlati, FirstOrDefaultAsync è quindi la scelta migliore.So if related data is needed, FirstOrDefaultAsync is the better choice.

Dati di route o stringa di queryRoute data vs. query string

L'URL della pagina Details è https://localhost:<port>/Students/Details?id=1.The URL for the Details page is https://localhost:<port>/Students/Details?id=1. Il valore della chiave primaria dell'entità si trova nella stringa di query.The entity's primary key value is in the query string. Alcuni sviluppatori preferiscono passare il valore della chiave nei dati della route: https://localhost:<port>/Students/Details/1.Some developers prefer to pass the key value in route data: https://localhost:<port>/Students/Details/1. Per altre informazioni, vedere Aggiornare il codice generato.For more information, see Update the generated code.

Aggiornare la pagina CreateUpdate the Create page

Il codice OnPostAsync con scaffolding per la pagina Create è vulnerabile all'overposting.The scaffolded OnPostAsync code for the Create page is vulnerable to overposting. Sostituire il metodo OnPostAsync in Pages/Students/Create.cshtml.cs con il codice seguente.Replace the OnPostAsync method in Pages/Students/Create.cshtml.cs with the following code.

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

TryUpdateModelAsyncTryUpdateModelAsync

Il codice precedente crea un oggetto Student e quindi usa i campi del modulo pubblicati per aggiornare le proprietà dell'oggetto Student.The preceding code creates a Student object and then uses posted form fields to update the Student object's properties. Il metodo TryUpdateModelAsync:The TryUpdateModelAsync method:

  • Usa i valori del modulo pubblicati dalla proprietà PageContext in PageModel.Uses the posted form values from the PageContext property in the PageModel.
  • Aggiorna solo le proprietà elencate (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).Updates only the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Cerca i campi del modulo con il prefisso "student".Looks for form fields with a "student" prefix. Ad esempio: Student.FirstMidName.For example, Student.FirstMidName. Non viene fatta distinzione tra maiuscole e minuscole.It's not case sensitive.
  • Usa il sistema di associazione di modelli per convertire i valori dei moduli da stringa ai tipi nel modello Student.Uses the model binding system to convert form values from strings to the types in the Student model. Ad esempio, EnrollmentDate deve essere convertito in DateTime.For example, EnrollmentDate has to be converted to DateTime.

Eseguire l'app e creare un'entità Student per testare la pagina Create.Run the app, and create a student entity to test the Create page.

OverpostingOverposting

L'uso di TryUpdateModel per l'aggiornamento dei campi con i valori inviati è una procedura di sicurezza consigliata poiché impedisce l'overposting.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Ad esempio, si supponga che l'entità Student includa una proprietà Secret che la pagina Web non deve aggiornare o aggiungere:For example, suppose the Student entity includes a Secret property that this web page shouldn't update or add:

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

Anche se l'app non include un campo Secret nella pagina Razor Create o Update, un hacker potrebbe impostare il valore Secret tramite overposting.Even if the app doesn't have a Secret field on the create or update Razor Page, a hacker could set the Secret value by overposting. Un hacker potrebbe usare uno strumento come Fiddler oppure scrivere codice JavaScript per inviare un valore di modulo Secret.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Il codice originale non limita i campi usati dallo strumento di associazione di modelli durante la creazione di un'istanza di Student.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

Qualsiasi valore specificato dall'hacker per il campo di modulo Secret viene aggiornato nel database.Whatever value the hacker specified for the Secret form field is updated in the database. L'immagine seguente illustra lo strumento Fiddler che aggiunge il campo Secret (con il valore "OverPost") ai valori di modulo inviati.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler aggiunge il campo Secret

Il valore "OverPost" è stato aggiunto alla proprietà Secret della riga inserita.The value "OverPost" is successfully added to the Secret property of the inserted row. Ciò accade anche se il progettista dell'app non ha mai previsto che la proprietà Secret venisse impostata con la pagina Create.That happens even though the app designer never intended the Secret property to be set with the Create page.

Modello di visualizzazioneView model

I modelli di visualizzazione rappresentano un altro metodo per impedire l'overposting.View models provide an alternative way to prevent overposting.

Il modello di applicazione è spesso chiamato modello di dominio.The application model is often called the domain model. Il modello di dominio contiene in genere tutte le proprietà richieste dall'entità corrispondente nel database.The domain model typically contains all the properties required by the corresponding entity in the database. Il modello di visualizzazione contiene solo le proprietà necessarie per l'interfaccia utente per cui viene usato, ad esempio la pagina Create.The view model contains only the properties needed for the UI that it is used for (for example, the Create page).

Oltre al modello di visualizzazione, alcune app usano un modello di associazione o un modello di input per passare i dati dalla classe del modello di pagina di Razor Pages al browser e viceversa.In addition to the view model, some apps use a binding model or input model to pass data between the Razor Pages page model class and the browser.

Si consideri il modello di visualizzazione Student seguente:Consider the following Student view model:

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

Il codice seguente usa il modello di visualizzazione StudentVM per creare un nuovo studente:The following code uses the StudentVM view model to create a new student:

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

Il metodo SetValues imposta i valori di questo oggetto leggendo i valori di un altro oggetto PropertyValues.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues usa la corrispondenza dei nomi di proprietà.SetValues uses property name matching. Poiché il tipo di modello di visualizzazione non deve essere correlato al tipo di modello, è sufficiente che abbia proprietà corrispondenti.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

Se si usa StudentVM è necessario che Create.cshtml venga aggiornato per l'uso di StudentVM anziché Student.Using StudentVM requires Create.cshtml be updated to use StudentVM rather than Student.

Aggiornare la pagina Edit (Modifica)Update the Edit page

In Pages/Students/Edit.cshtml.cs sostituire i metodi OnGetAsync e OnPostAsync con il codice seguente.In Pages/Students/Edit.cshtml.cs, replace the OnGetAsync and OnPostAsync methods with the following code.

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

Le modifiche al codice sono simili alla pagina Create con alcune eccezioni:The code changes are similar to the Create page with a few exceptions:

  • FirstOrDefaultAsync è stato sostituito con FindAsync.FirstOrDefaultAsync has been replaced with FindAsync. Quando non è necessario includere dati correlati, FindAsync è più efficiente.When you don't have to include related data, FindAsync is more efficient.
  • OnPostAsync ha un parametro id.OnPostAsync has an id parameter.
  • Lo studente corrente viene recuperato dal database senza creare uno studente vuoto.The current student is fetched from the database, rather than creating an empty student.

Eseguire l'app e testarla creando e modificando uno studente.Run the app, and test it by creating and editing a student.

Stati di entitàEntity States

Il contesto del database tiene traccia della sincronizzazione delle entità in memoria con le righe corrispondenti nel database.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database. Queste informazioni di traccia determinano le operazioni eseguite quando viene chiamato SaveChangesAsync.This tracking information determines what happens when SaveChangesAsync is called. Ad esempio, quando una nuova entità viene passata al metodo AddAsync, lo stato dell'entità viene impostato su Added.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Quando viene chiamato SaveChangesAsync, il contesto del database genera un comando SQL INSERT.When SaveChangesAsync is called, the database context issues a SQL INSERT command.

Un'entità può trovarsi in uno dei seguenti stati:An entity may be in one of the following states:

  • Added: l'entità non esiste ancora nel database.Added: The entity doesn't yet exist in the database. Il metodo SaveChanges genera un'istruzione INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged: non è necessario salvare alcuna modifica con questa entità.Unchanged: No changes need to be saved with this entity. Un'entità ha questo stato quando viene letta dal database.An entity has this status when it's read from the database.

  • Modified: sono stati modificati alcuni o tutti i valori di proprietà dell'entità.Modified: Some or all of the entity's property values have been modified. Il metodo SaveChanges genera un'istruzione UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted: l'entità è stata contrassegnata per l'eliminazione.Deleted: The entity has been marked for deletion. Il metodo SaveChanges genera un'istruzione DELETE.The SaveChanges method issues a DELETE statement.

  • Detached: l'entità non viene rilevata dal contesto del database.Detached: The entity isn't being tracked by the database context.

In un'applicazione desktop le modifiche dello stato vengono in genere impostate automaticamente.In a desktop app, state changes are typically set automatically. Viene letta un'entità, vengono apportate le modifiche e lo stato dell'entità viene modificato automaticamente in Modified.An entity is read, changes are made, and the entity state is automatically changed to Modified. La chiamata di SaveChanges genera un'istruzione SQL UPDATE che aggiorna solo le proprietà modificate.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

In un'app Web il DbContext che legge un'entità e visualizza i dati viene eliminato dopo il rendering di una pagina.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Quando viene chiamato il metodo OnPostAsync di una pagina, viene effettuata una nuova richiesta Web con una nuova istanza di DbContext.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. La rilettura dell'entità nel nuovo contesto simula l'elaborazione desktop.Rereading the entity in that new context simulates desktop processing.

Aggiornare la pagina Delete (Elimina)Update the Delete page

In questa sezione viene implementato un messaggio di errore personalizzato quando la chiamata a SaveChanges ha esito negativo.In this section, you implement a custom error message when the call to SaveChanges fails.

Sostituire il codice in Pages/Students/Delete.cshtml.cs con il codice seguente.Replace the code in Pages/Students/Delete.cshtml.cs with the following code. Le modifiche vengono evidenziate (a eccezione della pulizia delle istruzioni using).The changes are highlighted (other than cleanup of using statements).

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

Il codice precedente aggiunge il parametro facoltativo saveChangesError alla firma del metodo OnGetAsync.The preceding code adds the optional parameter saveChangesError to the OnGetAsync method signature. saveChangesError indica se il metodo è stato chiamato dopo un errore di eliminazione dell'oggetto Student.saveChangesError indicates whether the method was called after a failure to delete the student object. L'operazione di eliminazione potrebbe non riuscire a causa di problemi di rete temporanei.The delete operation might fail because of transient network problems. Gli errori di rete temporanei sono più probabili quando il database è nel cloud.Transient network errors are more likely when the database is in the cloud. Il parametrosaveChangesError è false quando si chiama OnGetAsync della pagina Delete dall'interfaccia utente.The saveChangesError parameter is false when the Delete page OnGetAsync is called from the UI. Quando OnGetAsync viene chiamato da OnPostAsync (perché l'operazione di eliminazione ha avuto esito negativo), il parametro saveChangesError ha valore true.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

Il metodo OnPostAsync recupera l'entità selezionata, quindi chiama il metodo Remove per impostare lo stato dell'entità su Deleted.The OnPostAsync method retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Quando viene chiamato SaveChanges, viene generato un comando SQL DELETE.When SaveChanges is called, a SQL DELETE command is generated. Se Remove ha esito negativo:If Remove fails:

  • Viene rilevata l'eccezione del database.The database exception is caught.
  • Il metodo OnGetAsync delle pagine Delete viene chiamato con saveChangesError=true.The Delete pages OnGetAsync method is called with saveChangesError=true.

Aggiungere un messaggio di errore alla pagina Razor Delete (Pages/Students/Delete.cshtml):Add an error message to the Delete Razor Page (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>

Eseguire l'app ed eliminare uno studente per testare la pagina Delete.Run the app and delete a student to test the Delete page.

Passaggi successiviNext steps

In questa esercitazione viene esaminato e personalizzato il codice CRUD (Create, Read, Update, Delete) con scaffolding.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Per ridurre la complessità e mantenere queste esercitazioni incentrate su EF Core, viene usato il codice EF Core nei modelli di pagina.To minimize complexity and keep these tutorials focused on EF Core, EF Core code is used in the page models. Alcuni sviluppatori usano un modello di servizio o di repository per creare un livello di astrazione tra l'interfaccia utente (Razor Pages) e il livello di accesso ai dati.Some developers use a service layer or repository pattern in to create an abstraction layer between the UI (Razor Pages) and the data access layer.

In questa esercitazione vengono esaminate le pagine Razor Create (Crea), Edit (Modifica), Delete (Elimina) e Details (Dettagli) nella cartella Students.In this tutorial, the Create, Edit, Delete, and Details Razor Pages in the Students folder are examined.

Il codice con scaffolding usa il modello seguente per le pagine Create, Edit e Delete:The scaffolded code uses the following pattern for Create, Edit, and Delete pages:

  • Ottenere e visualizzare i dati richiesti con il metodo HTTP GET OnGetAsync.Get and display the requested data with the HTTP GET method OnGetAsync.
  • Salvare le modifiche apportate ai dati con il metodo HTTP POST OnPostAsync.Save changes to the data with the HTTP POST method OnPostAsync.

Le pagine Index e Details ottengono e visualizzano i dati richiesti con il metodo HTTP GET OnGetAsyncThe Index and Details pages get and display the requested data with the HTTP GET method OnGetAsync

SingleOrDefaultAsync e FirstOrDefaultAsyncSingleOrDefaultAsync vs. FirstOrDefaultAsync

Il codice generato usa FirstOrDefaultAsync, che in genere è preferibile rispetto a SingleOrDefaultAsync.The generated code uses FirstOrDefaultAsync, which is generally preferred over SingleOrDefaultAsync.

FirstOrDefaultAsync è più efficace di SingleOrDefaultAsync nel recupero di una sola entità:FirstOrDefaultAsync is more efficient than SingleOrDefaultAsync at fetching one entity:

  • A meno che il codice non necessiti di verificare che non sia presente più di un'entità restituita dalla query.Unless the code needs to verify that there's not more than one entity returned from the query.
  • SingleOrDefaultAsync recupera una maggior quantità di dati ed esegue operazioni non necessarie.SingleOrDefaultAsync fetches more data and does unnecessary work.
  • SingleOrDefaultAsync genera un'eccezione se è presente più di un'entità che soddisfa la parte del filtro.SingleOrDefaultAsync throws an exception if there's more than one entity that fits the filter part.
  • FirstOrDefaultAsync non genera alcun elemento se è presente più di un'entità che soddisfa la parte del filtro.FirstOrDefaultAsync doesn't throw if there's more than one entity that fits the filter part.

FindAsyncFindAsync

Nella maggior parte del codice con scaffolding è possibile usare FindAsync al posto di FirstOrDefaultAsync.In much of the scaffolded code, FindAsync can be used in place of FirstOrDefaultAsync.

FindAsync:FindAsync:

  • Individua un'entità con la chiave primaria.Finds an entity with the primary key (PK). Se il contesto rileva un'entità con la chiave primaria, l'entità viene restituita senza una richiesta al database.If an entity with the PK is being tracked by the context, it's returned without a request to the DB.
  • È semplice e conciso.Is simple and concise.
  • È ottimizzato per la ricerca di una singola entità.Is optimized to look up a single entity.
  • Può offrire migliori prestazioni, ma raramente negli scenari Web tipici.Can have perf benefits in some situations, but that rarely happens for typical web apps.
  • Usa in modo implicito FirstAsync anziché SingleAsync.Implicitly uses FirstAsync instead of SingleAsync.

Se si vuole usare Include per includere altre entità, l'uso di FindAsync non è più appropriato.But if you want to Include other entities, then FindAsync is no longer appropriate. Ciò significa che potrebbe essere necessario abbandonare FindAsync e passare a una query durante la creazione dell'app.This means that you may need to abandon FindAsync and move to a query as your app progresses.

Personalizzare la pagina DetailsCustomize the Details page

Passare alla pagina Pages/Students.Browse to Pages/Students page. I collegamenti Edit, Details e Delete vengono generati dall'helper del tag di ancoraggio nel file Pages/Students/Index.cshtml.The Edit, Details, and Delete links are generated by the Anchor Tag Helper in the Pages/Students/Index.cshtml file.

<td>
    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>

Eseguire l'app e selezionare un collegamento Details.Run the app and select a Details link. L'URL è nel formato http://localhost:5000/Students/Details?id=2.The URL is of the form http://localhost:5000/Students/Details?id=2. L'ID studente viene passato tramite una stringa di query (?id=2).The Student ID is passed using a query string (?id=2).

Aggiornare le pagine Razor Edit, Details e Delete in modo da usare il modello di route "{id:int}".Update the Edit, Details, and Delete Razor Pages to use the "{id:int}" route template. Modificare la direttiva page per ognuna di queste pagine da @page a @page "{id:int}".Change the page directive for each of these pages from @page to @page "{id:int}".

Una richiesta alla pagina con il modello di route "{id: int}" che non include il valore di route intero restituisce un errore HTTP 404 (Non trovato).A request to the page with the "{id:int}" route template that does not include a integer route value returns an HTTP 404 (not found) error. Ad esempio, http://localhost:5000/Students/Details restituisce un errore 404.For example, http://localhost:5000/Students/Details returns a 404 error. Per rendere l'ID facoltativo, aggiungere ? al vincolo di route:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Eseguire l'app, fare clic su un collegamento Details e verificare che l'URL passi l'ID come dati della route (http://localhost:5000/Students/Details/2).Run the app, click on a Details link, and verify the URL is passing the ID as route data (http://localhost:5000/Students/Details/2).

Non modificare globalmente @page in @page "{id:int}" per non interrompere i collegamenti alle pagine Home e Create.Don't globally change @page to @page "{id:int}", doing so breaks the links to the Home and Create pages.

Il codice con scaffolding della pagina Students Index non include la proprietà Enrollments.The scaffolded code for the Students Index page doesn't include the Enrollments property. In questa sezione i contenuti della raccolta Enrollments sono visualizzati nella pagina Details.In this section, the contents of the Enrollments collection is displayed in the Details page.

Il metodo OnGetAsync del file Pages/Students/Details.cshtml.cs usa il metodo FirstOrDefaultAsync per recuperare una singola entità Student.The OnGetAsync method of Pages/Students/Details.cshtml.cs uses the FirstOrDefaultAsync method to retrieve a single Student entity. Aggiungere il codice evidenziato seguente:Add the following highlighted code:

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

    Student = await _context.Student
                        .Include(s => s.Enrollments)
                            .ThenInclude(e => e.Course)
                        .AsNoTracking()
                        .FirstOrDefaultAsync(m => m.ID == id);

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

I metodi Include e ThenInclude fanno in modo che il contesto carichi la proprietà di navigazione Student.Enrollments e la proprietà di navigazione Enrollment.Course all'interno di ogni iscrizione.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Questi metodi vengono esaminati in dettaglio nell'esercitazione sulla lettura dei dati correlati.These methods are examined in detail in the reading-related data tutorial.

Il metodo AsNoTracking migliora le prestazioni negli scenari in cui le entità restituite non vengono aggiornate nel contesto corrente.The AsNoTracking method improves performance in scenarios when the entities returned are not updated in the current context. AsNoTracking è descritto più avanti in questa esercitazione.AsNoTracking is discussed later in this tutorial.

Aprire Pages/Students/Details.cshtml.Open Pages/Students/Details.cshtml. Per visualizzare un elenco delle registrazioni, aggiungere il codice evidenziato seguente:Add the following highlighted code to display a list of enrollments:

@page "{id:int}"
@model ContosoUniversity.Pages.Students.DetailsModel

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

<h2>Details</h2>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd>
            <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>

Se dopo aver incollato il codice il rientro è errato, premere CTRL-K-D per correggerlo.If code indentation is wrong after the code is pasted, press CTRL-K-D to correct it.

Il codice precedente esegue il ciclo nelle entità nella proprietà di navigazione Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Per ogni registrazione, il codice visualizza il titolo del corso e il voto.For each enrollment, it displays the course title and the grade. Il titolo del corso viene recuperato dall'entità Course memorizzata nella proprietà di navigazione Course dell'entità Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Eseguire l'app, selezionare la scheda Students e fare clic sul collegamento Details relativo a uno studente.Run the app, select the Students tab, and click the Details link for a student. Viene visualizzato l'elenco dei corsi e dei voti dello studente selezionato.The list of courses and grades for the selected student is displayed.

Aggiornare la pagina CreateUpdate the Create page

Aggiornare il metodo OnPostAsync in Pages/Students/Create.cshtml.cs con il codice seguente:Update the OnPostAsync method in Pages/Students/Create.cshtml.cs with the following code:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Student.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return null;
}

TryUpdateModelAsyncTryUpdateModelAsync

Esaminare il codice TryUpdateModelAsync:Examine the TryUpdateModelAsync code:


var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
    emptyStudent,
    "student",   // Prefix for form value.
    s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{

Nel codice precedente TryUpdateModelAsync<Student> tenta di aggiornare l'oggetto emptyStudent usando i valori di modulo inviati dalla proprietà PageContext in PageModel.In the preceding code, TryUpdateModelAsync<Student> tries to update the emptyStudent object using the posted form values from the PageContext property in the PageModel. TryUpdateModelAsync aggiorna solo le proprietà elencate (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).TryUpdateModelAsync only updates the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).

Nell'esempio precedente:In the preceding sample:

  • Il secondo argomento ("student", // Prefix) è il prefisso usato per cercare i valori.The second argument ("student", // Prefix) is the prefix uses to look up values. Non viene fatta distinzione tra maiuscole e minuscole.It's not case sensitive.
  • I valori di modulo inviati sono convertiti nei tipi nel modello Student usando l'associazione di modelli.The posted form values are converted to the types in the Student model using model binding.

OverpostingOverposting

L'uso di TryUpdateModel per l'aggiornamento dei campi con i valori inviati è una procedura di sicurezza consigliata poiché impedisce l'overposting.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Ad esempio, si supponga che l'entità Student includa una proprietà Secret che la pagina Web non deve aggiornare o aggiungere:For example, suppose the Student entity includes a Secret property that this web page shouldn't update or add:

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

Anche se l'app non include un campo Secret nella pagina Razor Create o Update, un hacker potrebbe impostare il valore Secret tramite overposting.Even if the app doesn't have a Secret field on the create/update Razor Page, a hacker could set the Secret value by overposting. Un hacker potrebbe usare uno strumento come Fiddler oppure scrivere codice JavaScript per inviare un valore di modulo Secret.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Il codice originale non limita i campi usati dallo strumento di associazione di modelli durante la creazione di un'istanza di Student.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

Qualsiasi valore specificato dall'hacker per il campo di modulo Secret viene aggiornato nel database.Whatever value the hacker specified for the Secret form field is updated in the DB. L'immagine seguente illustra lo strumento Fiddler che aggiunge il campo Secret (con il valore "OverPost") ai valori di modulo inviati.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler aggiunge il campo Secret

Il valore "OverPost" è stato aggiunto alla proprietà Secret della riga inserita.The value "OverPost" is successfully added to the Secret property of the inserted row. La progettazione app non prevedeva in alcun modo che la proprietà Secret venisse impostata con la pagina Create.The app designer never intended the Secret property to be set with the Create page.

Modello di visualizzazioneView model

Un modello di visualizzazione contiene in genere un subset delle proprietà incluse nel modello usato dall'applicazione.A view model typically contains a subset of the properties included in the model used by the application. Il modello di applicazione è spesso chiamato modello di dominio.The application model is often called the domain model. Il modello di dominio contiene in genere tutte le proprietà richieste dall'entità corrispondente nel database.The domain model typically contains all the properties required by the corresponding entity in the DB. Il modello di visualizzazione contiene solo le proprietà necessarie per il livello di interfaccia utente, ad esempio la pagina Create.The view model contains only the properties needed for the UI layer (for example, the Create page). Oltre al modello di visualizzazione, alcune app usano un modello di associazione o un modello di input per passare i dati dalla classe del modello di pagina di Razor Pages al browser e viceversa.In addition to the view model, some apps use a binding model or input model to pass data between the Razor Pages page model class and the browser. Si consideri il modello di visualizzazione Student seguente:Consider the following Student view model:

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

I modelli di visualizzazione rappresentano un altro metodo per impedire l'overposting.View models provide an alternative way to prevent overposting. Il modello di visualizzazione contiene solo le proprietà da visualizzare o aggiornare.The view model contains only the properties to view (display) or update.

Il codice seguente usa il modello di visualizzazione StudentVM per creare un nuovo studente:The following code uses the StudentVM view model to create a new student:

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

Il metodo SetValues imposta i valori di questo oggetto leggendo i valori di un altro oggetto PropertyValues.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues usa la corrispondenza dei nomi di proprietà.SetValues uses property name matching. Poiché il tipo di modello di visualizzazione non deve essere correlato al tipo di modello, è sufficiente che abbia proprietà corrispondenti.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

Se si usa StudentVM è necessario che CreateVM.cshtml venga aggiornato per l'uso di StudentVM anziché Student.Using StudentVM requires CreateVM.cshtml be updated to use StudentVM rather than Student.

In Razor Pages la classe derivata PageModel è il modello di visualizzazione.In Razor Pages, the PageModel derived class is the view model.

Aggiornare la pagina Edit (Modifica)Update the Edit page

Aggiornare il modello di pagina per la pagina Edit.Update the page model for the Edit page. Le modifiche principali sono evidenziate:The major changes are highlighted:

public class EditModel : PageModel
{
    private readonly SchoolContext _context;

    public EditModel(SchoolContext context)
    {
        _context = context;
    }

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

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

        Student = await _context.Student.FindAsync(id);

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

    public async Task<IActionResult> OnPostAsync(int? id)
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var studentToUpdate = await _context.Student.FindAsync(id);

        if (await TryUpdateModelAsync<Student>(
            studentToUpdate,
            "student",
            s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
        {
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
        }

        return Page();
    }
}

Le modifiche al codice sono simili alla pagina Create con alcune eccezioni:The code changes are similar to the Create page with a few exceptions:

  • OnPostAsync include un parametro id facoltativo.OnPostAsync has an optional id parameter.
  • Lo studente corrente viene recuperato dal database senza creare uno studente vuoto.The current student is fetched from the DB, rather than creating an empty student.
  • FirstOrDefaultAsync è stato sostituito con FindAsync.FirstOrDefaultAsync has been replaced with FindAsync. FindAsync è una scelta ottimale quando si seleziona un'entità dalla chiave primaria.FindAsync is a good choice when selecting an entity from the primary key. Per altre informazioni, vedere FindAsync.See FindAsync for more information.

Testare le pagine Edit e CreateTest the Edit and Create pages

Creare e modificare alcune entità studente.Create and edit a few student entities.

Stati di entitàEntity States

Il contesto del database tiene traccia della sincronizzazione delle entità in memoria con le righe corrispondenti nel database.The DB context keeps track of whether entities in memory are in sync with their corresponding rows in the DB. Le informazioni di sincronizzazione del contesto del database determinano le operazioni eseguite quando viene chiamato SaveChangesAsync.The DB context sync information determines what happens when SaveChangesAsync is called. Ad esempio, quando una nuova entità viene passata al metodo AddAsync, lo stato dell'entità viene impostato su Added.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Quando viene chiamato SaveChangesAsync, il contesto del database genera un comando SQL INSERT.When SaveChangesAsync is called, the DB context issues a SQL INSERT command.

Un'entità può trovarsi in uno dei seguenti stati:An entity may be in one of the following states:

  • Added: l'entità non esiste ancora nel database.Added: The entity doesn't yet exist in the DB. Il metodo SaveChanges genera un'istruzione INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged: non è necessario salvare alcuna modifica con questa entità.Unchanged: No changes need to be saved with this entity. Un'entità ha questo stato quando viene letta dal database.An entity has this status when it's read from the DB.

  • Modified: sono stati modificati alcuni o tutti i valori di proprietà dell'entità.Modified: Some or all of the entity's property values have been modified. Il metodo SaveChanges genera un'istruzione UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted: l'entità è stata contrassegnata per l'eliminazione.Deleted: The entity has been marked for deletion. Il metodo SaveChanges genera un'istruzione DELETE.The SaveChanges method issues a DELETE statement.

  • Detached: l'entità non viene registrata dal contesto del database.Detached: The entity isn't being tracked by the DB context.

In un'applicazione desktop le modifiche dello stato vengono in genere impostate automaticamente.In a desktop app, state changes are typically set automatically. Viene letta un'entità, vengono apportate le modifiche e lo stato dell'entità viene modificato automaticamente in Modified.An entity is read, changes are made, and the entity state to automatically be changed to Modified. La chiamata di SaveChanges genera un'istruzione SQL UPDATE che aggiorna solo le proprietà modificate.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

In un'app Web il DbContext che legge un'entità e visualizza i dati viene eliminato dopo il rendering di una pagina.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Quando viene chiamato il metodo OnPostAsync di una pagina, viene effettuata una nuova richiesta Web con una nuova istanza di DbContext.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. La rilettura dell'entità nel nuovo contesto simula l'elaborazione desktop.Re-reading the entity in that new context simulates desktop processing.

Aggiornare la pagina Delete (Elimina)Update the Delete page

In questa sezione viene aggiunto un codice per implementare un messaggio di errore personalizzato quando la chiamata a SaveChanges ha esito negativo.In this section, code is added to implement a custom error message when the call to SaveChanges fails. Aggiungere una stringa per i possibili messaggi di errore:Add a string to contain possible error messages:

public class DeleteModel : PageModel
{
    private readonly SchoolContext _context;

    public DeleteModel(SchoolContext context)
    {
        _context = context;
    }

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

Sostituire il metodo OnGetAsync con il codice seguente:Replace the OnGetAsync method with the following code:

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

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

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

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

    return Page();
}

Il codice precedente contiene il parametro facoltativo saveChangesError.The preceding code contains the optional parameter saveChangesError. saveChangesError indica se il metodo è stato chiamato dopo un errore di eliminazione dell'oggetto Student.saveChangesError indicates whether the method was called after a failure to delete the student object. L'operazione di eliminazione potrebbe non riuscire a causa di problemi di rete temporanei.The delete operation might fail because of transient network problems. Gli errori di rete temporanei sono più probabili nel cloud.Transient network errors are more likely in the cloud. saveChangesError ha valore false quando OnGetAsync della pagina Delete viene chiamato dall'interfaccia utente.saveChangesErroris false when the Delete page OnGetAsync is called from the UI. Quando OnGetAsync viene chiamato da OnPostAsync (perché l'operazione di eliminazione ha avuto esito negativo), il parametro saveChangesError ha valore true.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

Metodo OnPostAsync delle pagine DeleteThe Delete pages OnPostAsync method

Sostituire OnPostAsync con il codice seguente:Replace the OnPostAsync with the following code:

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

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

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

    try
    {
        _context.Student.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 });
    }
}

Il codice precedente recupera l'entità selezionata, quindi chiama il metodo Remove per impostare lo stato dell'entità su Deleted.The preceding code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Quando viene chiamato SaveChanges, viene generato un comando SQL DELETE.When SaveChanges is called, a SQL DELETE command is generated. Se Remove ha esito negativo:If Remove fails:

  • Viene rilevata l'eccezione di database.The DB exception is caught.
  • Il metodo OnGetAsync delle pagine Delete viene chiamato con saveChangesError=true.The Delete pages OnGetAsync method is called with saveChangesError=true.

Aggiornare la pagina Razor DeleteUpdate the Delete Razor Page

Aggiungere il messaggio di errore evidenziato seguente alla pagina Razor Delete.Add the following highlighted error message to the Delete Razor Page.

@page "{id:int}"
@model ContosoUniversity.Pages.Students.DeleteModel

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

<h2>Delete</h2>

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

<h3>Are you sure you want to delete this?</h3>
<div>

Eseguire il test di Delete.Test Delete.

Errori comuniCommon errors

Students/Index o altri collegamenti non funzionano:Students/Index or other links don't work:

Verificare che la pagina Razor contenga la direttiva @page corretta.Verify the Razor Page contains the correct @page directive. Ad esempio, la pagina Razor Students/Index non deve contenere un modello di route:For example, The Students/Index Razor Page should not contain a route template:

@page "{id:int}"

Ogni pagina Razor deve includere la direttiva @page.Each Razor Page must include the @page directive.

Risorse aggiuntiveAdditional resources