• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 23/11/2017

Utiliser les contrôleurs

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Allez, place aux contrôleurs. Nous en avons déjà parlé précédemment, ils ont le même rôle que ceux qu'on voit dans les trains, ils sont ici pour contrôler !
Le contrôleur est au cœur de MVC, il fait le lien entre la vue et le modèle ; c’est lui qui gère les actions de l’utilisateur.
Il interprète la requête HTTP entrante et choisit la vue à afficher dans le navigateur.
Vous savez déjà pas mal de choses sur le contrôleur grâce à mes précédentes explications, mais il y a encore des choses à découvrir. Alors allons-y. ;)

Rappels et précisions

Le contrôleur peut être vu comme le point d’entrée de la requête car c’est lui qui va manipuler le modèle et qui va choisir la vue à afficher. On a vu qu’en fait, il y a un intermédiaire avant que la requête ne soit traitée par le contrôleur, il s’agit du mécanisme de routing qui permet de transformer une URL en action de contrôleur. Nous verrons plus loin qu’il est encore possible de mettre d’autres intermédiaires en amont grâce aux filtres.

Toujours est-il que toute action réalisée par l’utilisateur via son navigateur Internet est transformée en une action de contrôleur. Les contrôleurs se placent par convention dans le répertoire /Controllers, mais ce n’est en fait pas une obligation. N’importe quelle classe qui hérite de la classe de base Controller  est éligible au statut de contrôleur.

Nous avons vu dans le chapitre sur les vues comment ajouter un contrôleur nous permettant d’accueillir les utilisateurs du site. Il s’agissait du contrôleur AccueilController. Il possédait notamment une méthode Index() qui était instanciée lorsque l’utilisateur naviguait sur le site via les URL :

  • /

  • /Accueil

  • /Accueil/Index

Nous avons fait plein de tests dans ce contrôleur, mais en fait il n’est pas très intéressant car dans l’application finale, il ne va quasiment rien faire à part appeler la méthode View() , lui permettant de faire afficher la vue par défaut de l’action Index , qui, par convention, porte le nom Index.cshtml. Tout ça, ce sont des révisions. Donc supprimez-moi tout le code excédentaire et mettez simplement un :

public class AccueilController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Nous allons nous intéresser à des contrôleurs un peu plus complexes histoire de commencer à faire des choses sympathiques.

Pour cela, nous allons créer un nouveau contrôleur, qui va nous permettre de gérer les restaurants. Appelons le RestaurantController . Pour créer cette classe, rappelez-vous, il suffit de faire un simple clic droit sur le répertoire Controllers et de faire Ajouter -> Contrôleur. La boîte de dialogue s’ouvre, nous permettant de choisir le type du contrôleur et de renseigner le nom du contrôleur :

Ajout d'un contrôleur
Ajout d'un contrôleur

Vous pouvez voir qu'il y a plusieurs choix lors de l'ajout d'un contrôleur. Pour l'instant, nous allons laisser à Contrôleur MVC vide. Mais si vous êtes un peu curieux et que vous regardez les autres éléments, vous pourrez voir qu’ils pourront nous simplifier la vie. Mais plus tard seulement, pour l’instant on va faire tout de A à Z.
Comme à son habitude, Visual Studio nous génère la classe contrôleur :

public class RestaurantController : Controller
{
    //
    // GET: /Restaurant/

    public ActionResult Index()
    {
        return View();
    }

}

Comme vous le savez déjà, un contrôleur doit obligatoirement dériver de la classe de base Controller . En fait, pour pouvoir être éligible au statut de contrôleur, une classe doit seulement implémenter l’interface IController , mais c’est beaucoup plus facile d’hériter de la classe Controller, qui implémente déjà IController  et qui en plus nous fournit plein de méthodes utilitaires comme les fameuses méthodes View()  et PartialView()  que nous avons vues précédemment.

Vous aurez également remarqué que Visual Studio nous génère un commentaire, qu’en général je supprime pour augmenter la lisibilité du code. Il nous dit que l’action du contrôleur est accessible via la méthode GET du protocole HTTP, via la route /Restaurant/. Nous le savions déjà, mais il est bon de se rappeler que le fait d’accéder à l’URL /Restaurant effectue en fait une requête HTTP de type GET qui instancie le contrôleur Restaurant  et appelle sa méthode Index .
GET est la méthode par défaut. Nous verrons plus loin comment traiter les requêtes de type POST.

Nous allons profiter de cette méthode index pour construire une liste de restaurants et l’afficher sur la vue du même nom. Le contrôleur va donc appeler notre modèle via la couche d’accès aux données que nous avons créé puis renvoyer le modèle à la vue. Ce qui donnerait :

public ActionResult Index()
{
    using (IDal dal = new Dal())
    {
        List<Resto> listeDesRestaurants = dal.ObtientTousLesRestaurants();
        return View(listeDesRestaurants);
    }
}

C’est bien ça qu’il faut faire. Par contre, pour l’instant nous n’avons pas de données en base ; pas de restaurants, pas d’utilisateurs, rien !

Pour faciliter notre travail, nous allons temporairement initialiser notre base de données avec quelques valeurs de départ. Un peu comme ce que nous avions fait pour nos tests où nous avions choisi de remettre à zéro la base de données avant chaque lancement de test avec cette commande :

IDatabaseInitializer<BddContext> init = new DropCreateDatabaseAlways<BddContext>();
Database.SetInitializer(init);
init.InitializeDatabase(new BddContext());

Ici, ce que nous souhaitons faire, c’est qu’à chaque nouveau démarrage de notre application web, nous ayons quelques données simples et maîtrisées.
Pour ce faire, on va passer par un initialisateur personnalisé. Il suffit d’écrire une classe qui hérite de DropCreateDatabaseAlways  que nous avons déjà utilisé et de substituer sa méthode Seed  qui va nous permettre d’effectuer le remplissage de données. Ajoutez donc cette classe par exemple dans le répertoire Models :

public class InitChoixResto : DropCreateDatabaseAlways<BddContext>
{
    protected override void Seed(BddContext context)
    {
        context.Restos.Add(new Resto { Id = 1, Nom = "Resto pinambour", Telephone = "123" });
        context.Restos.Add(new Resto { Id = 2, Nom = "Resto pinière", Telephone = "456" });
        context.Restos.Add(new Resto { Id = 3, Nom = "Resto toro", Telephone = "789" });

        base.Seed(context);
    }
}

Voilà, avec ceci par exemple j’aurai trois restaurants dans ma base de données à chaque démarrage de l’application web.
Il ne me reste plus qu’à appeler cette classe depuis le Global.asax.cs :

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RouteConfig.RegisterRoutes(RouteTable.Routes);

        IDatabaseInitializer<BddContext> init = new InitChoixResto();
        Database.SetInitializer(init);
        init.InitializeDatabase(new BddContext());
    }
}

Ceci fait, nous allons pouvoir bricoler sereinement sur notre application.

Créons ensuite une vue toute simple et toute moche qui permet simplement de lister les restaurants. Pour la créer rapidement, vous pouvez bien sûr faire un clic droit dans l’action du contrôleur, puis « ajouter une vue ». La vue sera fortement typée avec une liste de Resto  :

@model List<ChoixResto.Models.Resto>

La vue complète sera :

@model List<ChoixResto.Models.Resto>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Liste des restaurants</title>
    <style type="text/css">
        table {
            border-collapse: collapse;
        }

        td, th {
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <table>
        <tr>
            <th>Nom</th>
            <th>Téléphone</th>
            <th>Modifier</th>
        </tr>
        @foreach (var resto in Model)
        {
        <tr>
            <td>@resto.Nom</td>
            <td>@resto.Telephone</td>
            <td>@Html.ActionLink("Modifier " + resto.Nom, "ModifierRestaurant", new { id = resto.Id })</td>
        </tr>
        }
    </table>
</body>
</html>

Et nous aurons :

La liste des restaurants
La liste des restaurants

Rien de spécial. La seule subtilité est l’utilisation du helper Html.ActionLink qui permet de générer un lien vers l’action ModifierRestaurant , en lui passant en paramètre l’id  du restaurant. Le HTML généré est du genre :

<a href="/Restaurant/ModifierRestaurant/1">Modifier Resto pinambour</a>

Remarque : il est possible de faire en sorte que l’action d’un contrôleur ne porte pas le même nom qu’une méthode de la classe. Par exemple, nous pouvons remplacer :

public ActionResult Index()
{
    return View();
}

par :

[ActionName("Index")]
public ActionResult LeNomQueJeVeux()
{
    return View();
}

En fait, ceci est surtout utile quand nous souhaitons définir deux méthodes ayant la même signature dans le contrôleur, une pour gérer la requête GET et l’autre pour gérer la requête POST, nous y reviendrons.

Passer des paramètres au contrôleur

Comme nous le savons déjà, et conformément à la définition de la route par défaut, le fait de naviguer sur l’URL /Restaurant/ModifierRestaurant/1 va instancier le contrôleur Restaurant, appeler la méthode ModifierRestaurant  et mettre la valeur 1 dans le paramètre nommé id .
Bon, forcément, pour l’instant, cela ne peut pas marcher car nous n’avons pas de méthode ModifierRestaurant . Qu’à cela ne tienne, il suffit de la rajouter :

public ActionResult ModifierRestaurant()
{
    return View();
}

Concomitamment, vous allez devoir créer la vue ModifierRestaurant  qui va avec.

Bien sûr, comme vous avez déjà lu la partie précédente, vous allez me dire que mon contrôleur est incomplet parce qu’il ne permet pas de récupérer la valeur de l’id, en l’occurrence 1. Ne manquerait-il pas un paramètre int id dans la signature de la méthode ModifierRestaurant  ?

Mouahahahaha (<- rire diabolique) ! Que nenni ! Nous pouvons récupérer l’identifiant car nous avons accès à l’URL :

public ActionResult ModifierRestaurant()
{
    string id = Request.Url.AbsolutePath.Split('/').Last();
    ViewBag.Id = id;
    return View();
}

On utilise la propriété Url  de l’objet Request  pour accéder à l’URL. Ensuite, un peu de bidouille sur l’URL et j’obtiens l’id. Je le mets ensuite dans le ViewBag  afin de pouvoir l’afficher dans ma vue avec :

@ViewBag.Id

Mais là, je ne vous apprends rien…

Vous me direz que ce n’est pas terrible comme technique… Et vous aurez raison ! Nous sommes en train de refaire le travail du système de routing (et en moins bien en plus !). Mais il faut savoir que l’objet représentant la requête est accessible depuis notre contrôleur et que nous pouvons nous en servir comme bon nous semble. Il s’agit de l’objet Request .

Bon, bien sûr, il y a une meilleure méthode pour récupérer cet identifiant, et en plus vous la connaissez. Nous pouvons récupérer sa valeur en utilisant un paramètre nommé id dans l’action du contrôleur, par exemple :

public ActionResult ModifierRestaurant(string id)
{
    ViewBag.Id = id;
    return View();
}

Remarquez que cet identifiant étant forcément numérique dans notre cas, vous pouvez remplacer le type string  par le type int  :

public ActionResult ModifierRestaurant(int id)
{
    ViewBag.Id = id;
    return View();
}

Automatiquement, le mécanisme de routing d’ASP.NET MVC tente de convertir le paramètre dans le type approprié. S’il n’y arrive pas, vous aurez une valeur nulle, comme c’est le cas avec la route /Restaurant/ModifierRestaurant/Nicolas. Vous aurez en plus une erreur car l’entier étant obligatoire, il n’est pas possible d’y mettre la valeur nulle. Dans ce cas, vous pouvez remplacer l’utilisation d’un int  par celle d’un int  nullable. Ceci permettra par exemple de renvoyer vers une vue spéciale s’il n’y a pas de valeur dans l’entier :

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        ViewBag.Id = id;
        return View();
    }
    else
        return View("Error");
}

Je ne sais pas si vous vous rappelez, nous avions dit précédemment que la route /Restaurant/ModifierRestaurant/1 était équivalente à /Restaurant/ModifierRestaurant?id=1. Si vous tentez de naviguer sur cette nouvelle URL, vous verrez que nous récupérons bien l’identifiant. C’est bien sûr grâce au mécanisme de routing qui analyse la query string et qui mappe les champs dans les paramètres de l’action.
Mais ! Nous pouvons également faire la même chose et interpréter nous-mêmes les éléments de la query string. Pour cela, changez le code de l’action du contrôleur pour avoir :

public ActionResult ModifierRestaurant()
{
    string idStr = Request.QueryString["id"];
    int id;
    if (int.TryParse(idStr, out id))
    {
        ViewBag.Id = id;
        return View();
    }
    else
        return View("Error");
}

Eh oui, c’est encore l’objet Request qui nous permet d’accéder à la query string et en l’occurrence on peut accéder au paramètre id grâce à Request.QueryString["id"] . Vous aurez noté que nous n’avons pas d’id dans les paramètres de l’action.

Bon, OK, la méthode précédente est quand même la meilleure. Mais cela vous montre les possibilités offertes par la query string. Cela permet notamment de passer plusieurs paramètres sans forcément toucher au système de routing. Ainsi, on pourra naviguer sur l’URL /Restaurant/ModifierRestaurant?id=1&val=3 et récupérer les deux paramètres. Alors que cela sera impossible avec /Restaurant/ModifierRestaurant/1/3

Mettre l’id du restaurant dans le ViewBag , c’est bien, mais c’est un peu limité. Bien sûr, ici nous aurons intérêt à accéder au modèle pour récupérer le restaurant à partir de son id :

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        using (IDal dal = new Dal())
        {
            Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
            if (restaurant == null)
                return View("Error");
            return View(restaurant);
        }
    }
    else
        return View("Error");
}

Une fois ce restaurant obtenu, nous pouvons le passer en tant que modèle de la vue. Le but bien sûr étant de modifier la vue afin qu’elle nous offre la possibilité de modifier ce restaurant. Vous savez déjà tout faire car nous l’avons vu dans le chapitre précédent, il nous faut jouer avec les helpers HTML. Sans faire un truc très joli, nous pouvons avoir :

@using (Html.BeginForm())
{
    <fieldset>
        <legend>Modifier un restaurant</legend>

        <div>
            @Html.LabelFor(model => model.Nom)
            @Html.TextBoxFor(model => model.Nom)
        </div>
        <div>
            @Html.LabelFor(model => model.Telephone)
            @Html.TextBoxFor(model => model.Telephone)
        </div>
        <br />
        <input type="submit" value="Modifier" />
    </fieldset>
}

On retrouve de quoi créer la balise de formulaire, puis un label et un textbox pour le nom du restaurant et son téléphone, sans oublier le bouton de soumission du formulaire. Ce qui donne :

Le formulaire de modification de restaurant
Le formulaire de modification de restaurant

Et voilà où les choses vont commencer à être intéressantes… nous allons apprendre à récupérer les valeurs des formulaires. Depuis le temps que j’en parle, il était temps. :p
La première chose à remarquer, c’est qu’avec notre helper Html.BeginForm  nous générons une balise dont l’action pointera vers l’action ModifierRestaurant  de notre contrôleur. En effet, le HTML généré est :

<form action="/Restaurant/ModifierRestaurant/1" method="post">

Ceci aura pour effet de rappeler la méthode ModifierRestaurant  de notre contrôleur, lorsque nous cliquerons sur le bouton Modifier. Le plus simple pour s’en convaincre est de mettre un point d’arrêt dans la méthode du contrôleur et de constater que nous y passons bien lorsque nous arrivons sur la page (méthode GET) et quand nous soumettons le formulaire (méthode POST). Sauf que du coup, nous allons faire exactement la même chose dans les deux cas, à savoir afficher le restaurant à partir de son id, situé en paramètre du contrôleur. Il nous faut un moyen de déterminer si nous sommes dans le cas d’un POST afin de pouvoir faire les modifications adéquates.

Et si nous utilisions l’objet représentant la requête ? Il y a sûrement des informations là-dedans… Rien de plus simple, nous pouvons accéder au type de méthode via la propriété HttpMethod  et aux éléments du formulaire grâce à la propriété Form  :

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        using (IDal dal = new Dal())
        {
            if (Request.HttpMethod == "POST")
            {
                string nouveauNom = Request.Form["Nom"];
                string nouveauTelephone = Request.Form["Telephone"];
                dal.ModifierRestaurant(id.Value, nouveauNom, nouveauTelephone);
            }

            Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
            if (restaurant == null)
                return View("Error");
            return View(restaurant);
        }
    }
    else
        return View("Error");
}

Modifions les valeurs dans le formulaire, soumettons tout ça et nous pouvoir voir que le changement est bel et bien effectif :

Modification du restaurant à partir des données postées depuis le formulaire
Modification du restaurant à partir des données postées depuis le formulaire

Vous aurez compris que nous accédons aux éléments du formulaire via le nom du contrôle HTML présent dans le formulaire. Étant donné que nous avons cette balise pour le nom :

<input data-val="true" data-val-required="Le champ Nom est requis." id="Nom" name="Nom" type="text" value="Resto pinambour" />

nous devons passer "Nom" en paramètre de l’objet Form  de la requête, comme ce qu’il y a dans l’attribut name du contrôle HTML.

Parfait. ;)

Et si je vous disais que nous pouvons simplifier notre méthode du contrôleur ? Oui, parce que ce gros if en plein milieu pour savoir si on est en POST, et l’accès unitaire à chaque contrôle du formulaire… on se croirait dans les années 1990…

La première bonne nouvelle, c’est qu’ASP.NET MVC est capable automatiquement de transformer les valeurs de l’objet Form en paramètres du contrôleur ayant le même nom. Nous pouvons donc rajouter deux paramètres dans la signature de notre action pour avoir :

public ActionResult ModifierRestaurant(int? id, string nom, string telephone)
{
    if (id.HasValue)
    {
        using (IDal dal = new Dal())
        {
            if (Request.HttpMethod == "POST")
            {
                dal.ModifierRestaurant(id.Value, nom, telephone);
            }

            Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
            if (restaurant == null)
                return View("Error");
            return View(restaurant);
        }
    }
    else
        return View("Error");
}

Ainsi, les paramètres nom  et telephone  seront automatiquement remplis avec les bonnes valeurs quand l’action sera appelée en POST. Par contre, ils vaudront bien sûr null  lorsque l’action sera appelée en GET. Remarquez qu’il est possible de nommer Nom et Telephone sans leur majuscule car la casse n’est pas importante.
Avec cette modification, le code n’en est que plus clair.

Mais si je vous disais qu’on peut définir deux méthodes de contrôleur différentes pour les actions GET et POST ? Ça serait encore mieux ! Regardez ceci :

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        using (IDal dal = new Dal())
        {
            Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
            if (restaurant == null)
                return View("Error");
            return View(restaurant);
        }
    }
    else
        return View("Error");
}

[HttpPost]
public ActionResult ModifierRestaurant(int? id, string nom, string telephone)
{
    if (id.HasValue)
    {
        using (IDal dal = new Dal())
        {
            dal.ModifierRestaurant(id.Value, nom, telephone);
            return RedirectToAction("Index");
        }
    }
    else
        return View("Error");
}

C’est grâce à l’attribut HttpPost  qui permet de restreindre l’action pour ne gérer que les requêtes HTTP de type POST. Ainsi, nous avons découpé nos deux actions GET et POST en deux méthodes distinctes qui font chacune ce qu’elles doivent faire.
Vous aurez remarqué qu’à la fin de la méthode du contrôleur correspondante au POST, j’utilise la méthode RedirectToAction  qui me permet de ré-exécuter l’action Index afin de me repositionner directement sur la liste des restaurants une fois la modification terminée.

Le binding de modèle

Quoi ? On peut encore simplifier ? Nooon !

Regardez cette nouvelle version de l’action en version POST :

[HttpPost]
public ActionResult ModifierRestaurant(Resto resto)
{
    using (IDal dal = new Dal())
    {
        dal.ModifierRestaurant(resto.Id, resto.Nom, resto.Telephone);
        return RedirectToAction("Index");
    }
}

On peut difficilement faire plus simple… Regardez les paramètres de la méthode… ASP.NET MVC fait ici ce qu’on appelle du binding de modèle. C’est-à-dire que grâce aux différents champs qui sont passés à la requête, il est capable de reconstruire notre modèle, en l’occurrence une instance de la classe Resto .
En effet, le paramètre id  qui est passé dans l’URL va parfaitement bien dans la propriété Resto.Id . De même, le contrôle formulaire Nom  s’appelle exactement de la même façon que la propriété Resto.Nom . Et pareil pour le champ Telephone

ASP.NET MVC reconnaît bien que les propriétés ont les mêmes noms que les champs de formulaire. Il fait alors une liaison de données entre les deux, permettant ainsi de créer un objet Resto à partir de tous les éléments de la requête.

Alors ce binding de modèle, c’est franchement quelque chose de bien classe qui va grandement contribuer à la légèreté d’ASP.NET MVC. Nous allons continuer à le pratiquer et vous verrez toute sa puissance.

Exemple de certificat de réussite
Exemple de certificat de réussite