Examen des méthodes d’action et des vues pour le contrôleur de film
par Rick Anderson
Remarque
Une version mise à jour de ce didacticiel est disponible ici qui utilise ASP.NET MVC 5 et Visual Studio 2013. Il est plus sécurisé, beaucoup plus simple à suivre et montre plus de fonctionnalités.
Dans cette section, vous allez examiner les méthodes et vues d’action générées pour le contrôleur de film. Ensuite, vous allez ajouter une page de recherche personnalisée.
Exécutez l’application et accédez au Movies
contrôleur en ajoutant /Movies à l’URL dans la barre d’adresse de votre navigateur. Maintenez le pointeur de la souris sur un lien Modifier pour afficher l’URL vers laquelle il est lié.
Le lien Edit a été généré par la Html.ActionLink
méthode dans la vue Views\Movies\Index.cshtml :
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
L’objet Html
est un assistance exposé à l’aide d’une propriété sur la classe de base System.Web.Mvc.WebViewPage . La ActionLink
méthode de l’assistance facilite la génération dynamique de liens hypertexte HTML qui relient les méthodes d’action sur les contrôleurs. Le premier argument de la ActionLink
méthode est le texte du lien à afficher (par exemple). <a>Edit Me</a>
Le deuxième argument est le nom de la méthode d’action à appeler. L’argument final est un objet anonyme qui génère les données de routage (dans ce cas, l’ID de 4).
Le lien généré affiché dans l’image précédente est http://localhost:xxxxx/Movies/Edit/4
. L’itinéraire par défaut (établi dans App_Start\RouteConfig.cs) prend le modèle {controller}/{action}/{id}
d’URL. Par conséquent, ASP.NET se traduit http://localhost:xxxxx/Movies/Edit/4
en une requête en méthode Edit
d’action du Movies
contrôleur avec le paramètre ID
égal à 4. Examinez le code suivant à partir du fichier App_Start\RouteConfig.cs .
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
Vous pouvez également passer des paramètres de méthode d’action à l’aide d’une chaîne de requête. Par exemple, l’URL http://localhost:xxxxx/Movies/Edit?ID=4
transmet également le paramètre ID
4 à la Edit
méthode d’action du Movies
contrôleur.
Ouvrez le Movies
contrôleur. Les deux Edit
méthodes d’action sont indiquées ci-dessous.
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Notez que la deuxième méthode d’action Edit
est précédée de l’attribut HttpPost
. Cet attribut spécifie que la surcharge de la Edit
méthode peut être appelée uniquement pour les requêtes POST. Vous pouvez appliquer l’attribut HttpGet
à la première méthode d’édition, mais cela n’est pas nécessaire, car il s’agit de la valeur par défaut. (Nous allons faire référence à des méthodes d’action qui sont implicitement affectées à l’attribut HttpGet
en tant que HttpGet
méthodes.)
La HttpGet
Edit
méthode prend le paramètre ID de film, recherche le film à l’aide de la méthode Entity Framework Find
et retourne le film sélectionné en mode Édition. Le paramètre ID spécifie une valeur par défaut de zéro si la Edit
méthode est appelée sans paramètre. Si un film est introuvable, HttpNotFound est retourné. 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 l’affichage Modifier qui a été généré :
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Notez que le modèle d’affichage a une @model MvcMovie.Models.Movie
instruction en haut du fichier . Cela spécifie que la vue s’attend à ce que le modèle de vue soit de type Movie
.
Le code généré automatiquement utilise plusieurs méthodes d’assistance pour simplifier le balisage HTML. L’assistance Html.LabelFor
affiche le nom du champ (« Title », « ReleaseDate », « Genre » ou « Price »). L’assistance Html.EditorFor
restitue un élément HTML <input>
. L’assistance Html.ValidationMessageFor
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 de l’élément de formulaire est illustré ci-dessous.
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" 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" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
Les <input>
éléments se trouvent dans un élément HTML <form>
dont action
l’attribut est défini pour publier sur l’URL /Movies/Edit . Les données de formulaire sont publiées sur le serveur lorsque le bouton Modifier est cliqué.
Traitement de la requête POST
Le code suivant montre la version HttpPost
de la méthode d’action Edit
.
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Le classeur de modèles MVC ASP.NET prend les valeurs de formulaire publiées et crée un Movie
objet passé en tant que movie
paramètre. La méthode 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, les données de film sont enregistrées dans la Movies
collection de l’instance db(MovieDBContext
). Les nouvelles données de film sont enregistrées dans la base de données en appelant la SaveChanges
méthode de MovieDBContext
. Après avoir enregistré les données, le code redirige l’utilisateur vers la Index
méthode d’action de la MoviesController
classe, qui affiche la collection de films, y compris les modifications apportées.
Si les valeurs publiées ne sont pas valides, elles sont réaffichées dans le formulaire. Les Html.ValidationMessageFor
helpers du modèle d’affichage Edit.cshtml s’occupent de l’affichage des messages d’erreur appropriés.
Remarque
pour prendre en charge la validation jQuery pour les paramètres régionaux non anglais qui utilisent une virgule (« , ») pour un point décimal, vous devez inclure des globalize.js et vos cultures/fichiers globalize.cultures.js spécifiques (à partir de https://github.com/jquery/globalize ) et JavaScript à utiliserGlobalize.parseFloat
. Le code suivant montre les modifications apportées au fichier Views\Movies\Edit.cshtml pour utiliser la culture « fr-FR » :
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
Le champ décimal peut nécessiter une virgule, et non un point décimal. En guise de correctif temporaire, vous pouvez ajouter l’élément de globalisation au fichier web.config racine des projets. Le code suivant montre l’élément de globalisation avec la culture définie sur États-Unis l’anglais.
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
Toutes les HttpGet
méthodes suivent un modèle similaire. Ils obtiennent un objet vidéo (ou liste d’objets, dans le cas de Index
), et passent le modèle à la vue. La Create
méthode transmet un objet vidéo vide à l’affichage 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. La modification de données dans une méthode HTTP GET est un risque de sécurité. La modification de données dans une méthode GET enfreint également les meilleures pratiques HTTP et le modèle REST architectural, qui spécifie que les requêtes 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.
Ajout d’une méthode de recherche et d’une vue de recherche
Dans cette section, vous allez ajouter une méthode d’action SearchIndex
qui vous permet de rechercher des films par genre ou par nom. Cette opération sera disponible à l’aide de l’URL /Movies/SearchIndex . La requête affiche un formulaire HTML qui contient des éléments d’entrée qu’un utilisateur peut entrer pour rechercher un film. Lorsqu’un utilisateur envoie le formulaire, la méthode d’action obtient les valeurs de recherche publiées par l’utilisateur et utilise les valeurs pour rechercher la base de données.
Affichage du formulaire SearchIndex
Commencez par ajouter une méthode d’action SearchIndex
à la classe existante MoviesController
. La méthode retourne une vue qui contient un formulaire HTML. Voici le code :
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
La première ligne de la SearchIndex
méthode crée la requête LINQ suivante pour sélectionner les films :
var movies = from m in db.Movies
select m;
La requête est définie à ce stade, mais n’a pas encore été exécutée sur le magasin de données.
Si le searchString
paramètre contient une chaîne, la requête films est modifiée pour filtrer la valeur de la chaîne de recherche à l’aide du code suivant :
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
Le code s => s.Title
ci-dessus est une expression lambda. Les lambda sont utilisés dans les requêtes LINQ basées sur des méthodes de méthode en tant qu’arguments pour les méthodes d’opérateur de requête standard telles que la méthode Where utilisée dans le code ci-dessus. Les requêtes LINQ ne sont pas exécutées lorsqu’elles sont définies ou lorsqu’elles sont modifiées en appelant une méthode telle que Where
ou OrderBy
. Au lieu de cela, l’exécution de la requête est différée, ce qui signifie que l’évaluation d’une expression est retardée jusqu’à ce que sa valeur réalisée soit réellement itérée ou que la ToList
méthode soit appelée. Dans l’exemple SearchIndex
, la requête est exécutée dans la vue SearchIndex. Pour plus d’informations sur l’exécution différée des requêtes, consultez Exécution de requête.
Vous pouvez maintenant implémenter l’affichage SearchIndex
qui affichera le formulaire à l’utilisateur. Cliquez avec le bouton droit dans la SearchIndex
méthode, puis cliquez sur Ajouter un affichage. Dans la boîte de dialogue Ajouter un affichage , spécifiez que vous allez passer un Movie
objet au modèle d’affichage en tant que classe de modèle. Dans la liste des modèles de structure Scaffold , choisissez Liste, puis cliquez sur Ajouter.
Lorsque vous cliquez sur le bouton Ajouter , le modèle de vue Views\Movies\SearchIndex.cshtml est créé. Étant donné que vous avez sélectionné Liste dans la liste des modèles de structure , Visual Studio a généré automatiquement (généré automatiquement) un balisage par défaut dans l’affichage. La structure a créé un formulaire HTML. Il a examiné la classe et créé du Movie
code pour afficher <label>
les éléments pour chaque propriété de la classe. La liste ci-dessous montre la vue Créer qui a été générée :
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
Exécutez l’application et accédez à /Movies/SearchIndex. Ajoutez une chaîne de requête comme ?searchString=ghost
à l’URL. Les films filtrés sont affichés.
Si vous modifiez la signature de la SearchIndex
méthode pour avoir un paramètre nommé id
, le id
paramètre correspond à l’espace {id}
réservé pour les itinéraires par défaut définis dans le fichier Global.asax .
{controller}/{action}/{id}
La méthode d’origine SearchIndex
ressemble à ceci :
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
La méthode modifiée SearchIndex
se présente comme suit :
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Vous pouvez maintenant passer le titre de la recherche en tant que données de routage (un segment de l’URL) et non pas en tant que valeur de chaîne de requête.
Cependant, vous ne pouvez pas attendre des utilisateurs qu’ils modifient l’URL à chaque fois qu’ils veulent rechercher un film. Vous allez donc ajouter l’interface utilisateur pour les aider à filtrer les films. Si vous avez modifié la signature de la SearchIndex
méthode pour tester comment passer le paramètre ID lié à l’itinéraire, remplacez-le afin que votre SearchIndex
méthode accepte un paramètre de chaîne nommé searchString
:
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Ouvrez le fichier Views\Movies\SearchIndex.cshtml , puis juste après @Html.ActionLink("Create New", "Create")
, ajoutez ce qui suit :
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
L’exemple suivant montre une partie du fichier Views\Movies\SearchIndex.cshtml avec le balisage de filtrage ajouté.
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
L’assistance Html.BeginForm
crée une balise d’ouverture <form>
. L’assistance Html.BeginForm
entraîne la publication du formulaire lorsque l’utilisateur envoie le formulaire en cliquant sur le bouton Filtrer .
Exécutez l’application et essayez de rechercher un film.
Il n’y a pas HttpPost
de surcharge de la SearchIndex
méthode. Vous n’en avez pas besoin, car la méthode ne change pas l’état de l’application, en filtrant simplement les données.
Vous pourriez ajouter la méthode HttpPost SearchIndex
suivante. Dans ce cas, l’appelant d’action correspondrait à la HttpPost SearchIndex
méthode et la HttpPost SearchIndex
méthode s’exécuterait comme indiqué dans l’image ci-dessous.
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
Cependant, même si vous ajoutez cette version HttpPost
de la méthode SearchIndex
, il existe une limitation dans la façon dont tout ceci a été implémenté. Imaginez que vous voulez insérer un signet pour une recherche spécifique, ou que vous voulez envoyer un lien à vos amis sur lequel ils peuvent cliquer pour afficher la même liste filtrée de films. Notez que l’URL de la requête HTTP POST est identique à l’URL de la requête GET (localhost :xxxxx/Movies/SearchIndex) : il n’existe aucune information de recherche dans l’URL elle-même. À l’heure actuelle, les informations de chaîne de recherche sont envoyées au serveur en tant que valeur de champ de formulaire. Cela signifie que vous ne pouvez pas capturer ces informations de recherche à signet ou à envoyer à des amis dans une URL.
La solution consiste à utiliser une surcharge de BeginForm
ce qui spécifie que la requête POST doit ajouter les informations de recherche à l’URL et qu’elle doit être routée vers la version HttpGet de la SearchIndex
méthode. Remplacez la méthode sans BeginForm
paramètre existante par les éléments suivants :
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
Maintenant, lorsque vous envoyez une recherche, l’URL contient une chaîne de requête de recherche. La recherche accède également à la méthode d’action HttpGet SearchIndex
, même si vous avez une méthode HttpPost SearchIndex
.
Ajout de la recherche par genre
Si vous avez ajouté la HttpPost
version de la méthode, supprimez-la SearchIndex
maintenant.
Ensuite, vous allez ajouter une fonctionnalité pour permettre aux utilisateurs de rechercher des films par genre. Remplacez la méthode SearchIndex
par le code suivant :
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
Cette version de la SearchIndex
méthode prend un paramètre supplémentaire, à savoir movieGenre
. Les premières lignes de code créent un List
objet pour contenir des genres vidéo à partir de la base de données.
Le code suivant est une requête LINQ qui récupère tous les genres dans la base de données.
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
Le code utilise la AddRange
méthode de la collection générique List
pour ajouter tous les genres distincts à la liste. (Sans le modificateur, les Distinct
genres en double seraient ajoutés , par exemple, la comédie serait ajoutée deux fois dans notre exemple). Le code stocke ensuite la liste des genres dans l’objet ViewBag
.
Le code suivant montre comment vérifier le movieGenre
paramètre. S’il n’est pas vide, le code limite davantage la requête films pour limiter les films sélectionnés au genre spécifié.
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
Ajout de balisage à la vue SearchIndex pour prendre en charge la recherche par genre
Ajoutez un Html.DropDownList
assistance au fichier Views\Movies\SearchIndex.cshtml , juste avant l’assistance TextBox
. Le balisage terminé est illustré ci-dessous :
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
Exécutez l’application et accédez à /Movies/SearchIndex. Essayez une recherche par genre, par nom de film, et par les deux critères.
Dans cette section, vous avez examiné les méthodes d’action CRUD et les vues générées par l’infrastructure. Vous avez créé une méthode d’action de recherche et une vue qui permettent aux utilisateurs de rechercher par titre et genre de film. Dans la section suivante, vous allez découvrir comment ajouter une propriété au Movie
modèle et comment ajouter un initialiseur qui créera automatiquement une base de données de test.