Partie 6, Méthodes et vues de contrôleur dans ASP.NET Core

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.

Important

Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.

Pour la version actuelle, consultez la version .NET 8 de cet article.

Par Rick Anderson

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Vue d’index : Release Date apparaît en un seul mot (sans espace) et chaque date de sortie d’un film a comme heure « 12 AM »

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

Les DataAnnotations sont expliquées dans le tutoriel suivant. L’attribut Display spécifie les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour afficher l’URL cible.

Fenêtre de navigateur avec la souris sur le lien Edit et l’URL de lien https://localhost:5001/Movies/Edit/5 affichée

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le fichier Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de votre navigateur favori ou utilisez les outils de développement pour examiner le balisage généré. Une partie du code HTML généré est affichée ci-dessous :

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Program.cs :

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les méthodes de contrôleur sont également appelées méthodes d’action.)

Les Tag Helpers sont l’une des nouvelles fonctionnalités les plus populaires dans ASP.NET Core. Pour plus d'informations, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit. Le code suivant montre la méthode HTTP GET Edit, qui extrait le film et renseigne le formulaire de modification généré par le fichier RazorEdit.cshtml.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit, qui traite les valeurs de film publiées :

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour les requêtes POST. Vous pouvez appliquer l’attribut [HttpGet] à la première méthode Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit (Views/Movies/Edit.cshtml). Le fichier de la vue Edit génère le jeton anti-contrefaçon avec le Tag Helper Form.

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la méthode Entity Framework FindAsync, et retourne le film sélectionné à la vue Edit. Si un film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input> de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée par le système de génération de modèles automatique de Visual Studio :

@model MvcMovie.Models.Movie

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

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le modèle pour le modèle de vue soit de type Movie.

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ (« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément <input> HTML. Le Tag Helper Validation affiche les messages de validation associés à cette propriété.

Exécutez l’application et accédez à l’URL /Movies. Cliquez sur un lien Edit. Dans le navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form> est indiqué ci-dessous.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup removed for brevity -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est défini de façon à publier à l’URL /Movies/Edit/id. Les données du formulaire sont publiées au serveur en cas de clic sur le bouton Save. La dernière ligne avant l’élément </form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST

Le code suivant montre la version [HttpPost] de la méthode d’action Edit.

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un objet Movie qui est passé en tant que paramètre movie. La propriété ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être utilisées pour changer (modifier ou mettre à jour) un objet Movie. Si les données sont valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont enregistrées dans la base de données en appelant la méthode SaveChangesAsync du contexte de base de données. Après avoir enregistré les données, le code redirige l’utilisateur vers la méthode d’action Index de la classe MoviesController, qui affiche la collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de l’affichage des messages d’erreur appropriés.

Vue Edit : une exception pour une valeur Price incorrecte « abc » indique que le champ Price doit être un nombre. Une exception pour une valeur Release Date incorrecte « xyz » indique que vous devez entrer une date valide.

Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles reçoivent un objet de film (ou une liste d’objets, dans le cas de Index) et passent l’objet (modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create. Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des données dans une méthode HTTP GET présente un risque pour la sécurité. La modification des données dans une méthode HTTP GET enfreint également les bonnes pratiques HTTP et le modèle architectural REST, qui spécifie que les demandes GET ne doivent pas changer l’état de votre application. En d’autres termes, une opération GET doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données persistantes.

Ressources supplémentaires

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Vue d’index : Release Date apparaît en un seul mot (sans espace) et chaque date de sortie d’un film a comme heure « 12 AM »

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

Les DataAnnotations sont expliquées dans le tutoriel suivant. L’attribut Display spécifie les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour afficher l’URL cible.

Fenêtre de navigateur avec la souris sur le lien Edit et l’URL de lien https://localhost:5001/Movies/Edit/5 affichée

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le fichier Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de votre navigateur favori ou utilisez les outils de développement pour examiner le balisage généré. Une partie du code HTML généré est affichée ci-dessous :

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Program.cs :

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les méthodes de contrôleur sont également appelées méthodes d’action.)

Les Tag Helpers sont l’une des nouvelles fonctionnalités les plus populaires dans ASP.NET Core. Pour plus d'informations, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit. Le code suivant montre la méthode HTTP GET Edit, qui extrait le film et renseigne le formulaire de modification généré par le fichier RazorEdit.cshtml.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit, qui traite les valeurs de film publiées :

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour les requêtes POST. Vous pouvez appliquer l’attribut [HttpGet] à la première méthode Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit (Views/Movies/Edit.cshtml). Le fichier de la vue Edit génère le jeton anti-contrefaçon avec le Tag Helper Form.

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la méthode Entity Framework FindAsync, et retourne le film sélectionné à la vue Edit. Si un film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input> de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée par le système de génération de modèles automatique de Visual Studio :

@model MvcMovie.Models.Movie

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

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le modèle pour le modèle de vue soit de type Movie.

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ (« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément <input> HTML. Le Tag Helper Validation affiche les messages de validation associés à cette propriété.

Exécutez l’application et accédez à l’URL /Movies. Cliquez sur un lien Edit. Dans le navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form> est indiqué ci-dessous.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup removed for brevity -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est défini de façon à publier à l’URL /Movies/Edit/id. Les données du formulaire sont publiées au serveur en cas de clic sur le bouton Save. La dernière ligne avant l’élément </form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST

Le code suivant montre la version [HttpPost] de la méthode d’action Edit.

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un objet Movie qui est passé en tant que paramètre movie. La propriété ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être utilisées pour changer (modifier ou mettre à jour) un objet Movie. Si les données sont valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont enregistrées dans la base de données en appelant la méthode SaveChangesAsync du contexte de base de données. Après avoir enregistré les données, le code redirige l’utilisateur vers la méthode d’action Index de la classe MoviesController, qui affiche la collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de l’affichage des messages d’erreur appropriés.

Vue Edit : une exception pour une valeur Price incorrecte « abc » indique que le champ Price doit être un nombre. Une exception pour une valeur Release Date incorrecte « xyz » indique que vous devez entrer une date valide.

Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles reçoivent un objet de film (ou une liste d’objets, dans le cas de Index) et passent l’objet (modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create. Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des données dans une méthode HTTP GET présente un risque pour la sécurité. La modification des données dans une méthode HTTP GET enfreint également les bonnes pratiques HTTP et le modèle architectural REST, qui spécifie que les demandes GET ne doivent pas changer l’état de votre application. En d’autres termes, une opération GET doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données persistantes.

Ressources supplémentaires

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Vue d’index : Release Date apparaît en un seul mot (sans espace) et chaque date de sortie d’un film a comme heure « 12 AM »

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string? Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string? Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

Les DataAnnotations sont expliquées dans le tutoriel suivant. L’attribut Display spécifie les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour afficher l’URL cible.

Fenêtre de navigateur avec la souris sur le lien Edit et l’URL de lien https://localhost:5001/Movies/Edit/5 affichée

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le fichier Views/Movies/Index.cshtml.

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de votre navigateur favori ou utilisez les outils de développement pour examiner le balisage généré. Une partie du code HTML généré est affichée ci-dessous :

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Program.cs :

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les méthodes de contrôleur sont également appelées méthodes d’action.)

Les Tag Helpers sont une fonctionnalité populaire dans ASP.NET Core. Pour plus d’informations sur ces éléments, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit. Le code suivant montre la méthode HTTP GET Edit, qui extrait le film et renseigne le formulaire de modification généré par le fichier RazorEdit.cshtml.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit, qui traite les valeurs de film publiées :

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost].

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour les requêtes POST. Vous pouvez appliquer l’attribut [HttpGet] à la première méthode Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit (Views/Movies/Edit.cshtml). Le fichier de la vue Edit génère le jeton anti-contrefaçon avec le Tag Helper Form.

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la méthode Entity Framework FindAsync, et retourne le film sélectionné à la vue Edit. Si un film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input> de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée par le système de génération de modèles automatique de Visual Studio :

@model MvcMovie.Models.Movie

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

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le modèle pour le modèle de vue soit de type Movie.

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ (« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément <input> HTML. Le Tag Helper Validation affiche les messages de validation associés à cette propriété.

Exécutez l’application et accédez à l’URL /Movies. Cliquez sur un lien Edit. Dans le navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form> est indiqué ci-dessous.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup removed for brevity -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est défini de façon à publier à l’URL /Movies/Edit/id. Les données du formulaire sont publiées au serveur en cas de clic sur le bouton Save. La dernière ligne avant l’élément </form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST

Le code suivant montre la version [HttpPost] de la méthode d’action Edit.

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un objet Movie qui est passé en tant que paramètre movie. La propriété ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être utilisées pour changer (modifier ou mettre à jour) un objet Movie. Si les données sont valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont enregistrées dans la base de données en appelant la méthode SaveChangesAsync du contexte de base de données. Après avoir enregistré les données, le code redirige l’utilisateur vers la méthode d’action Index de la classe MoviesController, qui affiche la collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de l’affichage des messages d’erreur appropriés.

Vue Edit : une exception pour une valeur Price incorrecte « abc » indique que le champ Price doit être un nombre. Une exception pour une valeur Release Date incorrecte « xyz » indique que vous devez entrer une date valide.

Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles reçoivent un objet de film (ou une liste d’objets, dans le cas de Index) et passent l’objet (modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create. Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des données dans une méthode HTTP GET présente un risque pour la sécurité. La modification des données dans une méthode HTTP GET enfreint également les bonnes pratiques HTTP et le modèle architectural REST, qui spécifie que les demandes GET ne doivent pas changer l’état de votre application. En d’autres termes, une opération GET doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données persistantes.

Ressources supplémentaires

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Vue d’index : Release Date apparaît en un seul mot (sans espace) et chaque date de sortie d’un film a comme heure « 12 AM »

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

Nous abordons DataAnnotations dans le prochain tutoriel. L’attribut Display spécifie les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour afficher l’URL cible.

Fenêtre de navigateur avec la souris sur le lien Edit et l’URL de lien https://localhost:5001/Movies/Edit/5 affichée

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le fichier Views/Movies/Index.cshtml.

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

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de votre navigateur favori ou utilisez les outils de développement pour examiner le balisage généré. Une partie du code HTML généré est affichée ci-dessous :

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Startup.cs :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les méthodes de contrôleur sont également appelées méthodes d’action.)

Pour plus d’informations sur les Tag Helpers, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit. Le code suivant montre la méthode HTTP GET Edit, qui extrait le film et renseigne le formulaire de modification généré par le fichier RazorEdit.cshtml.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit, qui traite les valeurs de film publiées :

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost].

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour les requêtes POST. Vous pouvez appliquer l’attribut [HttpGet] à la première méthode Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit (Views/Movies/Edit.cshtml). Le fichier de la vue Edit génère le jeton anti-contrefaçon avec le Tag Helper Form.

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la méthode Entity Framework FindAsync, et retourne le film sélectionné à la vue Edit. Si un film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input> de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée par le système de génération de modèles automatique de Visual Studio :

@model MvcMovie.Models.Movie

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

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le modèle pour le modèle de vue soit de type Movie.

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ (« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément <input> HTML. Le Tag Helper Validation affiche les messages de validation associés à cette propriété.

Exécutez l’application et accédez à l’URL /Movies. Cliquez sur un lien Edit. Dans le navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form> est indiqué ci-dessous.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup removed for brevity -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est défini de façon à publier à l’URL /Movies/Edit/id. Les données du formulaire sont publiées au serveur en cas de clic sur le bouton Save. La dernière ligne avant l’élément </form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST

Le code suivant montre la version [HttpPost] de la méthode d’action Edit.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un objet Movie qui est passé en tant que paramètre movie. La propriété ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être utilisées pour changer (modifier ou mettre à jour) un objet Movie. Si les données sont valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont enregistrées dans la base de données en appelant la méthode SaveChangesAsync du contexte de base de données. Après avoir enregistré les données, le code redirige l’utilisateur vers la méthode d’action Index de la classe MoviesController, qui affiche la collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de l’affichage des messages d’erreur appropriés.

Vue Edit : une exception pour une valeur Price incorrecte « abc » indique que le champ Price doit être un nombre. Une exception pour une valeur Release Date incorrecte « xyz » indique que vous devez entrer une date valide.

Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles reçoivent un objet de film (ou une liste d’objets, dans le cas de Index) et passent l’objet (modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create. Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des données dans une méthode HTTP GET présente un risque pour la sécurité. La modification des données dans une méthode HTTP GET enfreint également les bonnes pratiques HTTP et le modèle architectural REST, qui spécifie que les demandes GET ne doivent pas changer l’état de votre application. En d’autres termes, une opération GET doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données persistantes.

Ressources supplémentaires