Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 :
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.
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>
Après le
EnrollmentDate
champ et juste avant la balise de fermeturefieldset
, 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 chaqueEnrollment
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 laCourse
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 laCourses
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.)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é :
Mise à jour de la page Créer
Dans Controllers\StudentController.cs, remplacez la
HttpPost``Create
méthode d’action par le code suivant pour ajouter untry-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 auStudents
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 uneStudent
entité pour vous à l’aide de valeurs de propriété de laForm
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.
>
> 
>
> 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*.
Exécutez la page en sélectionnant l’onglet Étudiants et en cliquant sur Créer.
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.
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 .
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 Added
sur . 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. LaSaveChanges
méthode doit émettre uneINSERT
instruction.Unchanged
. La méthodeSaveChanges
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. LaSaveChanges
méthode doit émettre uneUPDATE
instruction.Deleted
. L’entité a été marquée pour suppression. LaSaveChanges
méthode doit émettre uneDELETE
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 .
Changez quelques données et cliquez sur Save. Vous voyez les données modifiées dans la page Index.
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.
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 laHttpGet
Delete
méthode est appelée sans échec précédent. Lorsqu’elle est appelée par laHttpPost
Delete
méthode en réponse à une erreur de mise à jour de base de données, le paramètre esttrue
et un message d’erreur est transmis à la vue.Remplacez la
HttpPost
Delete
méthode d’action (nomméeDeleteConfirmed
) 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
Deleted
de l’entité sur . LorsqueSaveChanges
est appelée, une commande SQLDELETE
est générée. Vous avez également changé le nom de la méthode d’action deDeleteConfirmed
enDelete
. Code généré automatiquement nommé laHttpPost
Delete
méthodeDeleteConfirmed
pour donner à laHttpPost
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 etHttpGet
deHttpPost
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 etRemove
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 deDeleted
l’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.Dans Views\Student\Delete.cshtml, ajoutez un message d’erreur entre le
h2
titre et leh3
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 :
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.