Partager via


Implémentation des fonctionnalités CRUD de base avec Entity Framework dans ASP.NET application MVC (2 sur 10)

par Tom Dykstra

L’exemple d’application web Contoso University montre comment créer ASP.NET applications MVC 4 à l’aide d’Entity Framework 5 Code First et de Visual Studio 2012. Pour obtenir des informations sur la série de didacticiels, consultez le premier didacticiel de la série.

Remarque

Si vous rencontrez un problème, téléchargez le chapitre terminé et essayez de reproduire votre problème. Vous pouvez généralement trouver la solution au problème en comparant votre code au code terminé. Pour certaines erreurs courantes et comment les résoudre, consultez Erreurs et solutions de contournement.

Dans le tutoriel précédent, vous avez créé une application MVC qui stocke et affiche des données à l’aide d’Entity Framework et de SQL Server LocalDB. Dans ce tutoriel, vous allez passer en revue et personnaliser le code CRUD (créer, lire, mettre à jour, supprimer) que la structure MVC crée automatiquement pour vous dans les contrôleurs et les vues.

Remarque

Il est courant d’implémenter le modèle de référentiel pour créer une couche d’abstraction entre votre contrôleur et la couche d’accès aux données. Pour simplifier ces didacticiels, vous n’implémenterez pas de référentiel tant qu’un didacticiel ultérieur n’est pas mis en œuvre dans cette série.

Dans ce tutoriel, vous allez créer les pages web suivantes :

Capture d’écran montrant la page Détails des étudiants de l’université Contoso.

Capture d’écran montrant la page Contoso University Student Edit.

Capture d’écran montrant la page Contoso University Student Create.

Capture d’écran montrant la page Supprimer des étudiants.

Création d’une page Détails

Le code généré automatiquement pour la page Students Index n’a pas quitté la Enrollments propriété, car cette propriété contient une collection. Dans la Details page, vous allez afficher le contenu de la collection dans une table HTML.

Dans Controllers\StudentController.cs, la méthode d’action de la Details vue utilise la Find méthode pour récupérer une seule Student entité.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

La valeur de clé est passée à la méthode en tant que id paramètre et provient des données de routage dans le lien hypertexte Détails de la page Index.

  1. Ouvrez Views\Student\Details.cshtml. Chaque champ s’affiche à l’aide d’un DisplayFor assistance, comme illustré dans l’exemple suivant :

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. Après le EnrollmentDate champ et juste avant la balise de fermeture fieldset , ajoutez du code pour afficher une liste d’inscriptions, comme illustré dans l’exemple suivant :

    <div class="display-label">
            @Html.LabelFor(model => model.Enrollments)
        </div>
        <div class="display-field">
            <table>
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </div>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Ce code parcourt en boucle les entités dans la propriété de navigation Enrollments. Pour chaque Enrollment entité de la propriété, elle affiche le titre du cours et la note. Le titre du cours est récupéré à partir de l’entité Course stockée dans la Course propriété de navigation de l’entité Enrollments . Toutes ces données sont récupérées automatiquement à partir de la base de données quand elles sont nécessaires. (En d’autres termes, vous utilisez le chargement différé ici. Vous n’avez pas spécifié de chargement impatient pour la Courses propriété de navigation. Par conséquent, la première fois que vous essayez d’accéder à cette propriété, une requête est envoyée à la base de données pour récupérer les données. Vous pouvez en savoir plus sur le chargement différé et le chargement impatient dans le didacticiel Lire les données associées plus loin dans cette série.)

  3. Exécutez la page en sélectionnant l’onglet Étudiants et en cliquant sur un lien Détails pour Alexander Carson. Vous voyez la liste des cours et des notes de l’étudiant sélectionné :

    Student_Details_page

Mise à jour de la page Créer

  1. Dans Controllers\StudentController.cs, remplacez la HttpPost``Create méthode d’action par le code suivant pour ajouter un try-catch bloc et l’attribut Bind à la méthode générée :

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(
       [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
       Student student)
    {
       try
       {
          if (ModelState.IsValid)
          {
             db.Students.Add(student);
             db.SaveChanges();
             return RedirectToAction("Index");
          }
       }
       catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
          ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
       }
       return View(student);
    }
    

    Ce code ajoute l’entité Student créée par le classeur de modèles MVC ASP.NET au Students jeu d’entités, puis enregistre les modifications apportées à la base de données. (Le classeur de modèles fait référence à la fonctionnalité ASP.NET MVC qui facilite l’utilisation des données soumises par un formulaire ; un classeur de modèles convertit les valeurs de formulaire publiées en types CLR et les transmet à la méthode d’action dans les paramètres. Dans ce cas, le classeur de modèles instancie une Student entité pour vous à l’aide de valeurs de propriété de la Form collection.)

    L’attribut ValidateAntiForgeryToken permet d’empêcher les attaques de falsification de requête intersite.

> [!WARNING]
    > Security - The `Bind` attribute is added to protect against *over-posting*. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to update.
    > 
    > [!code-csharp[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample5.cs?highlight=7)]
    > 
    > Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as [fiddler](http://fiddler2.com/home), or write some JavaScript, to post a `Secret` form value. Without the [Bind](https://msdn.microsoft.com/library/system.web.mvc.bindattribute(v=vs.108).aspx) attribute limiting the fields that the model binder uses when it creates a `Student` instance*,* the model binder would pick up that `Secret` form value and use it to update the `Student` entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
    > 
    > ![](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/_static/image6.png)  
    > 
    > The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to update that property.
    > 
    > It's a security best practice to use the `Include` parameter with the `Bind` attribute to *allowed attributes* fields. It's also possible to use the `Exclude` parameter to *blocked attributes* fields you want to exclude. The reason `Include` is more secure is that when you add a new property to the entity, the new field is not automatically protected by an `Exclude` list.
    > 
    > Another alternative approach, and one preferred by many, is to use only view models with model binding. The view model contains only the properties you want to bind. Once the MVC model binder has finished, you copy the view model properties to the entity instance.

    Other than the `Bind` attribute, the `try-catch` block is the only change you've made to the scaffolded code. If an exception that derives from [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) is caught while the changes are being saved, a generic error message is displayed. [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception (and non-null inner exceptions ) with a logging mechanism such as [ELMAH](https://code.google.com/p/elmah/).

    The code in *Views\Student\Create.cshtml* is similar to what you saw in *Details.cshtml*, except that `EditorFor` and `ValidationMessageFor` helpers are used for each field instead of `DisplayFor`. The following example shows the relevant code:

    [!code-cshtml[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample6.cshtml)]

    *Create.cshtml* also includes `@Html.AntiForgeryToken()`, which works with the `ValidateAntiForgeryToken` attribute in the controller to help prevent [cross-site request forgery](../../security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages.md) attacks.

    No changes are required in *Create.cshtml*.
  1. Exécutez la page en sélectionnant l’onglet Étudiants et en cliquant sur Créer.

    Student_Create_page

    Certaines validations de données fonctionnent par défaut. Entrez des noms et une date non valide, puis cliquez sur Créer pour afficher le message d’erreur.

    Students_Create_page_error_message

    Le code mis en surbrillance suivant montre la vérification de validation du modèle.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            db.Students.Add(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(student);
    }
    

    Remplacez la date par une valeur valide telle que 9/1/2005, puis cliquez sur Créer pour afficher le nouvel étudiant dans la page Index .

    Students_Index_page_with_new_student

Mise à jour de la page Modifier POST

Dans Controllers\StudentController.cs, la HttpGet Edit méthode (sans l’attribut HttpPost ) utilise la Find méthode pour récupérer l’entité sélectionnée Student , comme vous l’avez vu dans la Details méthode. Vous n’avez pas besoin de modifier cette méthode.

Toutefois, remplacez la HttpPost Edit méthode d’action par le code suivant pour ajouter un try-catch bloc et l’attribut Bind :

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
   }
   return View(student);
}

Ce code est similaire à ce que vous avez vu dans la HttpPost Create méthode. Toutefois, au lieu d’ajouter l’entité créée par le classeur de modèles au jeu d’entités, ce code définit un indicateur sur l’entité indiquant qu’elle a été modifiée. Lorsque la méthode SaveChanges est appelée, l’indicateur Modified provoque la création d’instructions SQL par Entity Framework pour mettre à jour la ligne de base de données. Toutes les colonnes de la ligne de base de données sont mises à jour, y compris celles que l’utilisateur n’a pas changé et les conflits d’accès concurrentiel sont ignorés. (Vous allez apprendre à gérer la concurrence dans un didacticiel ultérieur de cette série.)

États d’entité et méthodes Attach et SaveChanges

Le contexte de base de données effectue le suivi de la synchronisation ou non des entités en mémoire avec leurs lignes correspondantes dans la base de données, et ces informations déterminent ce qui se passe quand vous appelez la méthode SaveChanges. Par exemple, lorsque vous transmettez une nouvelle entité à la méthode Add , l’état de cette entité est défini Addedsur . Ensuite, lorsque vous appelez la méthode SaveChanges , le contexte de base de données émet une commande SQL INSERT .

Une entité peut se trouver dans l’undes états suivants :

  • Added. L’entité n’existe pas encore dans la base de données. La SaveChanges méthode doit émettre une INSERT instruction.
  • Unchanged. La méthode SaveChanges ne doit rien faire avec cette entité. Quand vous lisez une entité dans la base de données, l’entité a d’abord cet état.
  • Modified. Tout ou partie des valeurs de propriété de l’entité ont été modifiées. La SaveChanges méthode doit émettre une UPDATE instruction.
  • Deleted. L’entité a été marquée pour suppression. La SaveChanges méthode doit émettre une DELETE instruction.
  • Detached. L’entité n’est pas suivie par le contexte de base de données.

Dans une application de poste de travail, les changements d’état sont généralement définis automatiquement. Dans un type d’application de bureau, vous lisez une entité et apportez des modifications à certaines de ses valeurs de propriété. Son état passe alors automatiquement à Modified. Ensuite, lorsque vous appelez SaveChanges, Entity Framework génère une instruction SQL UPDATE qui met à jour uniquement les propriétés réelles que vous avez modifiées.

La nature déconnectée des applications web n’autorise pas cette séquence continue. DbContext qui lit une entité est supprimée après le rendu d’une page. Lorsque la HttpPost Edit méthode d’action est appelée, une nouvelle requête est effectuée et vous disposez d’une nouvelle instance de DbContext. Vous devez donc définir manuellement l’état Modified. de l’entité sur Then lorsque vous appelez SaveChanges, Entity Framework met à jour toutes les colonnes de la ligne de base de données, car le contexte n’a aucun moyen de savoir quelles propriétés vous avez modifiées.

Si vous souhaitez que l’instruction SQL Update met à jour uniquement les champs que l’utilisateur a réellement modifiés, vous pouvez enregistrer les valeurs d’origine d’une certaine manière (comme les champs masqués) afin qu’elles soient disponibles lorsque la HttpPost Edit méthode est appelée. Vous pouvez ensuite créer une Student entité à l’aide des valeurs d’origine, appeler la Attach méthode avec cette version d’origine de l’entité, mettre à jour les valeurs de l’entité vers les nouvelles valeurs, puis appeler SaveChanges. Pour plus d’informations, consultez États d’entité et SaveChanges et Données locales dans le Centre de développement de données MSDN.

Le code dans Views\Student\Edit.cshtml est similaire à ce que vous avez vu dans Create.cshtml et aucune modification n’est requise.

Exécutez la page en sélectionnant l’onglet Étudiants , puis en cliquant sur un lien hypertexte Modifier .

Student_Edit_page

Changez quelques données et cliquez sur Save. Vous voyez les données modifiées dans la page Index.

Students_Index_page_after_edit

Mise à jour de la page Supprimer

Dans Controllers\StudentController.cs, le code de modèle de la HttpGet Delete méthode utilise la Find méthode pour récupérer l’entité sélectionnéeStudent, comme vous l’avez vu dans les méthodes et Edit les Details méthodes. Cependant, pour implémenter un message d’erreur personnalisé quand l’appel à SaveChanges échoue, vous devez ajouter des fonctionnalités à cette méthode et à sa vue correspondante.

Comme vous l’avez vu pour les opérations de mise à jour et de création, les opérations de suppression nécessitent deux méthodes d’action. La méthode appelée en réponse à une requête GET affiche une vue qui permet à l’utilisateur d’approuver ou d’annuler l’opération de suppression. Si l’utilisateur l’approuve, une demande POST est créée. Lorsque cela se produit, la HttpPost Delete méthode est appelée, puis cette méthode effectue réellement l’opération de suppression.

Vous allez ajouter un try-catch bloc à la HttpPost Delete méthode pour gérer les erreurs qui peuvent se produire lorsque la base de données est mise à jour. Si une erreur se produit, la HttpPost Delete méthode appelle la HttpGet Delete méthode, en lui transmettant un paramètre qui indique qu’une erreur s’est produite. La HttpGet Delete méthode réaffiche ensuite la page de confirmation avec le message d’erreur, ce qui permet à l’utilisateur d’annuler ou de réessayer.

  1. Remplacez la méthode d’action HttpGet Delete par le code suivant, qui gère le rapport d’erreurs :

    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

    Ce code accepte un paramètre booléen facultatif qui indique s’il a été appelé après un échec d’enregistrement des modifications. Ce paramètre est false lorsque la HttpGet Delete méthode est appelée sans échec précédent. Lorsqu’elle est appelée par la HttpPost Delete méthode en réponse à une erreur de mise à jour de base de données, le paramètre est true et un message d’erreur est transmis à la vue.

  2. Remplacez la HttpPost Delete méthode d’action (nommée DeleteConfirmed) par le code suivant, qui effectue l’opération de suppression réelle et intercepte les erreurs de mise à jour de base de données.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Ce code récupère l’entité sélectionnée, puis appelle la méthode Remove pour définir l’état Deletedde l’entité sur . Lorsque SaveChanges est appelée, une commande SQL DELETE est générée. Vous avez également changé le nom de la méthode d’action de DeleteConfirmed en Delete. Code généré automatiquement nommé la HttpPost Delete méthode DeleteConfirmed pour donner à la HttpPost méthode une signature unique. ( Le CLR nécessite des méthodes surchargées pour avoir des paramètres de méthode différents.) Maintenant que les signatures sont uniques, vous pouvez respecter la convention MVC et utiliser le même nom pour les méthodes de suppression et HttpGet de HttpPost suppression.

    Si l’amélioration des performances dans une application à volume élevé est une priorité, vous pouvez éviter une requête SQL inutile pour récupérer la ligne en remplaçant les lignes de code qui appellent le Find code et Remove les méthodes par le code suivant, comme indiqué en surbrillance jaune :

    Student studentToDelete = new Student() { StudentID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Ce code instancie une Student entité en utilisant uniquement la valeur de clé primaire, puis définit l’état de Deletedl’entité sur . C’est tout ce dont a besoin Entity Framework pour pouvoir supprimer l’entité.

    Comme indiqué, la HttpGet Delete méthode ne supprime pas les données. L’exécution d’une opération de suppression en réponse à une requête GET (ou à ce titre, l’exécution d’une opération de modification, l’opération de création ou toute autre opération qui modifie les données) crée un risque de sécurité. Pour plus d’informations, consultez ASP.NET conseil MVC #46 — N’utilisez pas de liens de suppression, car ils créent des trous de sécurité sur le blog de Stephen Walther.

  3. Dans Views\Student\Delete.cshtml, ajoutez un message d’erreur entre le h2 titre et le h3 titre, comme illustré dans l’exemple suivant :

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    

    Exécutez la page en sélectionnant l’onglet Étudiants et en cliquant sur un lien hypertexte Supprimer :

    Student_Delete_page

  4. Cliquez sur Supprimer. La page Index s’affiche sans l’étudiant supprimé. (Vous verrez un exemple de code de gestion des erreurs en action dans le Didacticiel de gestion de l’accès concurrentiel plus loin dans cette série.)

S’assurer que les connexions de base de données ne sont pas ouvertes

Pour vous assurer que les connexions de base de données sont correctement fermées et que les ressources qu’elles contiennent sont libérées, vous devez voir que l’instance de contexte est supprimée. C’est pourquoi le code généré automatiquement fournit une méthode Dispose à la fin de la StudentController classe dans StudentController.cs, comme illustré dans l’exemple suivant :

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

La classe de base Controller implémente déjà l’interface IDisposable . Ce code ajoute simplement une substitution à la Dispose(bool) méthode pour supprimer explicitement l’instance de contexte.

Résumé

Vous disposez maintenant d’un ensemble complet de pages qui effectuent des opérations CRUD simples pour Student les entités. Vous avez utilisé des helpers MVC pour générer des éléments d’interface utilisateur pour les champs de données. Pour plus d’informations sur les helpers MVC, consultez Rendu d’un formulaire utilisant des helpers HTML (la page concerne MVC 3, mais elle est toujours pertinente pour MVC 4).

Dans le tutoriel suivant, vous allez développer les fonctionnalités de la page Index en ajoutant le tri et la pagination.

Vous trouverez des liens vers d’autres ressources Entity Framework dans la carte de contenu d’accès aux données ASP.NET.