Pages Razor avec EF Core dans ASP.NET Core - CRUD - 2 sur 8Razor Pages with EF Core in ASP.NET Core - CRUD - 2 of 8

Par Tom Dykstra, Jon P Smith et Rick AndersonBy Tom Dykstra, Jon P Smith, and Rick Anderson

L’application web Contoso University montre comment créer des applications web Pages Razor avec EF Core et Visual Studio.The Contoso University web app demonstrates how to create Razor Pages web apps using EF Core and Visual Studio. Pour obtenir des informations sur la série de tutoriels, consultez le premier tutoriel.For information about the tutorial series, see the first tutorial.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez l’application finale et comparez ce code à celui que vous avez créé en suivant le tutoriel.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.

Dans ce didacticiel, nous allons examiner et personnaliser le code CRUD (créer, lire, mettre à jour, supprimer) généré automatiquement.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Aucun référentielNo repository

Certains développeurs utilisent une couche de service ou un modèle de référentiel pour créer une couche d’abstraction entre l’interface utilisateur (Razor Pages) et la couche d’accès aux données.Some developers use a service layer or repository pattern to create an abstraction layer between the UI (Razor Pages) and the data access layer. Ce n’est pas le cas de ce tutoriel.This tutorial doesn't do that. Pour que ce tutoriel soit moins complexe et traite exclusivement d’EF Core, le code EF Core est directement ajouté aux classes de modèle de page.To minimize complexity and keep the tutorial focused on EF Core, EF Core code is added directly to the page model classes.

Mettre à jour la page DetailsUpdate the Details page

Le code généré automatiquement pour les pages Students n’inclut pas les données d’inscription (« enrollment »).The scaffolded code for the Students pages doesn't include enrollment data. Dans cette section, vous allez ajouter des inscriptions à la page Details.In this section, you add enrollments to the Details page.

Lire les inscriptionsRead enrollments

Pour afficher les données d’inscription d’un étudiant dans la page, vous devez les lire.To display a student's enrollment data on the page, you need to read it. Le code généré automatiquement dans Pages/Students/Details.cshtml.cs lit uniquement les données d’étudiant, sans les données d’inscription :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();
}

Remplacez la méthode OnGetAsync par le code suivant pour lire les données d’inscription de l’étudiant sélectionné.Replace the OnGetAsync method with the following code to read enrollment data for the selected student. Les modifications sont mises en surbrillance.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();
}

Les méthodes Include et ThenInclude forcent le contexte à charger la propriété de navigation Student.Enrollments et, dans chaque inscription, la propriété de navigation Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Ces méthodes sont examinées en détail dans le tutoriel Lecture de données associées.These methods are examined in detail in the Reading related data tutorial.

La méthode AsNoTracking améliore les performances dans les scénarios où les entités retournées ne sont pas mises à jour dans le contexte actuel.The AsNoTracking method improves performance in scenarios where the entities returned are not updated in the current context. Le sujet AsNoTracking est abordé plus loin dans ce didacticiel.AsNoTracking is discussed later in this tutorial.

Afficher les inscriptionsDisplay enrollments

Remplacez le code dans Pages/Students/Details.cshtml par le code suivant pour afficher une liste d’inscriptions.Replace the code in Pages/Students/Details.cshtml with the following code to display a list of enrollments. Les modifications sont mises en surbrillance.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>

Le code précédent effectue une itération sur les entités dans la propriété de navigation Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Pour chaque inscription, il affiche le titre du cours et le niveau.For each enrollment, it displays the course title and the grade. Le titre du cours est récupéré à partir de l’entité de cours qui est stockée dans la propriété de navigation Course de l’entité Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Exécutez l’application, sélectionnez l'onglet Students, puis cliquez sur le lien Details pour un étudiant.Run the app, select the Students tab, and click the Details link for a student. La liste des cours et les notes de l’étudiant sélectionné s’affiche.The list of courses and grades for the selected student is displayed.

Méthodes pour lire une entitéWays to read one entity

Le code généré utilise FirstOrDefaultAsync pour lire une entité.The generated code uses FirstOrDefaultAsync to read one entity. Cette méthode retourne la valeur Null si rien n’est trouvé ; sinon, elle retourne la première ligne trouvée qui répond aux critères de filtre de requête.This method returns null if nothing is found; otherwise, it returns the first row found that satisfies the query filter criteria. FirstOrDefaultAsync est généralement un meilleur choix que les autres solutions suivantes :FirstOrDefaultAsync is generally a better choice than the following alternatives:

  • SingleOrDefaultAsync – Lève une exception si plusieurs entités répondent au filtre de requête.SingleOrDefaultAsync - Throws an exception if there's more than one entity that satisfies the query filter. Pour déterminer si plusieurs lignes peuvent être retournées par la requête, SingleOrDefaultAsync tente de récupérer plusieurs lignes.To determine if more than one row could be returned by the query, SingleOrDefaultAsync tries to fetch multiple rows. Ce travail supplémentaire est inutile si la requête ne peut retourner qu’une seule entité, comme quand elle effectue une recherche sur une clé unique.This extra work is unnecessary if the query can only return one entity, as when it searches on a unique key.
  • FindAsync – Recherche une entité avec la clé primaire.FindAsync - Finds an entity with the primary key (PK). Si une entité avec la clé primaire est suivie par le contexte, elle est retournée sans qu’aucune requête soit envoyée à la base de données.If an entity with the PK is being tracked by the context, it's returned without a request to the database. Cette méthode est optimisée pour la recherche d’une seule entité, mais vous ne pouvez pas appeler Include avec FindAsync.This method is optimized to look up a single entity, but you can't call Include with FindAsync. Par conséquent, si des données associées sont nécessaires, FirstOrDefaultAsync est le meilleur choix.So if related data is needed, FirstOrDefaultAsync is the better choice.

Données de route/chaîne de requêteRoute data vs. query string

L’URL de la page Details est https://localhost:<port>/Students/Details?id=1.The URL for the Details page is https://localhost:<port>/Students/Details?id=1. La valeur de clé primaire de l’entité se trouve dans la chaîne de requête.The entity's primary key value is in the query string. Certains développeurs préfèrent passer la valeur de clé dans des données de route : https://localhost:<port>/Students/Details/1.Some developers prefer to pass the key value in route data: https://localhost:<port>/Students/Details/1. Pour plus d'informations, consultez Mettre à jour le code généré.For more information, see Update the generated code.

Mettre à jour la page CreateUpdate the Create page

Le code OnPostAsync généré automatiquement pour la page Create est vulnérable aux sur-publications.The scaffolded OnPostAsync code for the Create page is vulnerable to overposting. Remplacez la méthode OnPostAsync dans Pages/Students/Create.cshtml.cs par le code suivant.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

Le code précédent crée un objet Student, puis utilise des champs de formulaire publiés pour mettre à jour les propriétés de l’objet Student.The preceding code creates a Student object and then uses posted form fields to update the Student object's properties. La méthode TryUpdateModelAsync :The TryUpdateModelAsync method:

  • Utilise les valeurs de formulaire publiées de la propriété PageContext de PageModel.Uses the posted form values from the PageContext property in the PageModel.
  • Met à jour uniquement les propriétés listées (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).Updates only the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).
  • Recherche les champs de formulaire dotés d’un préfixe « Student ».Looks for form fields with a "student" prefix. Par exemple, Student.FirstMidName.For example, Student.FirstMidName. Il ne respecte pas la casse.It's not case sensitive.
  • Utilise le système de liaison de modèles pour convertir les valeurs de formulaire de chaînes en types dans le modèle Student.Uses the model binding system to convert form values from strings to the types in the Student model. Par exemple, EnrollmentDate doit être converti en DateTime.For example, EnrollmentDate has to be converted to DateTime.

Exécutez l’application, puis créez une entité Student pour tester la page Create.Run the app, and create a student entity to test the Create page.

Sur-publicationOverposting

L’utilisation de TryUpdateModel pour mettre à jour des champs avec des valeurs publiées est une bonne pratique de sécurité, car cela empêche la sur-publication.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Par exemple, supposez que l’entité Student comprend une propriété Secret que cette page web ne doit pas mettre à jour ou ajouter :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; }
}

Même si l’application n’a pas de champ Secret dans la page Razor de création ou de mise à jour, un pirate pourrait définir la valeur Secret par sur-publication.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 pirate pourrait utiliser un outil tel que Fiddler, ou écrire du JavaScript, pour publier une valeur de formulaire Secret.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Le code d’origine ne limite pas les champs que le classeur de modèles utilise quand il crée une instance de Student.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

La valeur spécifiée par le pirate pour le champ de formulaire Secret, quelle qu’elle soit, est mise à jour dans la base de données.Whatever value the hacker specified for the Secret form field is updated in the database. L’illustration suivante montre l’outil Fiddler en train d’ajouter le champ Secret (avec la valeur « OverPost ») aux valeurs du formulaire envoyé.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler ajoutant un champ Secret

La valeur « OverPost » est ajoutée avec succès à la propriété Secret de la ligne insérée.The value "OverPost" is successfully added to the Secret property of the inserted row. Cela se produit même si le concepteur de l’application n’avait jamais prévu que la propriété Secret serait définie avec la page Create.That happens even though the app designer never intended the Secret property to be set with the Create page.

Modèle d’affichageView model

Les modèles d’affichage fournissent une alternative pour empêcher la sur-publication.View models provide an alternative way to prevent overposting.

Le modèle d’application est souvent appelé modèle de domaine.The application model is often called the domain model. En règle générale, le modèle de domaine contient toutes les propriétés requises par l’entité correspondante dans la base de données.The domain model typically contains all the properties required by the corresponding entity in the database. Le modèle de vue contient uniquement les propriétés nécessaires à l’interface utilisateur pour laquelle il est utilisé (par exemple, la page Create).The view model contains only the properties needed for the UI that it is used for (for example, the Create page).

En plus du modèle d’affichage, certaines applications utilisent un modèle de liaison ou d’entrée pour transmettre des données entre la classe de modèles de pages de Pages Razor et le navigateur.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.

Considérez le modèle d’affichage Student suivant :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; }
    }
}

Le code suivant utilise le modèle d’affichage StudentVM pour créer un étudiant :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");
}

La méthode SetValues définit les valeurs de cet objet en lisant les valeurs d’un autre objet PropertyValues.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues utilise la correspondance de nom de propriété.SetValues uses property name matching. Le type de modèle d’affichage ne doit pas nécessairement être lié au type de modèle. Il doit simplement avoir des propriétés qui correspondent.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

L’utilisation de StudentVM exige que Create.cshtml soit mis à jour pour utiliser StudentVM plutôt que Student.Using StudentVM requires Create.cshtml be updated to use StudentVM rather than Student.

Mettre à jour la page EditUpdate the Edit page

Dans Pages/Students/Edit.cshtml.cs, remplacez les méthodes OnGetAsync et OnPostAsync par le code suivant.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();
}

Les modifications de code sont semblables à celles de la page Create, à quelques exceptions près :The code changes are similar to the Create page with a few exceptions:

  • FirstOrDefaultAsync a été remplacée par FindAsync.FirstOrDefaultAsync has been replaced with FindAsync. Quand vous n’êtes pas tenu d’inclure des données associées, FindAsync est plus efficace.When you don't have to include related data, FindAsync is more efficient.
  • OnPostAsync contient un paramètre id.OnPostAsync has an id parameter.
  • Plutôt que de créer un étudiant vide, l’étudiant actuel est récupéré à partir de la base de données.The current student is fetched from the database, rather than creating an empty student.

Exécutez l’application et testez-la en créant et modifiant un étudiant.Run the app, and test it by creating and editing a student.

États des entitésEntity States

Le contexte de base de données effectue un suivi pour déterminer si les entités en mémoire sont synchronisées avec les lignes correspondantes de la base de données.The database context keeps track of whether entities in memory are in sync with their corresponding rows in the database. Ces informations de suivi déterminent ce qui se passe quand SaveChangesAsync est appelé.This tracking information determines what happens when SaveChangesAsync is called. Par exemple, quand une nouvelle entité est passée à la méthode AddAsync, l’état de cette entité est défini sur Added.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Quand SaveChangesAsync est appelé, le contexte de base de données émet une commande SQL INSERT.When SaveChangesAsync is called, the database context issues a SQL INSERT command.

Une entité peut être dans l’un des états suivants :An entity may be in one of the following states:

  • Added: L’entité n’existe pas encore dans la base de données.Added: The entity doesn't yet exist in the database. La méthode SaveChanges émet une instruction INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged: Aucune modification ne doit être enregistrée avec cette entité.Unchanged: No changes need to be saved with this entity. Une entité est dans cet état quand elle est lue à partir de la base de données.An entity has this status when it's read from the database.

  • Modified: Tout ou partie des valeurs de propriété de l’entité ont été modifiées.Modified: Some or all of the entity's property values have been modified. La méthode SaveChanges émet une instruction UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted: L’entité a été marquée pour suppression.Deleted: The entity has been marked for deletion. La méthode SaveChanges émet une instruction DELETE.The SaveChanges method issues a DELETE statement.

  • Detached: L’entité n’est pas suivie par le contexte de base de données.Detached: The entity isn't being tracked by the database context.

Dans une application de bureau, les changements d’état sont généralement définis automatiquement.In a desktop app, state changes are typically set automatically. Une entité est lue, des modifications sont apportées et l’état d’entité passe automatiquement à Modified.An entity is read, changes are made, and the entity state is automatically changed to Modified. L’appel de SaveChanges génère une instruction SQL UPDATE qui met à jour uniquement les propriétés modifiées.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

Dans une application web, le DbContext qui lit une entité et affiche les données est supprimé après le rendu d’une page.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Quand la méthode OnPostAsync d’une page est appelée, une nouvelle requête web est faite avec une nouvelle instance du DbContext.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. La relecture de l’entité dans ce nouveau contexte simule le traitement de bureau.Rereading the entity in that new context simulates desktop processing.

Mettre à jour la page DeleteUpdate the Delete page

Dans cette section, vous allez implémenter un message d’erreur personnalisé quand l’appel à SaveChanges échoue.In this section, you implement a custom error message when the call to SaveChanges fails.

Remplacez le code dans Pages/Students/Delete.cshtml.cs par le code suivant.Replace the code in Pages/Students/Delete.cshtml.cs with the following code. Les modifications (autres que le nettoyage des instructions using) sont mises en surbrillance.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 });
            }
        }
    }
}

Le code précédent ajoute le paramètre facultatif saveChangesError à la signature de méthode OnGetAsync.The preceding code adds the optional parameter saveChangesError to the OnGetAsync method signature. saveChangesError indique si la méthode a été appelée après un échec de suppression de l’objet Student.saveChangesError indicates whether the method was called after a failure to delete the student object. L’opération de suppression peut échouer en raison de problèmes réseau temporaires.The delete operation might fail because of transient network problems. Vous avez plus de chances de rencontrer des erreurs réseau temporaires quand la base de données est dans le cloud.Transient network errors are more likely when the database is in the cloud. Le paramètre saveChangesError a la valeur false quand la méthode OnGetAsync de la page Delete est appelée à partir de l’interface utilisateur.The saveChangesError parameter is false when the Delete page OnGetAsync is called from the UI. Quand OnGetAsync est appelée par OnPostAsync (car l’opération de suppression a échoué), le paramètre saveChangesError a la valeur true.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

La méthode OnPostAsync récupère l’entité sélectionnée, puis appelle la méthode Remove pour définir l’état de l’entité sur Deleted.The OnPostAsync method retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Lorsque SaveChanges est appelée, une commande SQL DELETE est générée.When SaveChanges is called, a SQL DELETE command is generated. Si Remove échoue :If Remove fails:

  • L’exception de la base de données est interceptée.The database exception is caught.
  • La méthode OnGetAsync des pages est appelée avec saveChangesError=true.The Delete pages OnGetAsync method is called with saveChangesError=true.

Ajoutez un message d’erreur à la page 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>

Exécutez l’application et supprimez un étudiant pour tester la page Delete.Run the app and delete a student to test the Delete page.

Étapes suivantesNext steps

Dans ce didacticiel, nous allons examiner et personnaliser le code CRUD (créer, lire, mettre à jour, supprimer) généré automatiquement.In this tutorial, the scaffolded CRUD (create, read, update, delete) code is reviewed and customized.

Pour que ces tutoriels soient moins complexes et traitent exclusivement d’EF Core, nous avons utilisé le code EF Core dans les modèles de page.To minimize complexity and keep these tutorials focused on EF Core, EF Core code is used in the page models. Certains développeurs utilisent un modèle de référentiel ou de couche de service pour créer une couche d’abstraction entre l’interface utilisateur (Pages Razor) et la couche d’accès aux données.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.

Dans ce tutoriel, nous examinons les pages Create, Edit, Delete et Details de Razor Pages, qui sont dans le dossier Students.In this tutorial, the Create, Edit, Delete, and Details Razor Pages in the Students folder are examined.

Le code généré automatiquement utilise le modèle suivant pour les pages Create, Edit et Delete :The scaffolded code uses the following pattern for Create, Edit, and Delete pages:

  • Obtenir et afficher les données demandées avec la méthode HTTP GET OnGetAsync.Get and display the requested data with the HTTP GET method OnGetAsync.
  • Enregistrer les modifications dans les données avec la méthode HTTP POST OnPostAsync.Save changes to the data with the HTTP POST method OnPostAsync.

Les pages Index et Details obtiennent et affichent les données demandées avec la méthode HTTP GET OnGetAsync.The Index and Details pages get and display the requested data with the HTTP GET method OnGetAsync

SingleOrDefaultAsync vs. FirstOrDefaultAsyncSingleOrDefaultAsync vs. FirstOrDefaultAsync

Le code généré utilise FirstOrDefaultAsync, qui est généralement préféré à SingleOrDefaultAsync.The generated code uses FirstOrDefaultAsync, which is generally preferred over SingleOrDefaultAsync.

FirstOrDefaultAsync est plus efficace que SingleOrDefaultAsync pour récupérer une entité :FirstOrDefaultAsync is more efficient than SingleOrDefaultAsync at fetching one entity:

  • Sauf si le code doit vérifier que la requête ne retourne pas plusieurs entités.Unless the code needs to verify that there's not more than one entity returned from the query.
  • SingleOrDefaultAsync récupère davantage de données et effectue un travail inutile.SingleOrDefaultAsync fetches more data and does unnecessary work.
  • SingleOrDefaultAsync lève une exception si plusieurs entités correspondent à la partie filtre.SingleOrDefaultAsync throws an exception if there's more than one entity that fits the filter part.
  • FirstOrDefaultAsync ne lève pas d’exception si plusieurs entités correspondent à la partie filtre.FirstOrDefaultAsync doesn't throw if there's more than one entity that fits the filter part.

FindAsyncFindAsync

Dans une grande partie du code généré automatiquement, vous pouvez utiliser FindAsync à la place de FirstOrDefaultAsync.In much of the scaffolded code, FindAsync can be used in place of FirstOrDefaultAsync.

FindAsync:FindAsync:

  • Recherche une entité avec la clé primaire.Finds an entity with the primary key (PK). Si une entité avec la clé primaire est suivie par le contexte, elle est retournée sans qu’une requête soit envoyée à la base de données.If an entity with the PK is being tracked by the context, it's returned without a request to the DB.
  • Est simple et concise.Is simple and concise.
  • Est optimisée pour rechercher une entité unique.Is optimized to look up a single entity.
  • Peut avoir des avantages de performances dans certaines situations, mais c’est rarement le cas pour les applications web standard.Can have perf benefits in some situations, but that rarely happens for typical web apps.
  • Utilise implicitement FirstAsync au lieu de SingleAsync.Implicitly uses FirstAsync instead of SingleAsync.

Toutefois, si vous voulez exécuter Include sur d’autres entités, FindAsync n’est plus approprié.But if you want to Include other entities, then FindAsync is no longer appropriate. Cela signifie que vous devez peut-être abandonner FindAsync et passer à une requête à mesure que votre application progresse.This means that you may need to abandon FindAsync and move to a query as your app progresses.

Personnaliser la page DetailsCustomize the Details page

Accédez à la page Pages/Students.Browse to Pages/Students page. Les liens Edit, Details, et Delete sont générés par le Tag helper anchor dans le fichier Pages/Student/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>

Exécutez l’application et sélectionnez un lien Details.Run the app and select a Details link. L’URL est au format http://localhost:5000/Students/Details?id=2.The URL is of the form http://localhost:5000/Students/Details?id=2. L’ID d’étudiant est transmis à l’aide d’une chaîne de requête (?id=2).The Student ID is passed using a query string (?id=2).

Mettez à jour les Pages Razor Edit, Details et Delete pour utiliser le modèle de route "{id:int}".Update the Edit, Details, and Delete Razor Pages to use the "{id:int}" route template. Remplacez la directive de chacune de ces pages (@page) par @page "{id:int}".Change the page directive for each of these pages from @page to @page "{id:int}".

Une demande à la page avec le modèle de route «{id:int}» qui n'inclut pas une valeur de route avec un entier retourne une erreur HTTP 404 (not found).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. Par exemple, http://localhost:5000/Students/Details retourne une erreur 404.For example, http://localhost:5000/Students/Details returns a 404 error. Pour que l’ID soit facultatif, ajoutez ? à la contrainte de route :To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Exécutez l’application, cliquez sur un lien Détails et vérifiez que l’URL passe l’ID en tant que données de 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).

Ne changez pas @page en @page "{id:int}" globalement, cela casserait les liens vers les pages Home et Create.Don't globally change @page to @page "{id:int}", doing so breaks the links to the Home and Create pages.

Le code généré automatiquement pour la page Index des étudiants n’inclut pas la propriété Enrollments.The scaffolded code for the Students Index page doesn't include the Enrollments property. Dans cette section, le contenu de la collection Enrollments s’affiche dans la page Details.In this section, the contents of the Enrollments collection is displayed in the Details page.

Le méthode OnGetAsync de Pages/Students/Details.cshtml.cs utilise la méthode FirstOrDefaultAsync pour récupérer une seule entité Student.The OnGetAsync method of Pages/Students/Details.cshtml.cs uses the FirstOrDefaultAsync method to retrieve a single Student entity. Ajoutez le code en surbrillance suivant :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();
}

Les méthodes Include et ThenInclude forcent le contexte à charger la propriété de navigation Student.Enrollments et, dans chaque inscription, la propriété de navigation Enrollment.Course.The Include and ThenInclude methods cause the context to load the Student.Enrollments navigation property, and within each enrollment the Enrollment.Course navigation property. Ces méthodes sont examinées en détail dans le tutoriel sur la lecture des données associées.These methods are examined in detail in the reading-related data tutorial.

La méthode AsNoTracking améliore les performances dans les scénarios où les entités retournées ne sont pas mises à jour dans le contexte actuel.The AsNoTracking method improves performance in scenarios when the entities returned are not updated in the current context. Le sujet AsNoTracking est abordé plus loin dans ce didacticiel.AsNoTracking is discussed later in this tutorial.

Ouvrez Pages/Students/Details.cshtml.Open Pages/Students/Details.cshtml. Ajoutez le code en surbrillance suivant pour afficher la liste des inscriptions :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>

Si la mise en retrait du code est incorrecte, une fois que le code est collé, appuyez sur CTRL + K + D pour résoudre ce problème.If code indentation is wrong after the code is pasted, press CTRL-K-D to correct it.

Le code précédent effectue une itération sur les entités dans la propriété de navigation Enrollments.The preceding code loops through the entities in the Enrollments navigation property. Pour chaque inscription, il affiche le titre du cours et le niveau.For each enrollment, it displays the course title and the grade. Le titre du cours est récupéré à partir de l’entité de cours qui est stockée dans la propriété de navigation Course de l’entité Enrollments.The course title is retrieved from the Course entity that's stored in the Course navigation property of the Enrollments entity.

Exécutez l’application, sélectionnez l'onglet Students, puis cliquez sur le lien Details pour un étudiant.Run the app, select the Students tab, and click the Details link for a student. La liste des cours et les notes de l’étudiant sélectionné s’affiche.The list of courses and grades for the selected student is displayed.

Mettre à jour la page CreateUpdate the Create page

Mettez à jour la méthode OnPostAsync dans Pages/Students/Create.cshtml.cs avec le code suivant :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

Examinez le code 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))
{

Dans le code précédent, TryUpdateModelAsync<Student> tente de mettre à jour l’objet emptyStudent en utilisant les valeurs de formulaire publiées à partir de la propriété PageContext dans le 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 met uniquement à jour les propriétés répertoriées (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).TryUpdateModelAsync only updates the properties listed (s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate).

Dans l’exemple précédent :In the preceding sample:

  • Le deuxième argument ("student", // Prefix) est le préfixe utilisé pour rechercher des valeurs.The second argument ("student", // Prefix) is the prefix uses to look up values. Il ne respecte pas la casse.It's not case sensitive.
  • Les valeurs de formulaire publiées sont converties vers les types dans le modèle Student à l’aide de la liaison de modèle.The posted form values are converted to the types in the Student model using model binding.

Sur-publicationOverposting

L’utilisation de TryUpdateModel pour mettre à jour des champs avec des valeurs publiées est une bonne pratique de sécurité, car cela empêche la sur-publication.Using TryUpdateModel to update fields with posted values is a security best practice because it prevents overposting. Par exemple, supposez que l’entité Student comprend une propriété Secret que cette page web ne doit pas mettre à jour ou ajouter :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; }
}

Même si l’application n’a pas de champ Secret dans la page Razor de création/mise à jour, un pirate pourrait définir la valeur Secret par sur-publication.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 pirate pourrait utiliser un outil tel que Fiddler, ou écrire du JavaScript, pour publier une valeur de formulaire Secret.A hacker could use a tool such as Fiddler, or write some JavaScript, to post a Secret form value. Le code d’origine ne limite pas les champs que le classeur de modèles utilise quand il crée une instance de Student.The original code doesn't limit the fields that the model binder uses when it creates a Student instance.

La valeur spécifiée par le pirate pour le champ de formulaire Secret, quelle qu’elle soit, est mise à jour dans la base de données.Whatever value the hacker specified for the Secret form field is updated in the DB. L’illustration suivante montre l’outil Fiddler en train d’ajouter le champ Secret (avec la valeur « OverPost ») aux valeurs de formulaire publiées.The following image shows the Fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.

Fiddler ajoutant un champ Secret

La valeur « OverPost » est ajoutée avec succès à la propriété Secret de la ligne insérée.The value "OverPost" is successfully added to the Secret property of the inserted row. Le concepteur de l’application n’a jamais prévu que la propriété Secret soit définie avec la page Create.The app designer never intended the Secret property to be set with the Create page.

Modèle d’affichageView model

Un modèle d’affichage contient généralement un sous-ensemble des propriétés incluses dans le modèle utilisé par l’application.A view model typically contains a subset of the properties included in the model used by the application. Le modèle d’application est souvent appelé modèle de domaine.The application model is often called the domain model. En règle générale, le modèle de domaine contient toutes les propriétés requises par l’entité correspondante dans la base de données.The domain model typically contains all the properties required by the corresponding entity in the DB. Le modèle d’affichage contient uniquement les propriétés nécessaires pour la couche d’interface utilisateur (par exemple, la page Create).The view model contains only the properties needed for the UI layer (for example, the Create page). En plus du modèle d’affichage, certaines applications utilisent un modèle de liaison ou d’entrée pour transmettre des données entre la classe de modèles de pages de Pages Razor et le navigateur.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. Considérez le modèle d’affichage Student suivant :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; }
    }
}

Les modèles d’affichage fournissent une alternative pour empêcher la sur-publication.View models provide an alternative way to prevent overposting. Le modèle d’affichage contient uniquement les propriétés à afficher ou à mettre à jour.The view model contains only the properties to view (display) or update.

Le code suivant utilise le modèle d’affichage StudentVM pour créer un étudiant :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");
}

La méthode SetValues définit les valeurs de cet objet en lisant les valeurs d’un autre objet PropertyValues.The SetValues method sets the values of this object by reading values from another PropertyValues object. SetValues utilise la correspondance de nom de propriété.SetValues uses property name matching. Le type de modèle d’affichage ne doit pas nécessairement être lié au type de modèle. Il doit simplement avoir des propriétés qui correspondent.The view model type doesn't need to be related to the model type, it just needs to have properties that match.

L’utilisation de StudentVM exige que CreateVM.cshtml soit mis à jour pour utiliser StudentVM plutôt que Student.Using StudentVM requires CreateVM.cshtml be updated to use StudentVM rather than Student.

Dans les pages Razor, la classe dérivée PageModel est le modèle d’affichage.In Razor Pages, the PageModel derived class is the view model.

Mettre à jour la page EditUpdate the Edit page

Mettez à jour le modèle de page pour la page Edit.Update the page model for the Edit page. Les modifications principales apparaissent en surbrillance :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();
    }
}

Les modifications de code sont semblables à celles de la page Create, à quelques exceptions près :The code changes are similar to the Create page with a few exceptions:

  • OnPostAsync a un paramètre facultatif id.OnPostAsync has an optional id parameter.
  • L’étudiant actuel est récupéré à partir de la base de données (on ne crée pas un étudiant vide).The current student is fetched from the DB, rather than creating an empty student.
  • FirstOrDefaultAsync a été remplacée par FindAsync.FirstOrDefaultAsync has been replaced with FindAsync. FindAsync est un bon choix lors de la sélection d’une entité à partir de la clé primaire.FindAsync is a good choice when selecting an entity from the primary key. Pour plus d’informations, consultez FindAsync.See FindAsync for more information.

Tester les pages Edit et CreateTest the Edit and Create pages

Créez et modifiez quelques entités d’étudiants.Create and edit a few student entities.

États des entitésEntity States

Le contexte de base de données effectue un suivi afin de savoir si les entités en mémoire sont synchronisées avec les lignes correspondantes dans la base de données.The DB context keeps track of whether entities in memory are in sync with their corresponding rows in the DB. Les informations de synchronisation du contexte de base de données déterminent ce qui se produit quand SaveChangesAsync est appelé.The DB context sync information determines what happens when SaveChangesAsync is called. Par exemple, quand une nouvelle entité est passée à la méthode AddAsync, l’état de cette entité est défini sur Added.For example, when a new entity is passed to the AddAsync method, that entity's state is set to Added. Quand SaveChangesAsync est appelée, le contexte de base de données émet une commande SQL INSERT.When SaveChangesAsync is called, the DB context issues a SQL INSERT command.

Une entité peut être dans l’un des états suivants :An entity may be in one of the following states:

  • Added: L’entité n’existe pas encore dans la base de données.Added: The entity doesn't yet exist in the DB. La méthode SaveChanges émet une instruction INSERT.The SaveChanges method issues an INSERT statement.

  • Unchanged: Aucune modification ne doit être enregistrée avec cette entité.Unchanged: No changes need to be saved with this entity. Une entité est dans cet état quand elle est lue à partir de la base de données.An entity has this status when it's read from the DB.

  • Modified: Tout ou partie des valeurs de propriété de l’entité ont été modifiées.Modified: Some or all of the entity's property values have been modified. La méthode SaveChanges émet une instruction UPDATE.The SaveChanges method issues an UPDATE statement.

  • Deleted: L’entité a été marquée pour suppression.Deleted: The entity has been marked for deletion. La méthode SaveChanges émet une instruction DELETE.The SaveChanges method issues a DELETE statement.

  • Detached: L’entité n’est pas suivie par le contexte de base de données.Detached: The entity isn't being tracked by the DB context.

Dans une application de bureau, les changements d’état sont généralement définis automatiquement.In a desktop app, state changes are typically set automatically. Une entité est lue, des modifications sont apportées et l’état d’entité passe automatiquement à Modified.An entity is read, changes are made, and the entity state to automatically be changed to Modified. L’appel de SaveChanges génère une instruction SQL UPDATE qui met à jour uniquement les propriétés modifiées.Calling SaveChanges generates a SQL UPDATE statement that updates only the changed properties.

Dans une application web, le DbContext qui lit une entité et affiche les données est supprimé après le rendu d’une page.In a web app, the DbContext that reads an entity and displays the data is disposed after a page is rendered. Quand la méthode OnPostAsync d’une page est appelée, une nouvelle requête web est faite avec une nouvelle instance du DbContext.When a page's OnPostAsync method is called, a new web request is made and with a new instance of the DbContext. La relecture de l’entité dans ce nouveau contexte simule le traitement de bureau.Re-reading the entity in that new context simulates desktop processing.

Mettre à jour la page DeleteUpdate the Delete page

Dans cette section, nous ajoutons du code pour implémenter un message d’erreur personnalisé quand l’appel à SaveChanges échoue.In this section, code is added to implement a custom error message when the call to SaveChanges fails. Ajoutez une chaîne contenant les messages d’erreur possibles :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; }

Remplacez la méthode OnGetAsync par le code suivant :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();
}

Le code précédent contient le paramètre facultatif saveChangesError.The preceding code contains the optional parameter saveChangesError. saveChangesError indique si la méthode a été appelée après un échec de suppression de l’objet Student.saveChangesError indicates whether the method was called after a failure to delete the student object. L’opération de suppression peut échouer en raison de problèmes réseau temporaires.The delete operation might fail because of transient network problems. Les erreurs réseau temporaires sont probablement dans le cloud.Transient network errors are more likely in the cloud. saveChangesError a la valeur false quand la méthode OnGetAsync de la page Delete est appelée à partir de l’interface utilisateur.saveChangesErroris false when the Delete page OnGetAsync is called from the UI. Quand OnGetAsync est appelée par OnPostAsync (car l’opération de suppression a échoué), le paramètre saveChangesError a la valeur true.When OnGetAsync is called by OnPostAsync (because the delete operation failed), the saveChangesError parameter is true.

La méthode OnPostAsync des pages DeleteThe Delete pages OnPostAsync method

Remplacez OnPostAsync par le code suivant :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 });
    }
}

Le code précédent récupère l’entité sélectionnée, puis appelle la méthode Remove pour définir l’état de l’entité sur Deleted.The preceding code retrieves the selected entity, then calls the Remove method to set the entity's status to Deleted. Lorsque SaveChanges est appelée, une commande SQL DELETE est générée.When SaveChanges is called, a SQL DELETE command is generated. Si Remove échoue :If Remove fails:

  • L’exception de la base de données est interceptée.The DB exception is caught.
  • La méthode OnGetAsync des pages est appelée avec saveChangesError=true.The Delete pages OnGetAsync method is called with saveChangesError=true.

Mise à jour de la Page Razor DeleteUpdate the Delete Razor Page

Ajoutez le message d’erreur mis en surbrillance suivant à la Page 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>

Testez la suppression.Test Delete.

Erreurs courantesCommon errors

Students/Index ou d’autres liens ne fonctionnent pas :Students/Index or other links don't work:

Vérifiez que la Page Razor contient la bonne directive @page.Verify the Razor Page contains the correct @page directive. Par exemple, la page Student/Index Razor Page ne doit pas contenir de modèle d’itinéraire :For example, The Students/Index Razor Page should not contain a route template:

@page "{id:int}"

Chaque page Razor doit inclure la directive @page.Each Razor Page must include the @page directive.

Ressources supplémentairesAdditional resources