MVC Music Store

| Jon Galloway | Jean-Michel Ormes (Traduction) |

Traducteur

Jean-Michel Ormes

Le MVC Music Store est une application d'étude qui introduit et explique étape par étape comment utiliser ASP.NET MVC et Visual web Developer pour faire du développement web. Le MVC Music Store est un exemple simplifié d'application de magasin qui vend des albums de musique en ligne, implémente l'administration du site, l'authentification d'utilisateurs et la fonctionnalité d'achat sous forme de panier. Ce tutoriel détaille toutes les étapes à suivre pour construire le MVC Music Store.

N'hésitez pas à laisser votre avis sur le contenu de l'article directement via le forum : Commentez ♪

- Titre : MVC Music Store

- Auteurs : Jon Galloway, Jean-Michel Ormes (Traduction)

- Traducteur : Jean-Michel Ormes

- Parution : 13 janvier 2012

- Licence : Copyright ® 2012 Jon Galloway. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Traduction

- I. Aperçu

- II. Fichier -> Nouveau Projet

- II-A. Installer le logiciel

- II-B. Créer un nouveau projet ASP.NET MVC 3

- III. Introduction

- IV. Ajouter un HomeController

- V. Exécuter l'application

- VI. Ajouter un StoreController

- VII. Introduction

- VIII. Ajout d'une Vue

- IX. Ajout d'une mise en page pour les éléments communs du site

- X. Mise à jour de la feuille de style

- XI. Utilisation d'un Modèle pour passer des informations à notre Vue

- XII. Ajout de liens entre les pages

- XIII. Introduction

- XIV. Accès à la base de données avec Entity Framework Code-First

- XIV-A. Modifications de nos classes modèles

- XIV-B. Ajout de la classe modèle Artist

- XIV-C. Mise à jour de nos classes modèles

- XIV-D. Ajout du dossier App_Data

- XIV-E. Création de la chaîne de connexion dans le fichier web.config

- XIV-F. Ajout d'une classe Context

- XIV-G. Ajout de données au catalogue du magasin

- XV. Interroger la base de données

- XV-A. Mettre à jour l'Index du Store pour interroger la base de données

- XV-B. Mise à jour des vues Browse et Details du Store pour utiliser des données en direct

- XVI. Introduction

- XVII. Création du StoreManagerController

- XVIII. Modification d'une Vue échafaudée

- XIX. Un premier regard sur le Store Manager

- XX. Regard sur le code du StoreManagerController

- XX-A. Les actions Index et Details du Store Manager

- XX-B. Les actions Create

- XX-C. Passage d'informations à la vue en utilisant le ViewBag

- XX-D. HTML Helpers pour afficher les DropDownLists dans la vue Create

- XX-E. Gestion des valeurs postées

- XX-F. Lecture des valeurs du formulaire avec liaison du modèle

- XX-G. Validation du modèle

- XX-H. Sauvegarde des valeurs soumises

- XX-I. Affichage de la soumission du formulaire invalide avec des erreurs de validation

- XX-J. Test du formulaire Create

- XX-K. Gestion de l'édition

- XX-L. Gestion de la suppression

- XX-M. Utilisation d'un HTML Helper personnalisé pour tronquer du texte

- XXI. Introduction

- XXII. Ajouter une validation à notre formulaire Album

- XXIII. Tester la validation côté client

- XXIV. Introduction

- XXV. Ajout du AccountController et des vues

- XXVI. Ajout d'un administrateur avec le site de configuration ASP.NET

- XXVII. Autorisation basée sur les rôles

- XXVIII. Introduction

- XXIX. Ajout des classes modèles Cart, Order et OrderDetail

- XXX. Gestion de la logique métier du Panier

- XXXI. ViewModels

- XXXII. Le ShoppingCartController

- XXXIII. Utilisation d'Ajax et jQuery pour les mises à jour

- XXXIV. Introduction

- XXXV. Migration du Panier

- XXXVI. Création du CheckoutController

- XXXVII. Ajout de la vue AddressAndPayment

- XXXVIII. Définition des règles de validation pour la commande

- XXXIX. Ajout de la vue Checkout Complete

- XL. Mise à jour de la vue Error

- XLI. Introduction

- XLII. Création de la vue partielle du résumé du panier

- XLIII. Création de la vue partielle du menu Genre

- XLIV. Mise à jour de la mise en page du site afin d'afficher nos vues partielles

- XLV. Mise à jour de la page de navigation du magasin

- XLVI. Mettre à jour la page d'accueil afin d'afficher les albums les plus vendus

- XLVII. Conclusion

- Remerciements

Traduction

Cet article est la traduction non officielle de l'article original de Jon Galloway,MVC Music Store

Le code source ainsi que le support technique sont disponibles à cette adresse : http://mvcmusicstore.codeplex.com

I - Aperçu

Le MVC Music Store est une application d'étude qui introduit et explique étape par étape comment utiliser ASP.NET MVC et Visual web Developer pour faire du développement web. Nous allons débuter lentement, donc pour ceux qui n'ont pas ou peu d'expérience en développement web, ce tutoriel leur conviendra.

L'application que nous allons construire est un magasin de musique simplifié. Il y a trois principales parties dans cette application : l'achat, le contrôle et l'administration.

Les visiteurs peuvent parcourir les albums par genre :

Ils peuvent afficher un seul album et l'ajouter au panier :

Ils peuvent visualiser leur panier et enlever les éléments dont ils ne veulent plus :

Accéder au paiement les invitera à se connecter ou créer un nouveau compte d'utilisateur.

Après avoir créé un compte, ils peuvent compléter la commande en remplissant les informations de paiement et de livraison. Pour garder les choses simples, nous allons mettre en place une promotion étonnante : tout est gratuit s'ils entrent le code de promotion « FREE » !

Après avoir commandé, ils voient un simple écran de confirmation :

En plus des pages de profils client, nous allons aussi construire une section administration qui affichera une liste des albums à partir de laquelle les administrateurs pourront créer, modifier et supprimer des albums :

II - Fichier -> Nouveau Projet

II-A - Installer le logiciel

On va commencer ce tutoriel en créant un nouveau projet ASP.NET MVC 3 grâce à Visual web Developper 2010 Express (qui est gratuit), puis nous ajouterons au fur et à mesure des fonctionnalités afin de créer une application complète et fonctionnelle. Tout au long de notre parcours, nous couvrirons l'accès à la base de données, l'aspect métier, la validation des données, l'utilisation de pages maîtres pour une mise en page cohérente, l'utilisation d'AJAX pour la validation et la mise à jour des pages, l'authentification d'utilisateurs et bien d'autres.

Vous pouvez suivre étape par étape, ou bien télécharger l'application complète depuis : http://mvcmusicstore.codeplex.com.

Vous pouvez utiliser Visual Studio 2010 SP1 ou Visual web Developer 2010 Express SP1 (une version gratuite de Visual Studio 2010) pour construire cette application. Nous allons utiliser le SQL Server Compact (gratuit également) afin d'héberger la base de données. Avant de commencer, assurez-vous d'avoir installé les prérequis ci-dessous. Vous pouvez les installer en utilisant web Platform Installer grâce à ce lien : http://www.microsoft.com/web/gallery/install.aspx?appid=VWD2010SP1Pack

Note : vous pouvez trouver ce lien sur le gros bouton vert sur ce lien (facile à retenir): http://asp.net/mvc.

web Platform Installer va se charger de vérifier les éléments déjà installés et télécharger ceux dont vous avez besoin.

Si vous souhaitez installer chaque prérequis de façon individuelle en utilisant les liens suivants au lieu du lien ci-dessus, utilisez les liens suivants (écrits au cas où vous utilisez une version imprimée du tutoriel) :

- Visual Studio Web Developer Express SP1 prerequisites. http://www.microsoft.com/web/gallery/install.aspx?appid=VWD2010SP1Pack
.

- ASP.NET MVC 3 Tools Update http://www.microsoft.com/web/gallery/install.aspx?appid=MVC3.

- SQL Server Compact 4.0 - inclut le runtime et les outils d'aide. http://www.microsoft.com/web/gallery/install.aspx?appid=SQLCE;SQLCEVSTools_4_0.

Note : si vous utilisez Visual Studio 2010 au lieu de Visual web Developer 2010, installez les prérequis en utilisant plutôt ce lien :

Visual Studio Web Developer Express SP1 prerequisites. http://www.microsoft.com/web/gallery/install.aspx?appid=VWD2010SP1Pack.

Je vous recommande fortement d'utiliser le lien web Platform Installer, qui s'assurera que vous ayez tout configuré correctement.

II-B - Créer un nouveau projet ASP.NET MVC 3

Nous allons commencer en sélectionnant « New Project » à partir du menu File de Visual web Developer. Ceci fera apparaître la boite de dialogue New Project.

Nous allons sélectionner Visual C# -> web dans les Installed Templates sur la gauche, puis choisir « ASP.NET MVC 3 web Application » dans la colonne centrale. Nommez votre projet MvcMusicStore puis cliquez sur le bouton OK.

Ceci fera apparaître une deuxième boite de dialogue qui nous permettra d'effectuer quelques réglages spécifiques à MVC pour notre projet. Sélectionnez les éléments suivants :

Project Template - sélectionnez Empty ;

View Engine - sélectionnez Razor ;

Cochez Use HTML5 semantic markup.

Vérifiez que vos réglages sont comme ci-dessous, puis cliquez sur le bouton OK.

Cela aura pour effet de créer notre projet. Jetons un œil aux dossiers qui ont été ajoutés à notre application dans l'explorateur de solution à droite.

Le template Empty n'est pas complètement vide - il ajoute une structure composée des dossiers de base :

ASP.NET MVC utilise des conventions de nommage pour les noms de dossiers :

Dossier

Objectif

/Controllers

Les contrôleurs répondent à une entrée en provenance du navigateur, décident de ce qu'ils doivent en faire, puis renvoient la réponse à l'utilisateur.

/Views

Les vues s'en tiennent à l'organisation des modèles UI.

/Models

Les modèles s'en tiennent à l'organisation et la manipulation de données.

/Content

Ce dossier contient nos images, CSS et tout autre contenu statique.

/Scripts

Ce dossier contient nos fichiers JavaScript.

Ces dossiers sont inclus, même dans les applications ASP.NET MVC ayant le template Empty, car le framework ASP.NET MVC utilise par défaut une approche « convention plutôt que configuration » et fait certaines hypothèses par défaut basées sur les conventions de nommage des dossiers. Par exemple, les contrôleurs recherchent les vues dans le dossier Views par défaut sans que vous l'ayez explicitement spécifié dans votre code. S'en tenir aux conventions par défaut réduit la quantité de code que vous aurez à écrire, et peut également faciliter auprès d'autres développeurs, la compréhension de votre projet. Nous détaillerons un peu plus ces conventions quand nous aurons avancé dans la construction de notre application.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

III - Introduction

Avec les frameworks web traditionnels, les URL entrantes sont généralement mappées à des fichiers sur le disque. Par exemple: une demande pour une URL comme « /Products.aspx » ou « /Products.php » pourrait être traitée par un fichier « Products.aspx » ou « Products.php ».

Les frameworks web basés sur le framework MVC mappent les URL sur le code serveur d'une façon légèrement différente. Au lieu de mapper les URL entrantes à des fichiers, ils mappent les URL à des méthodes de classes. Ces classes sont appelées « Contrôleurs » et sont responsables du traitement des requêtes http entrantes, de la gestion des entrées utilisateur, de la récupération et sauvegarde de données, et déterminent la réponse à envoyer au client (afficher du HTML, télécharger un fichier, rediriger vers une URL différente, etc.).

IV - Ajouter un HomeController

Nous allons commencer notre application MVC Music Store en ajoutant une classe contrôleur qui va rediriger les URL vers la page d'accueil de notre site. Nous allons suivre les conventions de nommage par défaut d'ASP.NET MVC et l'appeler HomeController.

Faites un clic droit sur le dossier « Controllers » dans l'explorateur de solution et sélectionnez « Add », « Controller… » :

Cela fera apparaître la boite de dialogue « Add Controller ». Nommez votre contrôleur « HomeController » et cliquez sur le bouton Add.

Cela va créer un nouveau fichier, HomeController.cs, avec le code suivant :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}

Pour commencer le plus simplement possible, nous allons remplacer la méthode Index par une simple méthode qui renverra un string. Nous allons faire deux changements :

- changer la méthode de sorte qu'elle renvoie un string au lieu d'une ActionResult ;

- changer l'instruction de retour pour renvoyer “Hello from Home?.

La méthode devrait maintenant ressembler à ceci :

public string Index()
{
return "Hello from Home";
}

V - Exécuter l'application

Maintenant, exécutons le site. Nous pouvons lancer notre serveur web et essayer notre site en utilisant l'une des méthodes suivantes :

- choisir Start Debugging dans le menu Debug ;

- cliquer sur le bouton vert en forme de flèche dans la barre d'outils

- utiliser le raccourci clavier, F5.

L'utilisation de l'une des étapes précédentes va compiler notre projet, et provoquer le démarrage du serveur de développement ASP.NET qui est intégré à Visual web Developer. Une notification apparaîtra dans le coin inférieur de l'écran indiquant que le serveur de développement ASP.NET a démarré, et affichera le numéro du port utilisé par le serveur.

Visual web Developer ouvrira automatiquement une fenêtre de navigateur dont l'URL pointe vers notre serveur web. Ceci nous permettra de tester rapidement notre application web :

OK, ce fut assez rapide - nous avons créé un nouveau site web, ajouté une fonction de trois lignes, et nous avons fait apparaître du texte dans le navigateur. Rien de bien sorcier, mais c'est déjà un début.

Note: Visual web Developer inclut le serveur de développement ASP.NET, qui va exécuter votre site web sur un numéro de port libre et aléatoire. Dans la capture d'écran ci-dessus, le site s'est lancé sur http://localhost:26641/, donc il utilise le port 26641. Votre numéro de port sera différent. Lorsque nous parlons d'URL comme /Store/Browse dans ce tutoriel, ceci vient après le numéro de port. En supposant que le numéro de port soit 26641, naviguer vers /Store/Browse revient à naviguer vers http://localhost:26641/Store/Browse.

VI - Ajouter un StoreController

Nous avons ajouté un simple HomeController qui implémente la page d'accueil de notre site. Nous allons maintenant ajouter un autre contrôleur que nous utiliserons pour implémenter la fonctionnalité de navigation de notre magasin de musique. Notre contrôleur de magasin va prendre en charge trois scénarios :

- une page listant les genres de musique de notre magasin ;

- une page de navigation qui répertorie tous les albums de musique par genre ;

- une page de détails qui affiche les informations d'un album spécifique.

Nous allons commencer par ajouter une nouvelle classe StoreController. Si ce n'est déjà fait, arrêtez votre application en fermant le navigateur ou en choisissant Stop Debugging dans le menu Debug.

Maintenant, ajoutez un nouveau StoreController. Comme fait précédemment avec le HomeController, nous allons faire cela en effectuant un clic droit sur le dossier « Controllers » dans l'explorateur de solution et choisir « Add », « Controller… » :

Notre nouveau StoreController contient déjà une méthode « Index ». Nous allons utiliser cette méthode « Index » pour implémenter notre page listant tous les genres de notre magasin de musique. Nous allons également ajouter deux autres méthodes qui implémenteront les deux autres scénarios que nous souhaitons que notre StoreController manipule : Browse et Details.

Ces méthodes (Index, Browse et Details) au sein de notre contrôleur sont appelées « Actions », et comme vous avez déjà pu le voir dans la méthode HomeController.Index(), leur travail consiste à répondre à des requêtes URL et (généralement) à déterminer quel contenu doit être envoyé au navigateur où l'utilisateur a invoqué l'URL.

Nous allons commencer notre implémentation du StoreController en changeant la méthode Index() qui va retourner la chaîne « Hello from Store.Index() » et nous allons ajouter des méthodes similaires pour Browse() et Details() :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMusicStore.Controllers
{
public class StoreController : Controller
{
//
// GET: /Store/
public string Index()
{
return "Hello from Store.Index()";
}
//
// GET: /Store/Browse
public string Browse()
{
return "Hello from Store.Browse()";
}
//
// GET: /Store/Details
public string Details()
{
return "Hello from Store.Details()";
}
}
}

Lancez le projet une nouvelle fois et rendez-vous sur les URL suivantes :

- /Store ;

- /Store/Browse ;

- /Store/Details.

Accéder à ces URL invoquera les actions dans notre contrôleur et renverra les chaînes définies :

C'est bien, mais ce sont juste des chaînes statiques. Rendons-les dynamiques, afin qu'elles prennent des informations contenues dans l'URL et l'affichent dans la page de sortie.

En premier lieu, nous allons changer l'action Browse afin de récupérer une valeur querystring de l'URL. Nous pouvons faire ceci en ajoutant un paramètre « genre » à notre action. Lorsque nous faisons cela, ASP.NET MVC va automatiquement passer n'importe quel querystring ou paramètre de formulaire appelé « genre » à notre action lorsqu'elle sera invoquée.

//
// GET: /Store/Browse?genre=Disco
public string Browse(string genre)
{
string message = HttpUtility.HtmlEncode("Store.Browse, Genre = "
+ genre);

return message;
}

Note : nous utilisons la méthode HttpUtility.HtmlEncode pour assainir la saisie utilisateur. Cela empêche les utilisateurs d'injecter du JavaScript dans notre vue avec un lien comme

/Store/Browse?Genre=</i><script>window.location='http://hackersite.com'</script>.

Maintenant, rendons-nous sur /Store/Browse?Genre=Disco

Nous allons maintenant changer l'action Details afin de lire et d'afficher un paramètre d'entrée appelé ID. Contrairement à notre précédente méthode, nous n'allons pas mettre la valeur de l'ID dans un paramètre de type querystring. Nous l'intégrerons directement dans l'URL. Par exemple: /Store/Details/5.

ASP.NET MVC nous permet de le faire facilement sans avoir à configurer quoi que ce soit. La convention de routage par défaut d'ASP.NET MVC est de traiter le segment d'une URL après le nom d'action comme un paramètre appelé « ID ». Si votre action a un paramètre appelé ID, alors ASP.NET MVC va automatiquement passer le segment d'URL pour vous en tant que paramètre.

//
// GET: /Store/Details/5
public string Details(int id)
{
string message = "Store.Details, ID = " + id;

return message;
}

Exécutez l'application et allez sur /Store/Details/5:

Récapitulons ce que nous avons fait jusqu'à présent :

- nous avons créé un nouveau projet ASP.NET MVC dans Visual web Developer ;

- nous avons parlé de la structure des dossiers de base d'une application ASP.NET MVC ;

- nous avons appris à exécuter notre site web en utilisant le serveur de développement ASP.NET ;

- nous avons créé deux classes contrôleur : un HomeController et un StoreController ;

- nous avons ajouté deux actions à nos contrôleurs qui répondent à des requêtes URL et qui renvoient du texte dans le navigateur.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

VII - Introduction

Jusqu'à présent, nous avons renvoyé des strings dans nos Actions. C'est une bonne manière de se faire une idée sur la façon dont fonctionnent les contrôleurs, mais ce n'est pas comme cela que vous construirez une vraie application web. Nous voudrons une meilleure façon de générer du HTML à renvoyer aux navigateurs qui visitent notre site - une façon qui permette d'utiliser des templates pour personnaliser plus facilement le contenu HTML renvoyé. C'est exactement ce que font les Vues.

VIII - Ajout d'une Vue

Pour utiliser une Vue, nous allons modifier la méthode Index du HomeController afin qu'elle renvoie un ActionResult, et ajouter la ligne de code return View(), comme ci-dessous :

public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}

Le changement ci-dessus indique qu'au lieu de retourner un string, nous allons plutôt utiliser une « Vue » pour générer un résultat en retour.

Nous allons maintenant ajouter une Vue appropriée à notre projet. Pour cela, nous allons positionner le curseur dans la méthode Index, puis faire un clic droit et sélectionner « Add View ». Cela aura pour effet de faire apparaître la boite de dialogue Add View:

La boite de dialogue « Add View » nous permet de générer facilement et rapidement une Vue. Par défaut, la boite de dialogue « Add View » préremplit le nom de la Vue à créer de sorte qu'elle corresponde à la méthode qui va l'utiliser. Parce que nous avons utilisé le menu contextuel « Add View » dans la méthode Index() de notre HomeController, la boite de dialogue « Add View » ci-dessus contient « Index » dans le nom de la Vue prérempli par défaut. Nous n'avons besoin de changer aucune option sur cette boite de dialogue, donc nous cliquons sur le bouton Add.

Lorsque nous cliquons sur le bouton Add, Visual web Developer va créer une nouvelle Vue Index.cshtml pour nous dans le répertoire \Views\Home, créant le dossier s'il n'existe pas encore.

Le nom et l'emplacement du dossier du fichier « Index.cshtml » sont importants, et suivent la convention de nommage par défaut d'ASP.NET MVC. Le nom du répertoire, \Views\Home, correspond au contrôleur - qui est appelé HomeController. Le nom de la Vue, Index, correspond à la méthode du contrôleur qui va afficher la vue.

ASP.NET MVC permet d'éviter d'avoir à spécifier explicitement le nom ou le répertoire de la vue lorsque l'on utilise cette convention de nommage pour renvoyer une vue. Il va par défaut soumettre la vue \Views\Home\Index.cshtml lorsque nous écrivons le code ci-dessous dans notre HomeController :

public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}

Visual web Developer a créé et ouvert le template « Index.cshtml » après avoir cliqué sur le bouton « Add » dans la boite de dialogue « Add View ». Le contenu de Index.cshtml est présenté ci-dessous.

@{
ViewBag.Title = "Index";
}
<h2>Index</h2>

Cette vue utilise la syntaxe du moteur Razor, qui est plus concise que le moteur de vue web Forms utilisé dans ASP.NET web Forms et les précédentes versions d'ASP.NET MVC. Le moteur de vue web Forms est toujours disponible dans ASP.NET MVC 3, mais la plupart des développeurs trouvent que le moteur Razor s'adapte très bien au développement sous ASP.NET MVC.

Les trois premières lignes définissent le titre de la page grâce au ViewBag.Title. Nous verrons comment cela fonctionne plus en détail prochainement, mais avant cela ; nous allons tout d'abord mettre à jour le texte de l'entête et afficher la page. Mettez à jour le texte du tag <h2> pour écrire « This is the Home Page » comme indiqué ci-dessous.

@{
ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>

L'exécution de l'application montre que notre nouveau texte est visible sur la page d'accueil.

IX - Ajout d'une mise en page pour les éléments communs du site

La plupart des sites web ont un contenu qui est partagé entre plusieurs pages : navigation, pieds de pages, images, feuilles de styles, etc. Le moteur de vue Razor nous permet de gérer cela facilement en utilisant une page appelée _Layout.cshtml qui a été automatiquement créée pour nous dans le dossier /Views/Shared.

Double-cliquez sur ce dossier pour afficher le contenu, qui est présenté ci-dessous.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")"
type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>

Le contenu de nos vues individuelles sera affiché par la commande @RenderBody(), et tout contenu commun que nous voulons faire apparaître en dehors peut être ajouté au _Layout.cshtml. Nous voulons que notre MVC Music Store ait un entête commun avec des liens menant à la page d'accueil et le magasin sur toutes les pages du site, donc nous allons ajouter cela directement au template juste au-dessus de la déclaration @RenderBody().

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1>
ASP.NET MVC MUSIC STORE</h1>
<ul id="navlist">
<li class="first"><a href="/"
id="current">Home</a></li>
<li><a
href="/Store/">Store</a></li>
</ul>
</div>
@RenderBody()
</body>
</html>

X - Mise à jour de la feuille de style

Le modèle de projet vide comprend un fichier CSS très simplifié qui inclut tous les styles utilisés pour afficher des messages de validation. Notre designer a fourni quelques CSS et images supplémentaires pour définir le look de notre site, nous allons donc les ajouter.

Le fichier CSS et le dossier Images sont inclus dans le répertoire Content de MvcMusicStore-Assets.zip qui est disponible sur http://mvcmusicstore.codeplex.com. Nous allons les sélectionner dans l'explorateur Windows et les déposer dans le dossier Content de notre solution dans Visual web Developer, comme indiqué ci-dessous:

Il vous sera demandé de confirmer si vous souhaitez écraser le fichier existant Site.css. Cliquez sur Yes.

Le dossier Content de votre application apparaît désormais :

Maintenant, nous allons exécuter l'application et voir à quoi ressemblent nos changements sur la page d'accueil.

- Récapitulons ce qui a changé: la méthode Index du HomeController trouve et affiche la vue \Views\Home\Index.cshtml, même si notre code appelle “return View()?, car notre vue respecte les conventions de nommage ;

- la page d'accueil affiche un simple message de bienvenue défini dans la vue \Views\Home\Index.cshtml ;

- la page d'accueil utilise notre vue _Layout.cshtml, ainsi le message de bienvenue est contenu dans la mise en page standard du site HTML.

XI - Utilisation d'un Modèle pour passer des informations à notre Vue

Une Vue qui affiche juste du HTML codé en dur ne rendra pas un site très intéressant. Pour créer un site web dynamique, nous allons plutôt passer des informations de nos Contrôleurs à nos Vues.

Dans le pattern Modèle-Vue-Contrôleur, le terme Modèle se réfère aux objets qui représentent les données dans l'application. Souvent, les objets du modèle correspondent aux tables de votre base de données, mais ce n'est pas une nécessité.

Les méthodes du Contrôleur qui renvoient ActionResult peuvent passer un objet de modèle à la vue. Cela permet à un Contrôleur d'englober proprement toutes les informations nécessaires pour générer une réponse, et ensuite transmettre cette information à une Vue utilisée pour générer la réponse HTML appropriée. C'est plus simple à comprendre en le voyant en action, nous allons donc commencer.

Tout d'abord, nous allons créer quelques classes Modèle pour représenter les genres et albums de notre magasin. Commençons donc par la création de la classe Genre. Faites un clic droit sur le dossier « Models » dans votre projet, choisissez l'option « Add Class », et appelez votre fichier « Genre.cs ».

Puis ajoutons une propriété de type public string appelée Name à la classe qui vient d'être créée:

public class Genre
{
public string Name { get; set; }
}

Note : dans le cas où vous vous poseriez la question, la notation { get; set; } fait usage des propriétés autoimplémentées de C#. Cela nous donne les avantages d'une propriété sans nous obliger à déclarer un champ de sauvegarde.

Ensuite, suivez les mêmes étapes pour créer une classe Album (appelée Album.cs) qui a une propriété Title et un Genre :

public class Album
{
public string Title { get; set; }
public Genre Genre { get; set; }
}

Maintenant nous pouvons modifier le StoreController pour utiliser la Vue qui va afficher des informations dynamiques en provenance de notre Modèle. Si - dès maintenant, à des fins de démonstration - nous avions appelé nos Albums en fonction de l'ID de la requête, nous pourrions afficher ces informations comme dans la vue ci-dessous.

Nous allons commencer par changer l'action Details du magasin afin qu'elle affiche les informations d'un seul album. Ajoutez une instruction « using » tout en haut de la classe StoreControllers pour inclure l'espace de noms MvcMusicStore.Models, de cette façon, nous n'aurons pas besoin de taper MvcMusicStore.Models.Album à chaque fois que nous voudrons utiliser la classe album. La section « usings » de cette classe devrait maintenant être comme ci-dessous.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;

Ensuite, nous allons mettre à jour l'action Details afin qu'elle renvoie un ActionResult plutôt qu'un string, comme nous l'avons fait pour la méthode Index du HomeController.

public ActionResult Details(int id)

Maintenant nous pouvons modifier la logique pour renvoyer un objet Album à la vue. Plus tard dans ce tutoriel, nous récupérerons les données d'une base de données - mais pour l'instant nous allons utiliser des « données factices » pour commencer.

public ActionResult Details(int id)
{
var album = new Album { Title = "Album " + id };
return View(album);
}

Note : si vous n'êtes pas familier avec C#, vous pourriez supposer que l'utilisation de var signifie que notre variable album est liée tardivement. Ce n'est pas correct - le compilateur C# utilise l'inférence de type basée sur ce que nous assignons à la variable pour déterminer qu'album est de type Album et compile la variable locale album en tant que type Album, ainsi nous obtenons une vérification à la compilation et le support de l'éditeur de code Visual Studio.

Nous allons maintenant créer une Vue qui utilise notre Album pour générer une réponse HTML. Avant de faire cela, nous avons besoin de compiler le projet de sorte que la boite de dialogue « Add View » reconnaisse notre nouvelle classe Album. Vous pouvez compiler le projet en sélectionnant l'élément Build MvcMusicStore du menu Debug (vous pouvez également utiliser le raccourci clavier Ctrl-Shift-B).

Maintenant que nous avons mis en place le support de nos classes, nous sommes prêts à construire notre Vue. Faites un clic droit dans la méthode Details et choisissez « Add View… » dans le menu contextuel.

Nous allons créer une nouvelle Vue comme nous l'avions fait auparavant avec le HomeController. Du fait que nous la créons à partir du StoreController, elle sera par défaut générée dans le fichier \Views\Store\Index.cshtml.

Contrairement à précédemment, nous allons cocher la case « Create a strongly-typed view ». Nous allons ensuite sélectionner notre classe « Album » dans le menu déroulant « View data-class ». La boite de dialogue « Add View » va ainsi créer une vue qui attend un objet de type Album.

Lorsque l'on clique sur le bouton « Add » notre Vue \Views\Store\Details.cshtml sera créée, contenant le code suivant :

@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>

Remarquez la première ligne, qui indique que cette vue est fortement typée à notre classe Album. Le moteur de vue Razor comprend qu'on lui a passé un objet Album, donc nous pouvons accéder facilement aux propriétés du modèle et même profiter de l'IntelliSense de Visual web Developer.

Mettez à jour l'élément <h2> de sorte qu'il affiche la propriété Title de l'Album en modifiant cette ligne comme ci-dessous.

<h2>Album: @Model.Title</h2>

Notez que l'IntelliSense se déclenche lorsque vous saisissez après le mot-clé @Model, montrant les propriétés et méthodes que la classe Album supporte.

Nous allons réexécuter maintenant notre projet et visiter l'URL /Store/Details/5. Nous allons voir les détails d'un Album comme ci-dessous.

Maintenant, nous allons faire une mise à jour similaire pour la méthode Browse du magasin. Mettez à jour la méthode afin qu'elle renvoie un ActionResult, et modifiez la logique de la méthode afin qu'elle crée un nouvel objet Genre et qu'elle le renvoie à la Vue.

public ActionResult Browse(string genre)
{
var genreModel = new Genre { Name = genre };
return View(genreModel);
}

Faites un clic droit dans la méthode Browse et sélectionnez « Add View… » dans le menu contextuel, puis ajoutez une Vue qui est fortement typée à la classe Genre.

Mettez à jour l'élément <h2> dans le code de la vue (dans /Views/Store/Browse.cshtml) pour afficher les informations du genre.

@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>

Maintenant nous allons relancer notre projet et aller sur l'URL /Store/Browse?Genre=Disco. Nous verrons la page de recherche affichée comme ci-dessous.

Enfin, nous allons faire une mise à jour un peu plus complexe de la méthode Index du magasin et la vue pour afficher une liste de tous les genres de notre magasin. Nous allons faire cela en utilisant une liste de genres en tant que modèle, plutôt qu'un genre.

public ActionResult Index()
{
var genres = new List<Genre>
{
new Genre { Name = "Disco"},
new Genre { Name = "Jazz"},
new Genre { Name = "Rock"}
};
return View(genres);
}

Faites un clic droit sur la méthode Index du magasin et sélectionnez Add View comme précédemment, sélectionnez Genre dans Model class, et cliquez sur le bouton Add.

Nous allons d'abord changer la déclaration @model pour indiquer que la vue attendra plusieurs objets Genre au lieu d'un seul objet. Modifiez la première ligne de /Store/Index.cshtml comme suit :

@model IEnumerable<MvcMusicStore.Models.Genre>

Cela indique au moteur de vue Razor qu'il va travailler avec un objet qui peut contenir plusieurs objets Genre. Nous utilisons un IEnumerable<Genre> au lieu d'un List<Genre>, car il est plus générique, ce qui nous permet de changer notre type plus tard en n'importe quel type d'objet qui supporte l'interface IEnumerable.

Ensuite, nous allons boucler sur les objets Genre dans le Modèle comme montré dans le code complet de la vue qui suit.

@model IEnumerable<MvcMusicStore.Models.Genre>
@{
ViewBag.Title = "Store";
}
<h3>Browse Genres</h3>
<p>
Select from @Model.Count()
genres:</p>
<ul>
@foreach (var genre in Model)
{
<li>@genre.Name</li>
}
</ul>

Notez que nous avons une prise en charge complète de l'IntelliSense quand nous entrons ce code, de sorte que lorsque l'on tape « @Model. », nous puissions voir toutes les méthodes et propriétés supportées par un IEnumerable de type Genre.

Dans notre boucle « foreach », Visual web Developer sait que chaque élément est de type Genre, donc nous avons l'IntelliSence pour chaque type Genre.

Ensuite, cette fonctionnalité a examiné l'objet Genre et a déterminé que chacun a une propriété Name, donc a bouclé dessus et les a écrits. Cela génère également les liens d'édition, de détails, et de suppression pour chaque élément. Nous allons profiter de cela plus tard dans notre gestionnaire de magasin, mais pour l'instant nous aimerions avoir une simple liste à la place.

Quand nous exécutons l'application et allons sur /Store, nous voyons que le compte et la liste des Genres est affiché.

XII - Ajout de liens entre les pages

Notre URL /Store qui répertorie les Genres liste actuellement les noms de Genre simplement comme du texte brut. Nous allons modifier cela pour que nous ayons les noms de Genre reliés à l'URL /Store/Browse appropriée au lieu d'avoir du texte brut, afin que si l'on clique sur un genre comme « Disco », nous naviguions vers l'URL /Store/Browse?genre=Disco. Nous pourrions mettre à jour notre Vue \Views\Store\Index.cshtml pour faire ressortir ces liens en utilisant le code ci-dessous (ne l'écrivez pas, nous allons l'améliorer) :

<ul>
@foreach (var genre in Model)
{
<li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li>
}
</ul>

Cela fonctionne, mais pourrait conduire à des problèmes plus tard, car elle repose sur une chaîne codée en dur. Par exemple, si nous voulions renommer le Contrôleur, nous aurions besoin de rechercher dans notre code les liens qui ont besoin d'être mis à jour.

Une autre approche que nous pourrions utiliser serait de prendre avantage d'une méthode HTML Helper. ASP.NET MVC inclut les méthodes HTML Helper qui sont disponibles depuis le code de notre Vue pour effectuer une variété de tâches courantes juste comme celle-ci. Le helper Html.ActionLink() est particulièrement utile, et il est facile de construire des liens HTML <a> et de prendre soin des détails gênants comme le fait de s'assurer que les chemins des URL sont proprement encodés.

Html.ActionLink() a plusieurs surcharges différentes qui permettent de spécifier autant d'informations qu'il ne vous faut pour nos liens. Dans le cas le plus simple, vous aurez juste à fournir le lien hypertexte et l'action sur laquelle aller lorsque le lien hypertexte a été cliqué sur le client. Par exemple, nous pouvons lier la méthode Index() du « /Store/ » sur la page Details du Store avec le lien hypertexte « Go to the Store Index » en utilisant l'appel suivant:

@Html.ActionLink("Go
to the Store Index", "Index")

Note : dans ce cas, nous n'avons pas besoin de spécifier le nom du contrôleur, car nous sommes juste en train de nous relier à une autre action avec le même contrôleur qui rend la vue actuelle.

Nos liens vers la page d'accueil auront besoin qu'on leur passe un paramètre, aussi, nous allons donc utiliser une autre surcharge de la méthode Html.ActionLink qui prend trois paramètres :

1. Le texte du lien, qui va afficher le nom du Genre ;

2. Le nom de l'action du contrôleur (Browse) ;

3. Les valeurs des paramètres de routage, en précisant à la fois le nom (Genre) et la valeur (nom du Genre).

Mettant tout cela ensemble, voici comment nous allons écrire ces liens dans la vue Index du magasin:

<ul>
@foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name,
"Browse", new { genre = genre.Name })</li>
}
</ul>

Maintenant, lorsque nous lançons notre projet une nouvelle fois et accédons à l'URL /Store/, nous allons voir une liste de genres. Chaque genre est un lien hypertexte - lorsque l'on clique dessus, cela nous amène sur l'URL /Store/Browse?genre=[genre].

Le code HTML de la liste des genres ressemble à ceci :

<ul>
<li><a href="/Store/Browse?genre=Disco">Disco</a>
</li>
<li><a href="/Store/Browse?genre=Jazz">Jazz</a>
</li>
<li><a href="/Store/Browse?genre=Rock">Rock</a>
</li>
</ul>

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XIII - Introduction

Jusqu'à présent, nous passions des « données factices » depuis nos contrôleurs à nos vues. Maintenant nous sommes prêts à relier une vraie base de données. Dans ce tutoriel nous allons voir comment utiliser SQL Server Compact Edition (souvent appelé SQL CE) comme moteur de base de données. SQL CE est une base de données gratuite et embarquée, qui ne nécessite pas d'installation ou configuration, ce qui la rend très pratique pour le développement local.

XIV - Accès à la base de données avec Entity Framework Code-First

Nous allons utiliser Entity Framework (EF) qui est inclus dans les projets ASP.NET MVC 3 pour interroger et mettre à jour la base de données. EF est un mapping objet-relationnel flexible (ORM) qui permet aux développeurs d'interroger et mettre à jour des données stockées dans une base de données d'une manière orientée objet.

La version 4 d'Entity Framework supporte un paradigme de développement appelé code-first. Code-first nous permet de créer un modèle objet en écrivant de simples classes (également connu comme POCO du « plain-old » CLR objects), et peut même créer la base de données à la volée à partir de vos classes.

XIV-A - Modifications de nos classes modèles

Nous bénéficierons de la fonctionnalité de création de la base de données d'Entity Framework dans ce tutoriel. Avant cela, cependant, effectuons quelques changements mineurs à nos classes modèles en ajoutant certaines choses que nous utiliserons plus tard.

XIV-B - Ajout de la classe modèle Artist

Nos albums vont être associés aux artistes, nous allons donc ajouter une simple classe modèle correspondant à un artiste. Ajoutez une nouvelle classe au dossier Models appelée Artist.cs en utilisant le code ci-dessous.

namespace MvcMusicStore.Models
{
public class Artist
{
public int ArtistId { get; set; }
public string Name { get; set; }
}
}

XIV-C - Mise à jour de nos classes modèles

Mettez à jour la classe Album comme indiqué ci-dessous.

namespace MvcMusicStore.Models
{
public class Album
{
public int      AlbumId     { get; set;
}
public int      GenreId     { get; set; }
public int      ArtistId    { get; set; }
public string   Title       { get; set; }
public decimal  Price       { get; set; }
public string   AlbumArtUrl { get; set; }
public Genre    Genre       { get; set; }
public Artist   Artist      { get; set; }
}
}

Ensuite, effectuez les changements suivants à la classe Genre.

using System.Collections.Generic;

namespace MvcMusicStore.Models
{
public partial class Genre
{
public int      GenreId     { get; set; }
public string   Name        { get; set; }
public string   Description { get; set; }
public List<Album> Albums   { get; set; }
}
}

XIV-D - Ajout du dossier App_Data

Nous allons ajouter un dossier App_Data à notre projet qui accueillera la base de données SQL Server Express. App_Data est un dossier spécial d'ASP.NET qui contient déjà les autorisations correctes pour l'accès à la base de données. Depuis le menu Project, sélectionnez Add ASP.NET Folder, puis App_Data.

XIV-E - Création de la chaîne de connexion dans le fichier web.config

Nous allons ajouter quelques lignes dans le fichier de configuration du site web de sorte qu'Entity Framework sache comment se connecter à la base de données. Double-cliquez sur le fichier Web.config situé à la racine du projet.

Faites défiler vers le bas de ce fichier et ajoutez une section <connectionStrings> directement au-dessus de la dernière ligne, comme suit.

<connectionStrings>
<add name="MusicStoreEntities"
connectionString="Data Source=|DataDirectory|MvcMusicStore.sdf"
providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>
</configuration>

XIV-F - Ajout d'une classe Context

Faites un clic droit sur le dossier Models et ajoutez une nouvelle classe appelée MusicStoreEntities.cs.

Cette classe va représenter le contexte de base de données Entity Framework, et va s'occuper des opérations de création, lecture, mise à jour, et suppression pour nous. Voici le code de cette classe.

using System.Data.Entity;

namespace MvcMusicStore.Models
{
public class MusicStoreEntities : DbContext
{
public DbSet<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
}
}

C'est tout - il n'y a pas d'autre configuration, interfaces spéciales, etc. En étendant la classe de base DbContext, notre classe MusicStoreEntities est capable de gérer les opérations de base de données pour nous. Maintenant que nous l'avons attachée à DbContext, nous allons ajouter quelques propriétés supplémentaires à nos classes modèles pour profiter de quelques informations supplémentaires dans notre base de données.

XIV-G - Ajout de données au catalogue du magasin

Nous allons profiter d'une fonctionnalité d'Entity Framework qui ajoute des données de « semence » à une base de données fraîchement créée. Cela va prépeupler notre catalogue avec une liste de Genres, Artistes, et Albums. Le fichier MvcMusicStore-Assets.zip - qui comprend les fichiers de conception du site utilisé plus tôt dans ce tutoriel - contient une classe avec les données de semence, situé dans un dossier appelé Code.

Dans le dossier Code / Models, trouvez le fichier SampleData.cs et déposez-le dans le dossier Models de notre projet, comme ci-dessous.

Maintenant il nous faut ajouter une ligne de code pour informer Entity Framework de la présence de la classe SampleData. Double-cliquez sur le fichier Global.asax dans la racine du projet pour l'ouvrir et ajoutez la ligne suivante au début de la méthode Application_Start.

protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(
new MvcMusicStore.Models.SampleData());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

À cet instant, nous avons terminé les travaux nécessaires pour configurer Entity Framework pour notre projet.

XV - Interroger la base de données

Nous allons actualiser notre StoreController de sorte qu'au lieu d'utiliser des « données factices » il interroge la base de données pour obtenir les informations. Nous allons commencer par déclarer un champ dans le StoreController qui va récupérer une instance de la classe MusicStoreEntities, appelé storeDB :

public class StoreController : Controller
{
MusicStoreEntities storeDB = new MusicStoreEntities();

XV-A - Mettre à jour l'Index du Store pour interroger la base de données

La classe MusicStoreEntities est maintenue par Entity Framework et expose une propriété collection pour chaque table de notre base. Mettons à jour la méthode Index du StoreController pour retrouver tous les Genres de notre base. Précédemment nous l'avions fait en codant en dur une chaîne. Maintenant, nous pouvons simplement utiliser le contexte d'Entity Framework qui génère une collection à la place :

public ActionResult Index()
{
var genres = storeDB.Genres.ToList();
return View(genres);
}

Il n'y a pas de changement à apporter à notre Vue puisque nous retournons toujours le même StoreIndexViewModel qu'auparavant - nous renvoyons juste des données en direct de notre base maintenant.

Lorsque l'on exécute à nouveau le projet et que l'on va sur l'URL « /Store », nous pourrons voir une liste de tous les Genres de notre base de données :

XV-B - Mise à jour des vues Browse et Details du Store pour utiliser des données en direct

Dans l'action /Store/Browse?genre=[some-genre], nous recherchons un Genre par nom. Nous ne prévoyons qu'un seul résultat, puisque nous ne devrions jamais avoir 2 entrées pour un même nom, et donc nous pouvons utiliser l'extension LINQ .Single() pour interroger l'objet Genre approprié comme ceci (ne l'écrivez pas encore) :

var example = storeDB.Genres.Single(g => g.Name == "Disco");

La méthode Single prend une expression Lambda en paramètre, qui spécifie que nous voulons un unique objet Genre tel que son nom corresponde à la valeur que nous avons définie. Dans le cas ci-dessus, nous chargeons un objet Genre avec la valeur Disco pour le Name.

Nous allons profiter d'une fonctionnalité d'Entity Framework qui nous permet d'indiquer d'autres entités liées que nous voulons aussi bien charger lorsque l'objet Genre est retrouvé. Cette fonctionnalité est appelée Query Result Shaping, et nous permet de réduire le nombre de fois que nous avons besoin d'accéder à la base pour retrouver toutes les informations dont nous avons besoin. Nous voulons préextraire les Albums pour le Genre que nous récupérons, donc nous allons mettre à jour notre requête avec Genres.Include(« Albums ») pour indiquer que nous voulons aussi les albums liés. C'est plus efficace, car il va récupérer les données du Genre et de l'Album à l'aide d'une seule requête en base.

Une fois débarrassée des explications, voici comment l'action Browse du contrôleur mis à jour ressemble :

public ActionResult Browse(string genre)
{
// Retrieve Genre and its Associated Albums from database
var genreModel = storeDB.Genres.Include("Albums")
.Single(g => g.Name == genre);

return View(genreModel);
}

Nous pouvons maintenant mettre à jour la vue Browse du Store pour afficher les albums qui sont disponibles dans chaque Genre. Ouvrez la vue (qui se trouve dans /Views/Store/Browse.cshtml) et ajoutez une liste à puces d'albums comme suit.

@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>
<ul>
@foreach (var album in Model.Albums)
{
<li>
@album.Title
</li>
}
</ul>

Le fait d'exécuter notre application et d'aller sur l'url /Store/Browse?genre=Jazz nous montre que nos résultats sont extraits de la base, et affichent tous les albums du Genre sélectionné.

Nous allons faire la même modification pour l'URL /Store/Details/[id], et remplacer nos données actuelles par une requête en base qui va charger un Album dont l'ID correspond à la valeur du paramètre.

public ActionResult Details(int id)
{
var album = storeDB.Albums.Find(id);

return View(album);
}

Exécuter l'application et aller sur l'url /Store/Details/1 montre que nos résultats sont désormais extraits de la base.

Maintenant que notre page Details du Store est configurée pour afficher un album grâce à son ID, mettons à jour la vue Browse pour la relier à la vue Details. Nous allons utiliser un Html.ActionLink, exactement comme nous l'avions fait pour relier la page Index du Store à la page Browse du Store à la fin du précédent chapitre. Le code source complet de la vue Browse devrait apparaître comme suit.

@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>
<ul>
@foreach (var album in Model.Albums)
{
<li>
@Html.ActionLink(album.Title,
"Details", new { id = album.AlbumId })
</li>
}
</ul>

Nous sommes maintenant en mesure de naviguer depuis notre page Store vers la page Genre, qui liste les albums disponibles, et en cliquant sur un album nous pouvons voir les détails de l'album en question.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XVI - Introduction

Dans le précédent chapitre, nous avons chargé des données depuis notre base et les avons affichées. Dans ce chapitre, nous allons aussi permettre l'édition de données.

XVII - Création du StoreManagerController

Nous allons commencer par créer un nouveau contrôleur appelé StoreManagerController. Pour ce contrôleur, nous allons tirer profit des options d'échafaudage disponibles dans ASP.NET MVC 3 Tools Update. Configurez les options pour la boite de dialogue Add Controller dialog comme ci-dessous.

Lorsque vous cliquez sur le bouton Add, vous pouvez constater que le mécanisme d'échafaudage d'ASP.NET MVC 3 vous a mâché une bonne quantité de travail :

- Il crée le nouveau StoreManagerController avec une variable locale Entity Framework

- Il ajoute un dossier StoreManager au dossier Views du projet.

- Il ajoute les vues Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml, et Index.cshtml, fortement typées à la classe Album

Le nouveau contrôleur StoreManager inclut les opérations CRUD (create, read, update, delete) qui savent comment fonctionne la classe modèle Album et utilisent notre contexte Entity Framework pour les accès en base.

XVIII - Modification d'une Vue échafaudée

Il est important de se rappeler que, ce code qui a été généré pour nous, c'est la norme de codage d'ASP.NET MVC, tout comme nous l'avons écrit tout au long du tutoriel. C'est destiné à vous faire économiser le temps que vous passez sur l'écriture du code du contrôleur et la création des vues fortement typées manuellement, mais ce n'est pas le genre de code généré que vous avez déjà pu voir précédé d'avertissements dans les commentaires vous expliquant que vous ne devez rien changer au code. Ceci est votre code, et attendez-vous à le modifier.

Donc, nous allons commencer par une rapide modification de la vue Index du StoreManager (/Views/StoreManager/Index.cshtml). Cette vue va afficher une table qui liste les albums de notre store avec des liens d'édition / détails / suppression, et inclut les propriétés publiques de l'album. Nous allons retirer le champ AlbumArtUrl, vu qu'il n'est pas très utile dans cet écran. Dans la section <table> du code de la vue, enlevez les éléments <th> et <td> entourant les références faites à AlbumArtUrl, comme indiqué ci-dessous :

<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th>
AlbumArtUrl
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.AlbumArtUrl)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>

Le code de la vue modifié devrait apparaître comme suit :

@model IEnumerable<MvcMusicStore.Models.Album>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Artist.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>

XIX - Un premier regard sur le Store Manager

Maintenant, exécutez l'application et allez sur l'url /StoreManager/. Cela affiche l'Index du Store Manager que nous venons juste de modifier, affichant une liste des albums du store avec des liens d'éditions, de détails, et suppression.

Le fait de cliquer sur le lien d'édition affiche un formulaire avec des champs pour modifier l'album, incluant des dropdownlists pour le genre et l'artiste.

Cliquez sur le lien « Back to List » en bas, puis cliquez sur le lien Details pour un album. Cela va afficher les informations pour un album.

Une nouvelle fois, cliquez sur le lien « Back to List », puis cliquez sur le lien Delete. Une fenêtre de confirmation va s'afficher, affichant les détails de l'album et nous demandant si nous sommes sûrs de vouloir le supprimer.

Le fait de cliquer sur le bouton Delete en bas va supprimer l'album et vous renvoyer à la page d'Index, qui montre bien que l'album a été supprimé.

Nous n'avons pas encore fini avec le Store Manager, mais nous avons déjà un code fonctionnel dans le contrôleur et la vue pour les opérations CRUD.

XX - Regard sur le code du StoreManagerController

Le StoreManagerController contient une bonne quantité de code. Passons-le en revue de haut en bas. Le contrôleur inclut quelques espaces de noms standards pour un contrôleur MVC, ainsi qu'une référence pour notre espace de noms Models. Le contrôleur a une instance privée du MusicStoreEntities, utilisée par chaque action du contrôleur pour les accès à la base.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
public class StoreManagerController : Controller
{
private MusicStoreEntities db = new MusicStoreEntities();

XX-A - Les actions Index et Details du Store Manager

La vue index récupère une liste d'albums, y compris les informations liées au genre et à l'artiste pour chaque album, comme nous l'avons vu précédemment lorsque nous avons travaillé sur la méthode Browse du Store. La vue Index suit les références aux objets liés afin d'afficher le nom du genre et le nom de l'artiste de chaque album, de sorte que le contrôleur puisse être efficace et interroge cette information dans la demande initiale.

//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}

L'action Details du StoreManagerController fonctionne exactement comme l'action Details du StoreController que nous avons écrite précédemment - elle recherche l'album par ID en utilisant la méthode Find(), puis le renvoie à la vue.

//
// GET: /StoreManager/Details/5
public ViewResult Details(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}

XX-B - Les actions Create

Les actions Create sont un peu différentes de celles que nous avons vues jusqu'ici, parce qu'elles gèrent les saisies dans le formulaire. Lorsque les utilisateurs visitent /StoreManager/Create/ ils verront un formulaire vide. Cette page HTML va contenir un élément <form> qui contient des dropdownlists et textboxs où l'on peut saisir les détails de l'album.

Une fois que les utilisateurs ont rempli les valeurs du formulaire, ils peuvent appuyer sur le bouton « Save » pour soumettre ces changements à notre application afin de les sauvegarder dans notre base de données. Lorsque les utilisateurs appuient sur le bouton « save » le <form> va effectuer un postback vers le /StoreManager/Create/ URL et soumettre les valeurs du <form> dans le cadre du protocole HTTP-POST.

ASP.NET MVC nous permet de facilement séparer la logique de ces deux scénarios d'invocations d'URL en nous permettant de mettre en œuvre deux méthodes « Create » séparées dans notre classe StoreManagerController - une qui gère le protocole initial HTTP-GET afin de naviguer vers l'URL /StoreManager/Create/, et une autre pour gérer le protocole HTTP-POST des changements soumis.

XX-C - Passage d'informations à la vue en utilisant le ViewBag

Nous avons utilisé le ViewBag plus tôt dans ce tutoriel, mais je n'en ai pas beaucoup parlé. Le ViewBag nous permet de passer des informations à la vue sans utiliser un objet modèle fortement typé. Dans ce cas, notre action HTTP-GET Edit a besoin de passer une liste de genres et d'artistes au formulaire pour remplir les dropdownlists, et la façon la plus simple de le faire est de les renvoyer comme des éléments ViewBag.

Le ViewBag est un objet dynamique, ce qui signifie que vous pouvez taper ViewBag.Foo ou ViewBag.VotreNomIci sans écrire du code pour définir ces propriétés. Dans notre cas, le code du contrôleur utilise ViewBag.GenreId et ViewBag.ArtistId de sorte que les valeurs des dropdownlists soumises avec le formulaire soient GenreId et ArtistId, qui sont les propriétés de l'album qui vont être mises.

Ces valeurs des dropdownlists sont renvoyées au formulaire en utilisant l'objet SelectList, qui est construit juste pour ce but. Ceci est fait en utilisant le code comme ceci :

ViewBag.GenreId = new SelectList(db.Genres, "GenreId",
"Name");

Comme vous pouvez le voir depuis le code de la méthode, trois paramètres sont utilisés pour créer cet objet :

- La liste des éléments du dropdownlist est affichée. Notez que ce n'est pas juste un string ? nous passons une liste de genres.

- Le paramètre suivant passé au SelectList est la valeur sélectionnée. C'est de cette façon que le SelectList sait comment présélectionner un élément dans la liste. Ce sera plus facile à comprendre lorsque nous verrons le formulaire d'édition, qui est assez similaire.

- Le dernier paramètre est la propriété qui va être affichée. Dans notre cas, cela indique la propriété Genre.Name qui est celle que l'on va montrer à l'utilisateur.

Sachant cela, alors, l'action HTTP-GET Create est assez simple - deux SelectLists sont ajoutées au ViewBag, et aucun objet modèle n'est passé au formulaire (car il n'a pas encore été créé).

//
// GET: /StoreManager/Create
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId",
"Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId",
"Name");
return View();
}

XX-D - HTML Helpers pour afficher les DropDownLists dans la vue Create

Puisque nous avons parlé de la façon dont les valeurs des dropdownlists sont passées à la vue, nous allons jetons un coup d'œil à la vue pour voir comment ces valeurs sont affichées. Dans la vue (/Views/StoreManager/Create.cshtml), vous verrez que l'appel suivant est fait pour afficher le dropdownlist Genre.

@Html.DropDownList("GenreId",
String.Empty)

Ceci est connu comme étant un HTML Helper - une méthode utilitaire qui effectue une tâche de vue commune. Les HTML Helpers sont très utiles pour conserver le code des vues concis et lisible. Le Html.DropDownList helper est fourni par ASP.NET MVC, mais comme nous le verrons plus tard, il est possible de créer nos propres helpers pour les vues, que nous réutiliserons dans notre application.

L'appel Html.DropDownList a juste besoin de dire deux choses - où récupérer la liste à afficher, et quelle valeur (le cas échéant) doit être présélectionnée. Le premier paramètre, GenreId, dit au DropDownList de rechercher une valeur appelée GenreId dans le modèle ou ViewBag. Le deuxième paramètre est utilisé pour indiquer la valeur à afficher initialement dans le dropdownlist. Puisque le formulaire est un formulaire Create, il n'y a pas de valeur présélectionnée et String.Empty est passé.

XX-E - Gestion des valeurs postées

Comme nous l'avons évoqué précédemment, il y a deux méthodes associées à chaque formulaire. La première gère la requête HTTP-GET et affiche le formulaire. La seconde gère la requête HTTP-POST, qui contient les valeurs soumises du formulaire. Notez que l'action du contrôleur a un attribut [HttpPost], qui dit à ASP.NET MVC qu'il ne devrait répondre qu'aux requêtes HTTP-POST.

//
// POST: /StoreManager/Create
[HttpPost]
public ActionResult Create(Album album)
{
if (ModelState.IsValid)
{
db.Albums.Add(album);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId",
"Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId",
"Name", album.ArtistId);
return View(album);
}

Cette action a quatre responsabilités :

1. Lire les valeurs du formulaire

2. Vérifie que les valeurs du formulaire passent les règles de validation

3. Si la soumission du formulaire est valide, enregistre les données et affiche la liste mise à jour

4. Si la soumission du formulaire n'est pas valide, réaffiche le formulaire avec les erreurs de validation

XX-F - Lecture des valeurs du formulaire avec liaison du modèle

L'action du contrôleur traite la soumission du formulaire qui inclut les valeurs GenreId et ArtistId (venant des dropdownlists) et les valeurs des textboxs pour le Title, le Price, et l'AlbumArtUrl. Bien qu'il soit possible d'accéder directement aux valeurs, une meilleure approche serait d'utiliser les capacités de la liaison de modèle intégrée dans ASP.NET MVC. Lorsqu'une action de contrôleur prend un type modèle en paramètre, ASP.NET MVC va tenter de peupler un objet de ce type utilisé dans le formulaire (ainsi que les valeurs de route et querystring). Elle le fait en recherchant les valeurs dont les noms correspondent aux propriétés du modèle, par exemple, lorsque l'on met la valeur GenreId d'un nouvel objet Album, l'action recherche une entrée ayant le nom GenreId. Lorsque vous créez des vues utilisant les méthodes standards dans ASP.NET MVC, les formulaires seront toujours affichés en utilisant les propriétés de noms en tant que noms de champ de saisie, c'est donc les noms de champs qui vont être comparés.

XX-G - Validation du modèle

Le modèle est validé avec un simple appel à ModelState.IsValid. Nous n'avons pas encore ajouté de règles de validation à notre classe Album - nous le ferons d'ici peu - pour l'instant ces vérifications n'ont pas beaucoup de choses à faire. Ce qui est important, c'est que le ModelStat.IsValid s'adapte aux règles de validation que nous avons mises dans notre modèle, ainsi les futurs changements de ces règles ne nécessiteront aucune mise à jour du code de l'action.

XX-H - Sauvegarde des valeurs soumises

Si la soumission du formulaire passe les validations, il est temps d'enregistrer les valeurs dans la base de données. Avec Entity Framework, qui nécessite simplement d'ajouter le modèle à la collection d'albums et appeler SaveChanges.

db.Albums.Add(album);
db.SaveChanges();

Entity Framework génère les commandes SQL appropriées pour persister la valeur. Après avoir sauvegardé les données, nous sommes redirigés vers la liste d'albums et nous pouvons constater nos mises à jour. Cela est fait en renvoyant RedirectToAction avec le nom de l'action du contrôleur que nous voulons afficher. Dans notre cas, il s'agit de la méthode Index.

XX-I - Affichage de la soumission du formulaire invalide avec des erreurs de validation

Dans le cas de saisies invalides du formulaire, les valeurs des dropdownlists sont ajoutées au ViewBag (comme dans le cas de HTTP-GET) et les valeurs du modèle lié sont retransmises à la vue pour l'affichage. Les erreurs de validation sont automatiquement affichées en utilisant le HTML Helper @Html.ValidationMessageFor.

XX-J - Test du formulaire Create

Pour tester ceci, exécutez l'application et aller sur /StoreManager/Create/ - cela vous affichera un formulaire vide qui est retourné par la méthode HTTP-GET Create du StoreController.

Remplissez quelques valeurs et cliquez sur le bouton Create pour soumettre le formulaire.

XX-K - Gestion de l'édition

La paire d'actions d'édition (HTTP-GET et HTTP-POST) sont très similaires aux méthodes de création que nous avons vues. Comme le scénario d'édition consiste à travailler avec un album existant, la méthode HTTP-GET d'édition charge l'album en se basant sur le paramètre « id », passé via la route. Ce code pour retrouver un album par l'AlbumId est le même que celui que nous avons vu précédemment dans l'action Details du contrôleur. Comme avec la méthode HTTP-GET Create, les valeurs des dropdownlists sont renvoyées via le ViewBag. Cela nous permet de renvoyer un album en tant qu'objet modèle à la vue (qui est fortement typée à la classe Album) en passant des données supplémentaires (ex. : une liste de Genres) via le ViewBag.

//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId",
"Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId",
"Name", album.ArtistId);
return View(album);
}

L'action HTTP-POST Edit est très similaire à l'action HTTP-POST Create. La seule différence est qu'au lieu d'ajouter un nouvel album à la collection db.Albums, nous recherchons l'instance actuelle de l'album en utilisant db.Entry(album) et en mettant son statut à Modified. Cela indique à Entity Framework que nous modifions un album existant au lieu d'en créer un.

//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.GenreId = new SelectList(db.Genres, "GenreId",
"Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId",
"Name", album.ArtistId);
return View(album);
}

Nous pouvons tester cela en exécutant l'application et en naviguant vers /StoreManger/, puis en cliquant sur le lien Edit d'un album.

Ceci affiche un formulaire d'édition montré par la méthode HTTP-GET Edit. Remplissez quelques valeurs et cliquez sur le bouton Save.

Cette action poste le formulaire, enregistre les valeurs, et nous renvoie à la liste des albums, montrant que les valeurs ont été mises à jour.

XX-L - Gestion de la suppression

La suppression suit le même schéma que l'édition et la création, utilisant une action qui affiche un formulaire de confirmation, et une autre action qui manipule la soumission du formulaire.

L'action HTTP-GET Delete est exactement la même que notre précédente action Details du Store Manager.

//
// GET: /StoreManager/Delete/5

public ActionResult Delete(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}

On affiche un formulaire qui est fortement typé à un type d'album, utilisant le contenu de la vue Delete.

Le template Delete montre tous les champs du modèle, mais nous pouvons simplifier cela un peu. Modifiez le code de la vue /Views/StoreManager/Delete.cshtml comme suit.

@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Delete";
}
<h2>Delete Confirmation</h2>
<p>Are you sure you want to delete the album titled
<strong>@Model.Title</strong>?
</p>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" />
</p>
<p>
@Html.ActionLink("Back to
List", "Index")
</p>
}

Cela affiche un simple message de confirmation de la suppression.

Cliquer sur le bouton Delete permet la publication du formulaire su le serveur, ce qui exécute l'action DeleteConfirmed.

//
// POST: /StoreManager/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Albums.Find(id);
db.Albums.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}

Notre action HTTP-POST Delete effectue les actions suivantes :

- Charge l'album par ID

- Supprime l'album et sauvegarde les modifications

- Redirige vers l'Index, montre que l'album a été retiré de la liste

Afin de tester ceci, exécutez l'application et rendez vous sur /StoreManager. Sélectionnez un album de la liste et cliquez sur le lien Delete.

Cela affiche notre écran de confirmation de suppression.

Cliquer sur le bouton Delete supprime l'album et nous renvoie sur la page Index du Store Manager, ce qui montre bien que l'album a été supprimé.

XX-M - Utilisation d'un HTML Helper personnalisé pour tronquer du texte

Nous avons un potentiel problème avec notre page Index du Store Manager. Nos titres d'albums et noms d'artistes peuvent tous deux être suffisamment longs pour « casser » la mise en forme de notre tableau. Nous allons créer un HTML Helper personnalisé afin de nous permettre de les tronquer facilement ainsi que d'autres propriétés de nos vues.

La syntaxe @helper de Razor fait qu'il est assez facile de créer vos propres helpers à utiliser dans vos vues. Ouvrez la vue /Views/StoreManager/Index.cshtml et ajoutez le code suivant directement après la ligne @model.

@helper Truncate(string
input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}

Ce helper prend un string et une longueur maximale à autoriser. Si le texte fourni est plus court que la longueur spécifiée, le helper le ressort tel quel. S'il est plus long, alors il tronque le texte et renvoie « … » pour le reste.

Maintenant nous pouvons utiliser notre helper Truncate pour s'assurer que le titre de l'album et le nom de l'artiste valent moins de 25 caractères. Le code complet de la vue utilisant notre nouvel helper Truncate apparaît ci-dessous.

@model IEnumerable<MvcMusicStore.Models.Album>
@helper Truncate(string input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create
New", "Create")
</p>
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Genre.Name)
</td>
<td>
@Truncate(item.Artist.Name, 25)
</td>
<td>
@Truncate(item.Title, 25)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.AlbumId }) |
@Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })
</td>
</tr>
}
</table>

Maintenant lorsque nous naviguons sur l'url /StoreManager/, les albums et titres sont conservés sous nos longueurs maximales.

Note: Cela montre le cas simple de création et d'utilisation d'un helper dans une vue. Pour en savoir un peu plus sur la création des helpers que vous pourriez utiliser sur votre site, vous pouvez voir mon blog : http://bit.ly/mvc3-helper-options

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XXI - Introduction

Nous avons un problème majeur avec nos formulaires Create et Edit : ils n'effectuent aucune validation. Nous pouvons faire certaines choses comme laisser des champs requis vides ou encore taper une lettre dans le champ Price, et la première erreur que nous allons voir apparaître proviendra de la base de données.

Nous pouvons facilement ajouter une validation de données à notre application en ajoutant des DataAnnotations à notre modèle de classes. Les DataAnnotations nous permettent de décrire les règles que nous voulons appliquer à notre modèle de propriétés, ASP.NET MVC se chargeant de les faire respecter et d'afficher les messages appropriés à nos utilisateurs.

XXII - Ajouter une validation à notre formulaire Album

Nous allons utiliser les attributs DataAnnotations suivants :

- Required ? Indique que cette propriété est un champ requis.

- DisplayName ? Définit le texte que l'on veut utiliser dans les champs du formulaire et dans les messages de validation.

- StringLength ? Définit la longueur maximum pour un champ de type chaîne.

- Range ? Donne une valeur minimale et maximale d'un champ de type numérique.

- Bind ? Liste les champs à exclure ou inclure lorsque l'on lie des paramètres ou des données du formulaire au modèle de propriété.

- ScaffoldColumn ? Permet d'avoir des champs cachés dans les formulaires d'édition.

Note : Pour plus d'informations sur la validation de modèle en utilisant les attributs DataAnnotation, voir la documentation MSDN sur http://go.microsoft.com/fwlink/?LinkId=159063

Ouvrez la classe Album et ajoutez les instructions using suivantes en haut.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

Ensuite, mettez à jour les propriétés afin d'ajouter des attributs d'affichage et de validation comme indiqué ci-dessous.

namespace MvcMusicStore.Models
{
[Bind(Exclude = "AlbumId")]
public class Album
{
[ScaffoldColumn(false)]
public int      AlbumId    { get; set; }
[DisplayName("Genre")]
public int      GenreId    { get; set; }
[DisplayName("Artist")]
public int      ArtistId   { get; set; }
[Required(ErrorMessage = "An Album Title is required")]
[StringLength(160)]
public string   Title      { get; set; }
[Required(ErrorMessage = "Price is required")]
[Range(0.01, 100.00,
ErrorMessage = "Price must be between 0.01 and 100.00")]
public decimal Price       { get; set; }
[DisplayName("Album Art URL")]
[StringLength(1024)]
public string AlbumArtUrl { get; set; }
public virtual Genre  Genre    { get; set; }
public virtual Artist Artist   { get; set; }
}
}

Tant que nous y sommes, nous avons également changé les propriétés Genre et Artist en tant que virtual. Cela permet à Entity Framework de les charger si nécessaire.

public virtual Genre    Genre       { get; set; }
public virtual Artist   Artist      { get; set; }

Après avoir ajouté ces attributs à notre modèle Album, nos écrans Create et Edit commencent immédiatement la validation des champs et utilisent les attributs DisplayName que nous avons choisis (e.g. « Album Art Url » au lieu de « AlbumArtUrl »). Lancez l'application et naviguer vers /StoreManager/Create.

Ensuite, nous allons briser certaines règles de validation. Saisissez 0 dans le champ Price et laissez le champ Title vide. Lorsque l'on clique sur le bouton Create, nous voyons le formulaire s'afficher avec des messages d'erreurs de validation indiquant quels champs ne respectent pas les règles de validation que nous avons définies.

XXIII - Tester la validation côté client

La validation côté serveur est très importante dans une application, car les utilisateurs peuvent contourner la validation côté client. Cependant, les formulaires web qui n'implémentent que la validation côté serveur se retrouvent confrontés à 3 problèmes importants.

1. L'utilisateur doit attendre que le formulaire soit posté, validé sur le serveur, et que la réponse soit envoyée au navigateur.

2. L'utilisateur n'a pas un retour immédiat lorsqu'il corrige un champ donc outrepasse les règles de validation.

3. Nous gaspillons les ressources du serveur pour effectuer la logique de validation au lieu de tirer parti du navigateur de l'utilisateur.

Heureusement, les modèles d'ASP.NET MVC 3 ont une validation côté client intégrée, qui ne nécessite aucun travail supplémentaire, quel qu'il soit.

Taper une seule lettre dans le champ Title répond aux exigences de validation, alors le message de validation est immédiatement enlevé.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XXIV - Introduction

Notre contrôleur StoreManager est actuellement accessible à toute personne visitant notre site. Nous allons changer cela afin de restreindre l'accès à la partie administrateurs.

XXV - Ajout du AccountController et des vues

Une différence entre le modèle « full ASP.NET MVC 2 web Application » et le modèle « ASP.NET MVC 2 Empty Web Application » est que le modèle Empty n'inclut pas un AccountController. Nous allons ajouter un contrôleur AccountController en copiant quelques fichiers à partir d'une nouvelle application ASP.NET MVC créée depuis le modèle « full ASP.NET MVC 2 web Application ».

Créez une nouvelle application ASP.NET MVC en utilisant le modèle « full ASP.NET MVC 2 web Application template » et copiez les fichiers suivants dans les mêmes répertoires dans notre projet :

1. Copiez AccountController.cs dans le répertoire Controllers

2. Copiez AccountModels dans le répertoire Models

3. Créez un répertoire Account dans le répertoire Views et copiez-y les quatre vues

Changez l'espace de nom des classes contrôleur et modèle afin qu'ils commencent par MvcMusicStore. La classe AccountController devrait utiliser l'espace de nom MvcMusicStore.Controllers, et la classe AccountModels devrait utiliser l'espace de nom MvcMusicStore.Models.

Note : Ces fichiers sont également disponibles dans le MvcMusicStore-Assets.zip depuis lequel nous avons copié les fichiers de notre site au début du tutoriel. Le fichier Membership est situé dans le répertoire Code.

La solution mise à jour devrait ressembler à ceci :

XXVI - Ajout d'un administrateur avec le site de configuration ASP.NET

Avant de demander une autorisation d'accès à notre site web, nous aurons besoin de créer un utilisateur avec des droits d'accès. La meilleure façon de créer un utilisateur est d'utiliser le site de configuration ASP.NET.

Lancez le site web de configuration ASP.NET en cliquant sur l'icône suivante dans l'explorateur de solution.

Ceci lancera un site web de configuration. Cliquez sur l'onglet Security dans la page d'accueil, puis cliquez sur le lien « Enable roles » au milieu de l'écran.

Cliquez sur le lien « Create or Manage roles ».

Entrez « Administrator » comme nom de rôle et appuyez sur le bouton Add Role.

Cliquez sur le bouton Précédent, puis cliquez sur le lien « Create user » à gauche.

Remplissez les champs d'informations de l'utilisateur sur la gauche avec les informations suivantes :

Field

Value

User Name

Administrator

Password

password123!

Confirm Password

password123!

email

(n'importe quelle adresse email fonctionnera)

Security Question

(Ce que vous voulez)

Security Answer

(Ce que vous voulez)

Note : Vous pouvez bien sûr utiliser n'importe quel mot de passe si vous le souhaitez. Le mot de passe ci-dessus est présenté en tant qu'exemple, et est supposé être celui-ci sur les forums de support sur CodePlex. Les paramètres par défaut d'un mot de passe sécurisé nécessitent un mot de passe de 7 caractères contenant 1 caractère alphanumérique.

Choisissez le rôle Administrator pour cet utilisateur, et cliquez sur le bouton Create User.

À ce stade, vous devriez voir un message indiquant que l'utilisateur a été créé avec succès.

Vous pouvez maintenant fermer la fenêtre du navigateur.

XXVII - Autorisation basée sur les rôles

Maintenant nous pouvons restreindre l'accès au StoreManagerController en utilisant l'attribut [Authorize], précisant que l'utilisateur doit avoir le rôle Administrator pour accéder à n'importe quelle action de la classe.

[Authorize(Roles = "Administrator")]
public class StoreManagerController : Controller
{
// Controller code here
}

Note : L'attribut [Authorize] peut être placé aussi bien au niveau d'une action spécifique qu'au niveau de la classe.

Maintenant, si on essaye de parcourir /StoreManager, on atterrit sur une fenêtre d'authentification :

Une fois connectés à notre nouveau compte Administrateur, nous sommes capables d'aller sur l'écran d'édition d'Album comme auparavant.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XXVIII - Introduction

Nous allons permettre aux utilisateurs de placer des albums dans leurs paniers sans s'identifier, mais ils auront besoin de s'identifier en tant qu'invités pour compléter la commande. Les processus d'achat et de paiement seront séparés en deux contrôleurs : un ShoppingCartController qui permet d'ajouter anonymement des objets au panier, et un CheckoutController qui gère le processus de paiement. Nous allons commencer avec le panier dans cette section, puis construire le processus de paiement dans la section suivante.

XXIX - Ajout des classes modèles Cart, Order et OrderDetail

Nos processus de paiement et de panier feront usage de nouvelles classes. Faites un clic droit sur le dossier Models et ajoutez une classe Cart (Cart.cs) avec le code suivant.

using System.ComponentModel.DataAnnotations;

namespace MvcMusicStore.Models
{
public class Cart
{
[Key]
public int      RecordId    { get; set; }
public string   CartId      { get; set; }
public int      AlbumId     { get; set; }
public int      Count       { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual Album Album  { get; set; }
}
}

Cette classe est assez similaire à d'autres classes que nous avons déjà utilisées jusqu'à présent, à l'exception de l'attribut [Key] pour la propriété RecordId. Nos éléments du panier auront un identifiant string appelé CartID qui permet les achats anonymes, mais la table inclut un entier en clé primaire appelé RecordId. Par convention, Entity Framework Code-First s'attend à ce que la clé primaire pour une table appelée Cart soit CartId ou ID, mais nous pouvons facilement changer cela par des annotations ou par code si on veut. Ceci est un exemple de la façon dont on peut utiliser les conventions simples d'Entity Framework Code-First quand elles nous conviennent, mais nous ne sommes pas contraints par elles quand ce n'est pas le cas.

Ensuite, ajoutez une classe Order (Order.cs) avec le code suivant.

using System.Collections.Generic;

namespace MvcMusicStore.Models
{
public partial class Order
{
public int    OrderId    { get; set; }
public string Username   { get; set; }
public string FirstName  { get; set; }
public string LastName   { get; set; }
public string Address    { get; set; }
public string City       { get; set; }
public string State      { get; set; }
public string PostalCode { get; set; }
public string Country    { get; set; }
public string Phone      { get; set; }
public string Email      { get; set; }
public decimal Total     { get; set; }
public System.DateTime OrderDate      { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
}
}

Cette classe trace le résumé et les informations de livraison pour une commande. Elle ne compile pas encore, car elle contient une propriété de navigation OrderDetails qui dépend d'une classe que nous n'avons pas encore créée. Corrigeons ceci maintenant en ajoutant une classe appelée OrderDetail.cs, en ajoutant le code suivant.

namespace MvcMusicStore.Models
{
public class OrderDetail
{
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public int AlbumId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public virtual Album Album { get; set; }
public virtual Order Order { get; set; }
}
}

Nous allons faire une dernière mise à jour de notre classe MusicStoreEntities afin d'inclure des DbSets qui exposent ces nouvelles classes modèles, et en incluant également un DbSet<Artist>. La classe MusicStoreEntities mise à jour apparaît ci-dessous.

using System.Data.Entity;

namespace MvcMusicStore.Models
{
public class MusicStoreEntities : DbContext
{
public DbSet<Album>     Albums  { get; set; }
public DbSet<Genre>     Genres  { get; set; }
public DbSet<Artist>    Artists {
get; set; }
public DbSet<Cart>
Carts { get; set; }
public DbSet<Order>     Orders
{ get; set; }
public DbSet<OrderDetail>
OrderDetails { get; set; }
}
}

XXX - Gestion de la logique métier du Panier

Ensuite, nous allons créer la classe ShoppingCart dans le dossier Models. Le modèle ShoppingCart gère l'accès aux données de la table Panier. De plus, cela va gérer la logique métier à l'ajout et la suppression des éléments du panier.

Puisque nous ne voulons pas obliger les utilisateurs à s'inscrire juste pour ajouter des éléments à leurs paniers, nous allons leur assigner un identifiant unique temporaire (en utilisant un GUID, ou un identifiant globalement unique) lorsqu'ils accèdent au panier. Nous allons stocker cet ID en utilisant la classe Session d'ASP.NET.

Note : La Session d'ASP.NET est un endroit pratique pour stocker des informations spécifiques à un utilisateur qui expireront après avoir quitté le site. Tout abus de l'état de session peut avoir des conséquences sur les performances de sites plus importants, l'utilisation que nous en ferons conviendra parfaitement à des fins de démonstrations.

La classe ShoppingCart expose les méthodes suivantes :

AddToCart prend un album en paramètre et l'ajoute au panier de l'utilisateur. Puisque la table Panier recense la quantité de chaque album, cela inclut une logique de création d'une nouvelle ligne si nécessaire ou juste l'incrémentation de la quantité si l'utilisateur a déjà commandé une copie de l'album.

RemoveFromCart prend un ID d'Album et l'enlève du panier de l'utilisateur. Si l'utilisateur n'a qu'une copie de l'album dans son panier, la ligne est supprimée.

EmptyCart enlève tous les éléments du panier de l'utilisateur.

GetCartItems récupère une liste des CartItems pour l'affichage ou le traitement.

GetCount récupère le nombre total d'albums qu'un utilisateur a dans son panier.

GetTotal calcule le cout total de tous les éléments présents dans le panier.

CreateOrder convertit le panier en une commande pendant la phase de paiement.

GetCart méthode statique qui permet à nos contrôleurs d'obtenir un objet panier. Elle utilise la méthode GetCartId pour gérer la lecture du CartId depuis la session utilisateur. La méthode GetCartId nécessite le HttpContextBase afin qu'elle puisse lire le CartId de l'utilisateur depuis sa session.

Voici la classe ShoppingCart complète :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMusicStore.Models
{
public partial class ShoppingCart
{
MusicStoreEntities storeDB = new MusicStoreEntities();
string ShoppingCartId { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
// Helper method to simplify shopping cart calls
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public void AddToCart(Album album)
{
// Get the matching cart and album instances
var cartItem = storeDB.Carts.SingleOrDefault(
c => c.CartId == ShoppingCartId
&& c.AlbumId == album.AlbumId);

if (cartItem == null)
{
// Create a new cart item if no cart item exists
cartItem = new Cart
{
AlbumId = album.AlbumId,
CartId = ShoppingCartId,
Count = 1,
DateCreated = DateTime.Now
};
storeDB.Carts.Add(cartItem);
}
else
{
// If the item does exist in the cart,
// then add one to the quantity
cartItem.Count++;
}
// Save changes
storeDB.SaveChanges();
}
public int RemoveFromCart(int id)
{
// Get the cart
var cartItem = storeDB.Carts.Single(
cart => cart.CartId == ShoppingCartId
&& cart.RecordId == id);

int itemCount = 0;

if (cartItem != null)
{
if (cartItem.Count > 1)
{
cartItem.Count--;
itemCount = cartItem.Count;
}
else
{
storeDB.Carts.Remove(cartItem);
}
// Save changes
storeDB.SaveChanges();
}
return itemCount;
}
public void EmptyCart()
{
var cartItems = storeDB.Carts.Where(
cart => cart.CartId == ShoppingCartId);

foreach (var cartItem in cartItems)
{
storeDB.Carts.Remove(cartItem);
}
// Save changes
storeDB.SaveChanges();
}
public List<Cart> GetCartItems()
{
return storeDB.Carts.Where(
cart => cart.CartId == ShoppingCartId).ToList();
}
public int GetCount()
{
// Get the count of each item in the cart and sum them up
int? count = (from cartItems in storeDB.Carts
where cartItems.CartId == ShoppingCartId
select (int?)cartItems.Count).Sum();
// Return 0 if all entries are null
return count ?? 0;
}
public decimal GetTotal()
{
// Multiply album price by count of that album to get
// the current price for each of those albums in the cart
// sum all album price totals to get the cart total
decimal? total = (from cartItems in storeDB.Carts
where cartItems.CartId == ShoppingCartId
select (int?)cartItems.Count *
cartItems.Album.Price).Sum();

return total ?? decimal.Zero;
}
public int CreateOrder(Order order)
{
decimal orderTotal = 0;

var cartItems = GetCartItems();
// Iterate over the items in the cart,
// adding the order details for each
foreach (var item in cartItems)
{
var orderDetail = new OrderDetail
{
AlbumId = item.AlbumId,
OrderId = order.OrderId,
UnitPrice = item.Album.Price,
Quantity = item.Count
};
// Set the order total of the shopping cart
orderTotal += (item.Count * item.Album.Price);

storeDB.OrderDetails.Add(orderDetail);

}
// Set the order's total to the orderTotal count
order.Total = orderTotal;

// Save the order
storeDB.SaveChanges();
// Empty the shopping cart
EmptyCart();
// Return the OrderId as the confirmation number
return order.OrderId;
}
// We're using HttpContextBase to allow access to cookies.
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
// When a user has logged in, migrate their shopping cart to
// be associated with their username
public void MigrateCart(string userName)
{
var shoppingCart = storeDB.Carts.Where(
c => c.CartId == ShoppingCartId);

foreach (Cart item in shoppingCart)
{
item.CartId = userName;
}
storeDB.SaveChanges();
}
}
}

XXXI - ViewModels

Notre ShoppingCartController aura besoin de communiquer quelques informations complexes à ses vues qui ne correspondent pas proprement à nos objets modèles. Nous ne voulons pas modifier nos modèles pour que cela convienne à nos vues ; les classes modèles devraient représenter notre domaine, et non l'interface utilisateur. Une solution serait de passer l'information à nos vues en utilisant la classe ViewBag, comme nous l'avons fait avec les informations des dropdownlists du Store Manager, mais passer beaucoup d'informations via le ViewBag est difficile à gérer.

Une solution serait d'utiliser le pattern ViewModel. Lorsque l'on utilise ce pattern, nous créons des classes fortement typées qui sont optimisées pour les scénarios de notre vue spécifique, et qui exposent les propriétés pour les valeurs/contenus dynamiques dont la vue à besoin. Nos classes contrôleur peuvent remplir et transmettre ces classes optimisées à nos vues afin de les utiliser. Cela permet un type sécurisé, lors de la compilation, et la présence de l'IntelliSense dans les vues.

Nous allons créer deux Vues Modèles pour les utiliser dans notre contrôleur Shopping Cart : le ShoppingCartViewModel va s'occuper du contenu du panier de l'utilisateur, et le ShoppingCartRemoveViewModel sera utilisé pour afficher les informations de confirmation lorsqu'un utilisateur enlève quelque chose de son panier.

Nous allons créer un dossier ViewModels à la racine du projet pour garder les choses organisées. Faites un clic droit sur le projet, sélectionnez Add / New Folder.

Appelez le dossier ViewModels.

Ensuite, ajoutez la classe modèle ShoppingCartViewModel dans le dossier ViewModels. Elle a deux propriétés : une liste d'éléments Cart, et une valeur décimale qui contient le prix total de tous les éléments du panier.

using System.Collections.Generic;
using MvcMusicStore.Models;

namespace MvcMusicStore.ViewModels
{
public class ShoppingCartViewModel
{
public List<Cart> CartItems { get; set; }
public decimal CartTotal { get; set; }
}
}

Maintenant, ajoutez le ShoppingCartRemoveViewModel au dossier ViewModels, avec les quatre propriétés suivantes.

namespace MvcMusicStore.ViewModels
{
public class ShoppingCartRemoveViewModel
{
public string Message { get; set; }
public decimal CartTotal { get; set; }
public int CartCount { get; set; }
public int ItemCount { get; set; }
public int DeleteId { get; set; }
}
}

XXXII - Le ShoppingCartController

Le ShoppingCartController a trois buts principaux : ajouter des éléments au panier, enlever des éléments du panier, et visualiser les éléments du panier. Il fera usage des trois classes que nous venons juste de créer : ShoppingCartViewModel, ShoppingCartRemoveViewModel, et ShoppingCart. Comme dans StoreController et StoreManagerController, nous allons ajouter un champ qui sera une instance de MusicStoreEntities.

Ajoutez le ShoppingCartController en utilisant un template de contrôleur vide.

Voici le contrôleur ShoppingCartController complet. Les actions Index et Add devraient être très familières. Les actions Remove et CartSummary gèrent deux cas spéciaux, dont nous discuterons dans la section suivante.

using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
using MvcMusicStore.ViewModels;

namespace MvcMusicStore.Controllers
{
public class ShoppingCartController : Controller
{
MusicStoreEntities storeDB = new MusicStoreEntities();
//
// GET: /ShoppingCart/
public ActionResult Index()
{
var cart = ShoppingCart.GetCart(this.HttpContext);

// Set up our ViewModel
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal()
};
// Return the view
return View(viewModel);
}
//
// GET: /Store/AddToCart/5
public ActionResult AddToCart(int id)
{
// Retrieve the album from the database
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);

// Add it to the shopping cart
var cart = ShoppingCart.GetCart(this.HttpContext);

cart.AddToCart(addedAlbum);

// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
//
// AJAX: /ShoppingCart/RemoveFromCart/5
[HttpPost]
public ActionResult RemoveFromCart(int id)
{
// Remove the item from the cart
var cart = ShoppingCart.GetCart(this.HttpContext);

// Get the name of the album to display confirmation
string albumName = storeDB.Carts
.Single(item => item.RecordId == id).Album.Title;

// Remove from cart
int itemCount = cart.RemoveFromCart(id);

// Display the confirmation message
var results = new ShoppingCartRemoveViewModel
{
Message = Server.HtmlEncode(albumName) +
" has been removed from your shopping cart.",
CartTotal = cart.GetTotal(),
CartCount = cart.GetCount(),
ItemCount = itemCount,
DeleteId = id
};
return Json(results);
}
//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
public ActionResult CartSummary()
{
var cart = ShoppingCart.GetCart(this.HttpContext);

ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
}
}
}

XXXIII - Utilisation d'Ajax et jQuery pour les mises à jour

Nous allons ensuite créer une page Index du Panier qui est fortement typée au ShoppingCartViewModel et qui utilise le template List utilisant la même méthode que précédemment.

Cependant, au lieu d'utiliser un Html.ActionLink pour enlever les éléments du panier, nous utiliserons jQuery pour « lier » l'événement click pour tous les liens dans cette vue qui ont la classe HTML RemoveLink. Plutôt que de poster le formulaire, ce gestionnaire d'événements va juste faire un appel AJAX à notre action RemoveFromCart. Le RemoveFromCart renvoie un résultat JSON sérialisé, qui est automatiquement transmis à la méthode JavaScript spécifiée dans notre paramètre OnSuccess- handleUpdate dans ce cas. La fonction Javascript handleUpdate analyse les résultats JSON et effectue quatre mises à jour rapides de la page en utilisant jQuery :

1. Enlève les albums supprimés de la liste

2. Met à jour le compteur du panier dans l'entête

3. Affiche un message de mise à jour à l'utilisateur

4. Met à jour le prix total du panier

Puisque le scénario de suppression est géré par un appel Ajax dans la vue Index, nous n'avons pas besoin d'une vue supplémentaire pour l'action RemoveFromCar. Voic le code complet de la vue /ShoppingCart/Index :

@model MvcMusicStore.ViewModels.ShoppingCartViewModel
@{
ViewBag.Title = "Shopping Cart";
}
<script src="/Scripts/jquery-1.4.4.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(function () {
// Document.ready -> link up remove event handler
$(".RemoveLink").click(function () {
// Get the id from the link
var recordToDelete = $(this).attr("data-id");
if (recordToDelete != '') {
// Perform the ajax post
$.post("/ShoppingCart/RemoveFromCart", {"id": recordToDelete },
function (data) {
// Successful requests get here
// Update the page elements
if (data.ItemCount == 0) {
$('#row-' + data.DeleteId).fadeOut('slow');
} else {
$('#item-count-' + data.DeleteId).text(data.ItemCount);
}
$('#cart-total').text(data.CartTotal);
$('#update-message').text(data.Message);
$('#cart-status').text('Cart (' + data.CartCount + ')');
});
}
});
});
function handleUpdate() {
// Load and deserialize the returned JSON data
var json = context.get_data();
var data = Sys.Serialization.JavaScriptSerializer.deserialize(json);

// Update the page elements
if (data.ItemCount == 0) {
$('#row-' + data.DeleteId).fadeOut('slow');
} else {
$('#item-count-' + data.DeleteId).text(data.ItemCount);
}
$('#cart-total').text(data.CartTotal);
$('#update-message').text(data.Message);
$('#cart-status').text('Cart (' + data.CartCount + ')');
}
</script>
<h3>
<em>Review</em> your cart:
</h3>
<p class="button">
@Html.ActionLink("Checkout
>>", "AddressAndPayment", "Checkout")
</p>
<div id="update-message">
</div>
<table>
<tr>
<th>
Album Name
</th>
<th>
Price (each)
</th>
<th>
Quantity
</th>
<th></th>
</tr>
@foreach (var item in
Model.CartItems)
{
<tr id="row-@item.RecordId">
<td>
@Html.ActionLink(item.Album.Title,
"Details", "Store", new { id = item.AlbumId }, null)
</td>
<td>
@item.Album.Price
</td>
<td id="item-count-@item.RecordId">
@item.Count
</td>
<td>
<a href="#" class="RemoveLink"
data-id="@item.RecordId">Remove
from cart</a>
</td>
</tr>
}
<tr>
<td>
Total
</td>
<td>
</td>
<td>
</td>
<td id="cart-total">
@Model.CartTotal
</td>
</tr>
</table>

Afin de tester ceci, nous devons être en mesure d'ajouter des éléments de notre panier. Nous allons mettre à jour notre vue Store Details afin d'inclure un bouton « Add to cart ». Tant qu'on y est, nous pouvons inclure certaines informations additionnelles de l'album que nous avons ajoutées depuis que nous avons fait la dernière mise à jour de cette vue : Genre, Artist, Price, et Album Art. Le code mis à jour de la vue Details du Store devrait apparaître comme suit.

@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Album - " + Model.Title;
}
<h2>@Model.Title</h2>
<p>
<img alt="@Model.Title"
src="@Model.AlbumArtUrl" />
</p>
<div id="album-details">
<p>
<em>Genre:</em>
@Model.Genre.Name
</p>
<p>
<em>Artist:</em>
@Model.Artist.Name
</p>
<p>
<em>Price:</em>
@String.Format("{0:F}",
Model.Price)
</p>
<p class="button">
@Html.ActionLink("Add to
cart", "AddToCart",
"ShoppingCart", new { id = Model.AlbumId }, "")
</p>
</div>

Maintenant nous pouvons cliquer sur le store et essayer d'ajouter et enlever des albums de notre panier. Exécutez l'application et allez sur l'Index du Store.

Puis, cliquez sur un genre pour voir la liste des albums.

Maintenant, le fait de cliquer sur le titre d'un album nous montre la vue Details de l'album mise à jour, incluant le bouton « Add to cart ».

Cliquer sur le bouton « Add to cart » montre la vue Index de notre panier avec la liste des éléments.

Après le chargement de votre panier, vous pouvez cliquer sur le lien Remove du panier pour voir Ajax faire ses mises à jour.

Nous avons construit un panier qui fonctionne et qui permet aux utilisateurs non enregistrés d'ajouter des éléments à leurs paniers. Dans la section suivante, nous allons leur permettre de s'inscrire et de compléter le processus de paiement.

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XXXIV - Introduction

Dans cette section, nous allons créer un CheckoutController qui va recueillir les informations concernant l'adresse et le paiement de l'acheteur. Nous allons demander aux utilisateurs de s'inscrire sur notre site avant les vérifications, donc le contrôleur sera soumis à autorisation.

Les utilisateurs pourront accéder au processus de paiement de leur panier en cliquant sur le bouton « Checkout ».

Si l'utilisateur n'est pas connecté, il sera invité à le faire.

Si la connexion réussit, on affiche la vue AddressAndPayment à l'utilisateur.

Une fois qu'ils ont rempli le formulaire et soumis la commande, ils pourront voir apparaître l'écran de confirmation de la commande.

Si l'on tente d'afficher la vue pour une commande qui n'existe pas ou qui ne nous appartient pas, la vue Error nous sera retournée.

XXXV - Migration du Panier

Alors que le processus d'achat est anonyme, lorsque les utilisateurs cliquent sur le bouton Checkout, ils seront tenus de s'inscrire et s'identifier. Les utilisateurs s'attendront à ce que l'on conserve les informations du panier entre deux visites, nous aurons donc besoin d'associer les informations des paniers aux utilisateurs lorsqu'ils se seront enregistrés ou connectés.

C'est vraiment très simple à faire, car notre classe ShoppingCart a déjà une méthode qui va associer tous les éléments du panier courant à un nom d'utilisateur. Nous aurons juste besoin d'appeler cette méthode lorsqu'un utilisateur s'enregistre ou se connecte.

Ouvrez la classe AccountController que nous avons ajoutée quand nous mettions en place le membership et les autorisations. Ajoutez une instruction using qui référence MvcMusicStore.Models, puis ajoutez la méthode suivante MigrateShoppingCart :

private void MigrateShoppingCart(string UserName)
{
// Associate shopping cart items with logged-in user
var cart = ShoppingCart.GetCart(this.HttpContext);

cart.MigrateCart(UserName);
Session[ShoppingCart.CartSessionKey] = UserName;
}

Puis, modifiez l'action LogOn afin d'appeler MigrateShoppingCart une fois que l'utilisateur aura été validé, comme suit :

//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
MigrateShoppingCart(model.UserName);

FormsAuthentication.SetAuthCookie(model.UserName,
model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1
&& returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") &&
!returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}

Effectuez la même modification pour l'action Register, immédiatement après que le compte de l'utilisateur ait été créé avec succès :

//
// POST: /Account/Register
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email,
"question", "answer", true, null, out
createStatus);

if (createStatus == MembershipCreateStatus.Success)
{
MigrateShoppingCart(model.UserName);

FormsAuthentication.SetAuthCookie(model.UserName, false /*
createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}

C'est tout - maintenant un panier anonyme sera automatiquement transféré à un compte d'utilisateur lors d'un enregistrement réussi ou une connexion.

XXXVI - Création du CheckoutController

Faites un clic droit sur le dossier Controllers et ajoutez un nouveau contrôleur au projet appelé CheckoutController en utilisant le template « Empty controller ».

Tout d'abord, ajoutez l'attribut Authorize au-dessus de la déclaration de la classe contrôleur pour obliger les utilisateurs à s'inscrire avant le paiement :

namespace MvcMusicStore.Controllers
{
[Authorize]
public class CheckoutController : Controller

Note: Ceci est similaire au changement que nous avons effectué auparavant dans le StoreManagerController, mais dans ce cas l'attribut Authorize nécessite que l'utilisateur soit dans un rôle Administrator. Dans le CheckoutController, nous exigeons que l'utilisateur soit connecté, mais nous n'exigeons pas qu'il soit administrateur.

Par souci de simplicité, nous n'allons pas traiter les informations de paiement dans ce tutoriel. Au lieu de cela, nous allons autoriser les utilisateurs à utiliser un code promo. Nous allons stocker ce code promo en utilisant une constante appelée PromoCode.

Comme dans le StoreController, nous allons déclarer un champ qui sera une instance de la classe MusicStoreEntities, appelé storeDB. Afin d'utiliser la classe MusicStoreEntities, nous aurons besoin d'ajouter une instruction using pour l'espace de noms MvcMusicStore.Models. Le début de notre contrôleur Checkout apparaît comme suit.

using System;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
[Authorize]
public class CheckoutController : Controller
{
MusicStoreEntities storeDB = new MusicStoreEntities();
const string PromoCode = "FREE";

Le CheckoutController aura les actions suivantes :

AddressAndPayment (GET method) va afficher un formulaire qui permet à l'utilisateur d'entrer ses informations.

AddressAndPayment (POST method) va valider les saisies et procéder à la commande.

Complete sera affichée une fois que l'utilisateur aura finalisé avec succès le processus de paiement. Cette vue inclura le numéro de commande de l'utilisateur, comme confirmation.

En premier lieu, renommons l'action Index (qui a été générée lorsque nous avons créé le contrôleur) de AddressAndPayment. Cette action affiche juste le formulaire de paiement, et ne nécessite aucune information sur le modèle.

//
// GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
return View();
}

Notre méthode POST AddressAndPayment va suivre le même pattern que celui que nous avons utilisé dans le StoreManagerController : il va essayer d'accepter la soumission du formulaire et remplir la commande, et va ré-afficher le formulaire si cela échoue.

Une fois que la validation des saisies du formulaire répond à nos exigences de validation d'une commande, nous allons vérifier la valeur PromoCode du formulaire directement. En supposant que tout est correct, nous allons sauvegarder les informations mises à jour avec la commande, indiquer à l'objet ShoppingCart de completer le processus de commande, et de rediriger vers l'action Complete.

//
// POST: /Checkout/AddressAndPayment
[HttpPost]
public ActionResult AddressAndPayment(FormCollection values)
{
var order = new Order();
TryUpdateModel(order);

try
{
if (string.Equals(values["PromoCode"], PromoCode,
StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.Username = User.Identity.Name;
order.OrderDate = DateTime.Now;

//Save Order
storeDB.Orders.Add(order);
storeDB.SaveChanges();
//Process the order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);

return RedirectToAction("Complete",
new { id = order.OrderId });
}
}
catch
{
//Invalid - redisplay with errors
return View(order);
}
}

Après avoir réussi le processus de paiement, les utilisateurs vont être redirigés vers l'action Complete du contrôleur. Cette action va effectuer une simple vérification afin de valider que la commande appartient bien à l'utilisateur connecté avant d'afficher le numéro de commande pour confirmation.

//
// GET: /Checkout/Complete
public ActionResult Complete(int id)
{
// Validate customer owns this order
bool isValid = storeDB.Orders.Any(
o => o.OrderId == id &&
o.Username == User.Identity.Name);

if (isValid)
{
return View(id);
}
else
{
return View("Error");
}
}

Note : La vue Error a été automatiquement créée pour nous dans le dossier /Views/Shared lorsque nous avons commencé le projet.

Le code complet du CheckoutController est le suivant :

using System;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
[Authorize]
public class CheckoutController : Controller
{
MusicStoreEntities storeDB = new MusicStoreEntities();
const string PromoCode = "FREE";
//
// GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
return View();
}
//
// POST: /Checkout/AddressAndPayment
[HttpPost]
public ActionResult AddressAndPayment(FormCollection values)
{
var order = new Order();
TryUpdateModel(order);

try
{
if (string.Equals(values["PromoCode"], PromoCode,
StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.Username = User.Identity.Name;
order.OrderDate = DateTime.Now;

//Save Order
storeDB.Orders.Add(order);
storeDB.SaveChanges();
//Process the order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);

return RedirectToAction("Complete",
new { id = order.OrderId });
}
}
catch
{
//Invalid - redisplay with errors
return View(order);
}
}
//
// GET: /Checkout/Complete
public ActionResult Complete(int id)
{
// Validate customer owns this order
bool isValid = storeDB.Orders.Any(
o => o.OrderId == id &&
o.Username == User.Identity.Name);

if (isValid)
{
return View(id);
}
else
{
return View("Error");
}
}
}
}

XXXVII - Ajout de la vue AddressAndPayment

Maintenant, nous allons créer la vue AddressAndPayment. Faites un clic droit sur une des actions du contrôleur AddressAndPayment et ajoutez une vue appelée AddressAndPayment qui est fortement typée à une commande et qui utilise le template Edit, comme montré ci-dessous.

Cette vue se servira de deux des techniques que nous avons observées pendant la construction de la vue StoreManagerEdit :

- Nous allons utiliser Html.EditorForModel() pour afficher les champs du formulaire pour le modèle Order

- Nous allons tirer parti des règles de validation en utilisant la classe Order avec les attributs de validation

Nous allons commencer par mettre à jour le code du formulaire en utilisant Html.EditorForModel(), suivi d'une textbox supplémentaire pour le code promo. Le code complet de la vue AddressAndPayment est montré ci-dessous.

@model MvcMusicStore.Models.Order
@{
ViewBag.Title = "Address And Payment";
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"
type="text/javascript"></script>
@using (Html.BeginForm()) {

<h2>Address And Payment</h2>
<fieldset>
<legend>Shipping Information</legend>
@Html.EditorForModel()
</fieldset>
<fieldset>
<legend>Payment</legend>
<p>We're running a promotion: all music is free
with the promo code: "FREE"</p>
<div class="editor-label">
@Html.Label("Promo Code")
</div>
<div class="editor-field">
@Html.TextBox("PromoCode")
</div>
</fieldset>

<input type="submit" value="Submit Order" />
}

XXXVIII - Définition des règles de validation pour la commande

Maintenant que notre vue est prête, nous allons mettre en place les règles de validation pour notre modèle Commande comme nous l'avons fait précédemment pour le modèle Album. Faites un clic droit sur le dossier Models et ajoutez une classe appelée Order. En plus des attributs de validation que nous avons auparavant utilisés pour l'album, nous allons également utiliser une expression régulière pour valider l'adresse email de l'utilisateur.

using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace MvcMusicStore.Models
{
[Bind(Exclude = "OrderId")]
public partial class Order
{
[ScaffoldColumn(false)]
public int OrderId { get; set; }
[ScaffoldColumn(false)]
public System.DateTime OrderDate { get; set; }
[ScaffoldColumn(false)]
public string Username { get; set; }
[Required(ErrorMessage = "First Name is required")]
[DisplayName("First Name")]
[StringLength(160)]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
[DisplayName("Last Name")]
[StringLength(160)]
public string LastName { get; set; }
[Required(ErrorMessage = "Address is required")]
[StringLength(70)]
public string Address { get; set; }
[Required(ErrorMessage = "City is required")]
[StringLength(40)]
public string City { get; set; }
[Required(ErrorMessage = "State is required")]
[StringLength(40)]
public string State { get; set; }
[Required(ErrorMessage = "Postal Code is required")]
[DisplayName("Postal Code")]
[StringLength(10)]
public string PostalCode { get; set; }
[Required(ErrorMessage = "Country is required")]
[StringLength(40)]
public string Country { get; set; }
[Required(ErrorMessage = "Phone is required")]
[StringLength(24)]
public string Phone { get; set; }
[Required(ErrorMessage = "Email Address is required")]
[DisplayName("Email Address")]

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
ErrorMessage = "Email is is not valid.")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[ScaffoldColumn(false)]
public decimal Total { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
}
}

Le fait de tenter de soumettre le formulaire avec des informations manquantes ou invalides va maintenant afficher un message d'erreur en utilisant la validation côté client.

Ok, nous avons fait le plus dur du travail pour le processus de paiement ; nous avons juste quelques bricoles à finaliser. Nous avons besoin d'ajouter deux vues simples, et nous avons besoin de prendre soin de la non-intervention des informations du panier durant le processus de connexion.

XXXIX - Ajout de la vue Checkout Complete

La vue Checkout Complete est assez simple, car elle a juste besoin d'afficher l'ID de la commande. Faites un clic droit sur l'action Complete et ajoutez une vue appelée Complete qui est fortement typée en tant qu'entier.

Maintenant, nous allons mettre à jour le code de la vue pour afficher l'ID de la commande, comme ci-dessous.

@model int
@{
ViewBag.Title = "Checkout Complete";
}
<h2>Checkout Complete</h2>
<p>Thanks for your order! Your order number is: @Model</p>
<p>How about shopping for some more music in our
@Html.ActionLink("store",
"Index", "Home")
</p>

XL - Mise à jour de la vue Error

Le template de base inclut une vue Error qui se trouve dans le dossier vue Shared afin qu'elle puisse être réutilisée n'importe où dans le site. Cette vue Error contient une très simple erreur et n'utilise pas la mise en page de notre site, donc nous allons la mettre à jour.

Comme il s'agit d'une page d'erreur générique, le contenu est très simple. Nous allons inclure un message et un lien pour accéder à la page précédente de notre historique si les utilisateurs veulent réessayer leur action.

@{
ViewBag.Title = "Error";
}

<h2>Error</h2>

<p>We're sorry, we've hit an unexpected error.
<a href="javascript:history.go(-1)">Click here</a>
if you'd like to go back and try that again.</p>

Vous pouvez utiliser les discussions disponibles sur http://mvcmusicstore.codeplex.com pour toutes questions ou tous commentaires.

XLI - Introduction

Nous avons finalisé toutes les fonctionnalités majeures de notre site, mais il nous reste encore quelques fonctionnalités à ajouter à la navigation du site, la page d'accueil et la page de navigation du magasin.

XLII - Création de la vue partielle du résumé du panier

Nous voulons exposer le nombre d'articles du panier de l'utilisateur sur l'ensemble du site.

Nous pouvons facilement implémenter cela en créant une vue partielle que l'on ajoute à notre Site.master.

Comme indiqué précédemment, le contrôleur ShoppingCart inclut une action CartSummary qui retourne une vue partielle :

//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
public ActionResult CartSummary()
{
var cart = ShoppingCart.GetCart(this.HttpContext);

ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
}

Pour créer la vue partielle CartSummary, faites un clic droit sur le dossier Views/ShoppingCart folder et choisissez « Add View ». Nommez-la « CartSummary » et cochez l'option « Create a partial view » comme indiqué ci-dessous.

La vue partielle CartSummary est vraiment simple - c'est juste un lien vers la vue Index ShoppingCart qui montre le nombre d'articles dans le panier. Le code complet pour le fichier CartSummary.cshtml se présente comme suit :

@Html.ActionLink("Cart
(" + ViewData["CartCount"] + ")",
"Index",
"ShoppingCart",
new { id = "cart-status" })

Nous pouvons inclure une vue partielle au sein de n'importe quelle page du site, y compris le Site.master, en utilisant la méthode Html.RenderAction. RenderAction nous oblige à préciser le nom de l'action (« CartSummary ») et le nom du contrôleur (« ShoppingCart ») comme ci-dessous.

@Html.RenderAction("CartSummary",
"ShoppingCart")

Avant de l'ajouter à la mise en page du site, nous allons également créer le menu Genre afin de n'effectuer toutes les mises à jour du Site.master qu'une seule fois.

XLIII - Création de la vue partielle du menu Genre

Nous pouvons rendre la vie beaucoup plus facile à nos utilisateurs qui naviguent à travers le magasin en ajoutant un menu Genre qui va lister tous les genres disponibles dans notre magasin.

Nous allons suivre les mêmes étapes en créant une vue partielle GenreMenu, et ensuite nous pourrons ajouter les deux vues au Site.master. Tout d'abord, ajoutez l'action GenreMenu suivante au StoreController :

//
// GET: /Store/GenreMenu
[ChildActionOnly]
public ActionResult GenreMenu()
{
var genres = storeDB.Genres.ToList();
return PartialView(genres);
}

Cette action retourne une liste de Genres qui sera affichée par la vue partielle que nous allons bientôt créer.

Note : nous avons ajouté l'attribut [ChildActionOnly] à cette action, ce qui indique que nous voulons que cette action soit utilisée uniquement par une vue partielle. Cet attribut permettra d'éviter qu'elle soit utilisée en parcourant /Store/GenreMenu. Ce n'est pas préconisé pour les vues partielles, mais c'est une bonne pratique, dès lors que nous voulons être sûr que notre action soit utilisée telle que nous le souhaitons. Nous retournons également une vue partielle plutôt qu'une vue, ce qui permet au moteur de vue de savoir qu'il ne doit pas utiliser de layout pour cette vue, comme il est inclus dans les autres vues.

Faites un clic droit sur l'action GenreMenu et créez une vue partielle appelée GenreMenu qui est fortement typée en utilisant la classe Genre comme montré ci-dessous.

Mettez à jour le code de la vue partielle GenreMenu afin d'afficher les articles en utilisant une liste non ordonnée comme suit.

@model IEnumerable<MvcMusicStore.Models.Genre>
<ul id="categories">
@foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name,
"Browse", "Store",
new { Genre = genre.Name }, null)
</li>
}
</ul>

XLIV - Mise à jour de la mise en page du site afin d'afficher nos vues partielles

Nous pouvons ajouter nos vues partielles à la mise en page du site (/Views/Shared/_Layout.cshtml) en appelant Html.RenderAction(). Nous allons les ajouter, ainsi que quelques balises supplémentaires pour les afficher, comme montré ci-dessous :

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet"
type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1><a href="/">ASP.NET MVC MUSIC
STORE</a></h1>
<ul id="navlist">
<li class="first">
<a href="@Url.Content("~")" id="current">
Home</a></li>
<li><a href="@Url.Content("~/Store/")">Store</a></li>
<li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li>
<li><a href="@Url.Content("~/StoreManager/")">
Admin</a></li>
</ul>
</div>
@{Html.RenderAction("GenreMenu", "Store");}
<div id="main">
@RenderBody()
</div>
<div id="footer">
built with <a href="http://asp.net/mvc">ASP.NET MVC 3</a>
</div>
</body>
</html>

Dès lors, lorsque nous exécutons l'application, nous verrons le genre dans la zone de navigation de gauche et le résumé du panier en haut.

XLV - Mise à jour de la page de navigation du magasin

La page de navigation du magasin est fonctionnelle, mais n'est pas très jolie. Nous pouvons mettre à jour la page afin d'afficher les albums de plus belle façon en mettant à jour le code de la vue (qui se trouve dans /Views/Store/Browse.cshtml) comme suit :

@model MvcMusicStore.Models.Genre

@{
ViewBag.Title = "Browse Albums";
}

<div class="genre">
<h3><em>@Model.Name</em> Albums</h3>

<ul id="album-list">
@foreach (var album in Model.Albums)
{
<li>
<a href="@Url.Action("Details",
new { id = album.AlbumId })">
<img alt="@album.Title"
src="@album.AlbumArtUrl" />
<span>@album.Title</span>
</a>
</li>
}
</ul>
</div>

Ici, nous faisons usage de Url.Action plutôt que Html.ActionLink de sorte que nous puissions appliquer un formatage spécial sur le lien pour y inclure l'illustration de l'album.

Remarque : nous affichons une couverture générique pour ces albums. Cette information est stockée dans la base de données et modifiable via le Store Manager. Vous êtes donc invités à ajouter vos propres créations.

Maintenant, lorsque nous parcourrons les genres, nous pourrons voir les albums affichés dans une grille avec l'illustration.

XLVI - Mettre à jour la page d'accueil afin d'afficher les albums les plus vendus

Nous voulons présenter nos albums les plus vendus sur la page d'accueil afin d'augmenter les ventes. Nous allons donc faire quelques mises à jour du HomeController pour gérer cela et ajouter quelques éléments graphiques supplémentaires également.

Dans un premier temps, nous allons ajouter une propriété de navigation à la classe Album de sorte qu'EntityFramework sache qu'ils sont associés. Les dernières lignes de notre classe Album devraient maintenant ressembler à ceci :

public virtual Genre  Genre                  { get; set; }
public virtual Artist Artist                 { get; set; }
public virtual List<OrderDetail>OrderDetails { get; set; }
}
}

Note : ceci nécessitera l'ajout d'une instruction using nous permettant d'avoir accès au namespace System.Collections.Generic.

Premièrement, nous allons ajouter un champ storeDB et l'instruction MvcMusicStore.Models, comme dans nos autres contrôleurs. Ensuite, nous allons ajouter la méthode suivante au HomeController qui va interroger notre base de données et trouver les albums les plus vendus en fonction d'OrderDetails.

private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();
}

C'est une méthode privée, car nous ne voulons pas la rendre disponible en tant qu'action. Nous l'inclurons dans le HomeController pour plus de simplicité, mais vous êtes encouragés à déplacer votre logique métier dans des classes services plus appropriées.

Avec cela en place, nous pouvons mettre à jour l'action Index du contrôleur afin d'obtenir le top 5 des albums les plus vendus et les retourner à la vue.

public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);

return View(albums);
}

Le code complet du HomeController mis à jour est indiqué ci-dessous.

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
MusicStoreEntities storeDB = new MusicStoreEntities();
public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);

return View(albums);
}
private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();
}
}
}

Enfin, nous aurons besoin de mettre à jour la vue Index du Home afin qu'elle puisse afficher une liste d'albums en mettant à jour le Model et d'ajouter la liste d'albums en bas. Nous allons en profiter pour ajouter également un titre et une section promotion à la page.

@model List<MvcMusicStore.Models.Album>
@{
ViewBag.Title = "ASP.NET MVC Music Store";
}
<div id="promotion">
</div>
<h3><em>Fresh</em> off the grill</h3>
<ul id="album-list">
@foreach (var album in Model)
{
<li><a href="@Url.Action("Details", "Store",
new { id = album.AlbumId })">
<img alt="@album.Title" src="@album.AlbumArtUrl" />
<span>@album.Title</span>
</a>
</li>
}
</ul>

Maintenant, lorsque nous lançons l'application, nous allons voir notre page d'accueil mise à jour avec le top des albums les plus vendus et notre message de promotion.

XLVII - Conclusion

Nous avons vu comment avec ASP.NET MVC il est facile de créer un site web sophisitiqué avec accès à la base de données, le membership, AJAX, etc. assez rapidement. Espérons que ce tutoriel vous a donné les bases dont vous avez besoin pour commencer à bâtir votre propre application ASP.NET MVC !

Remerciements

Je tiens à remercier Jon Galloway pour son aimable autorisation de traduire l'article, ainsi que ClaudeLELOUP, Philippe Vialatte, Thomas Levesque, jacques_jean et jpcheck pour la relecture orthographique.