Professional Documents
Culture Documents
NET MVC
par l'exemple
http://tahe.developpez.com
1/362
http://tahe.developpez.com
2/362
http://tahe.developpez.com
3/362
http://tahe.developpez.com
4/362
9.16.1 LE PROBLME....................................................................................................................................................................305
9.16.2 CRITURE DE L'ACTION SERVEUR [VOIRSIMULATIONS].........................................................................................................305
9.17 TAPE 11 : TERMINER LA SESSION.......................................................................................................................................306
9.17.1 LE PROBLME....................................................................................................................................................................306
9.17.2 CRITURE DE L'ACTION SERVEUR [TERMINERSESSION].........................................................................................................307
9.17.3 MODIFICATION DE LA FONCTION JAVASCRIPT [TERMINERSESSION]........................................................................................307
9.18 TAPE 12 : EFFACER LA SIMULATION...................................................................................................................................307
9.18.1 LE PROBLME....................................................................................................................................................................307
9.18.2 CRITURE DE L'ACTION CLIENT [EFFACERSIMULATION].........................................................................................................308
9.19 TAPE 13 : RETIRER UNE SIMULATION.................................................................................................................................308
9.19.1 LE PROBLME....................................................................................................................................................................308
9.19.2 CRITURE DE L'ACTION CLIENT [RETIRERSIMULATION].........................................................................................................309
9.19.3 CRITURE DE L'ACTION SERVEUR [RETIRERSIMULATION]......................................................................................................309
9.20 TAPE 14 : AMLIORATION DE LA MTHODE D'INITIALISATION DE L'APPLICATION.............................................................309
9.20.1 AJOUT DES RFRENCES [SPRING] AU PROJET WEB...............................................................................................................311
9.20.2 CONFIGURATION DE [WEB.CONFIG]......................................................................................................................................312
9.20.3 MODIFICATION DE [APPLICATION_START]............................................................................................................................313
9.20.4 GRER UNE ERREUR D'INITIALISATION DE L'APPLICATION......................................................................................................313
9.21 O EN SOMMES NOUS ?....................................................................................................................................................316
9.22 TAPE 15 : MISE EN PLACE DE LA COUCHE ENTITY FRAMEWORK 5...................................................................................318
9.22.1 LA BASE DE DONNES.........................................................................................................................................................318
9.22.2 LE PROJET VISUAL STUDIO.................................................................................................................................................322
9.22.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................324
9.22.4 LES ENTITS ENTITY FRAMEWORK......................................................................................................................................325
9.22.5 CONFIGURATION DE L'ORM EF5........................................................................................................................................328
9.22.6 TEST DE LA COUCHE [EF5].................................................................................................................................................330
9.22.7 DLL DE LA COUCHE [EF5].................................................................................................................................................333
9.23 TAPE 16 : MISE EN PLACE DE LA COUCHE [DAO].............................................................................................................333
9.23.1 L'INTERFACE DE LA COUCHE [DAO]...................................................................................................................................333
9.23.2 LE PROJET VISUAL STUDIO.................................................................................................................................................334
9.23.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................334
9.23.4 IMPLMENTATION DE LA COUCHE [DAO]............................................................................................................................335
9.23.5 CONFIGURATION DE LA COUCHE [DAO]..............................................................................................................................337
9.23.6 TEST DE LA COUCHE [DAO]...............................................................................................................................................337
9.23.7 DLL DE LA COUCHE [DAO]...............................................................................................................................................339
9.24 TAPE 17 : MISE EN PLACE DE LA COUCHE [MTIER]..........................................................................................................339
9.24.1 L'INTERFACE DE LA COUCHE [MTIER].................................................................................................................................339
9.24.2 LE PROJET VISUAL STUDIO.................................................................................................................................................339
9.24.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................339
9.24.4 IMPLMENTATION DE LA COUCHE [MTIER]..........................................................................................................................340
9.24.5 CONFIGURATION DE LA COUCHE [MTIER]...........................................................................................................................341
9.24.6 TEST DE LA COUCHE [METIER]............................................................................................................................................342
9.24.7 DLL DE LA COUCHE [MTIER]............................................................................................................................................343
9.25 TAPE 18 : MISE EN PLACE DE LA COUCHE [WEB]...............................................................................................................343
9.25.1 LE PROJET VISUAL STUDIO.................................................................................................................................................344
9.25.2 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................345
9.25.3 IMPLMENTATION DE LA COUCHE [WEB]..............................................................................................................................346
9.25.4 CONFIGURATION DE LA COUCHE [WEB]................................................................................................................................346
9.25.5 TEST DE LA COUCHE [WEB].................................................................................................................................................347
10 CONCLUSION.................................................................................................................................................................... 350
11 RENDRE ACCESSIBLE SUR INTERNET UNE APPLICATION ASP.NET............................................................... 351
http://tahe.developpez.com
5/362
1 Introduction
Nous nous proposons ici d'introduire l'aide d'exemples les notions importantes de ASP.NET MVC, un framework Web .NET qui
fournit un cadre pour dvelopper des applications Web selon le modle MVC (Modle Vue Contrleur).
La comprhension des exemples de ce document ncessite peu de pr-requis. Seules des connaissances de base du langage C# sont
ncessaires. On pourra les trouver dans le document Introduction au langage C# l'URL
[http://tahe.developpez.com/dotnet/csharp]. Une tude de cas est propose pour mettre en pratique les connaissances acquises sur
ASP.NET MVC. Elle utilise l'ORM (Object Relational Mapper) Entity Framework. Cet ORM est prsent dans le document
Introduction Entity Framework 5 Code First disponible l'URL [http://tahe.developpez.com/dotnet/ef5cf].
Les divers exemples de ce document sont disponibles l'URL [http://tahe.developpez.com/dotnet/aspnetmvc] sous la forme d'un
fichier zip tlcharger.
Ce document est incomplet bien des gards. Pour approfondir ASP.NET MVC, on pourra utiliser les rfrences suivantes :
[ref1] : le livre " Pro ASP.NET MVC 4 " crit par Adam Freeman aux ditions Apress. C'est un excellent livre. Il serait
disponible en ligne gratuitement et en franais que le prsent document n'aurait pas lieu d'tre. Je recommande tous ceux
qui le peuvent de s'initier ASP.NET MVC l'aide de ce livre. Les codes source des exemples du livre sont eux
disponibles gratuitement l'URL [http://www.apress.com/9781430242369] ;
[ref2] : le site du framework [http://www.asp.net/mvc]. On y trouvera tout le matriel ncessaire pour une autoformation. Une version franaise est disponible l'URL [http://dotnet.developpez.com/mvc/ ] ;
le site de [developpez.com] consacr ASP.NET [http://dotnet.developpez.com/aspnet/ ].
Le document a t crit de telle faon qu'il puisse tre lu sans ordinateur sous la main. Aussi, donne-t-on beaucoup de copies
d'cran.
1.1
Tout d'abord, situons ASP.NET MVC dans le dveloppement d'une application Web. Le plus souvent, celle-ci sera btie sur une
architecture multicouche telle que la suivante :
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[ORM]
Connecteur
[ADO.NET]
Sgbd
Spring.net
la couche [Web] est la couche en contact avec l'utilisateur de l'application Web. Celui-ci interagit avec l'application Web au
travers de pages Web visualises par un navigateur. C'est dans cette couche que se situe ASP.NET MVC et
uniquement dans cette couche ;
la couche [mtier] implmente les rgles de gestion de l'application, tels que le calcul d'un salaire ou d'une facture. Cette
couche utilise des donnes provenant de l'utilisateur via la couche [Web] et du SGBD via la couche [DAO] ;
la couche [DAO] (Data Access Objects), la couche [ORM] (Object Relational Mapper) et le connecteur ADO.NET grent
l'accs aux donnes du SGBD. La couche [ORM] fait un pont entre les objets manipuls par la couche [DAO] et les
lignes et les colonnes des tables d'une base de donnes relationnelle. Deux ORM sont couramment utiliss dans le
monde .NET, NHibernate (http://sourceforge.net/projects/nhibernate/ ) et Entity Framework
(http://msdn.microsoft.com/en-us/data/ef.aspx ) ;
l'intgration des couches peut tre ralise par un conteneur d'injection de dpendances (Dependency Injection Container)
tel que Spring (http://www.springframework.net/ ) ;
La plupart des exemples donns dans la suite, n'utiliseront qu'une seule couche, la couche [Web] :
http://tahe.developpez.com
6/362
Utilisateur
Couche
[Web]
Utilisateur
Couche
[mtier]
Couche
[Web]
Connecteur
[ADO.NET]
Couche
ORM / Entity
Framework
Couche
[DAO]
SGBD
Spring
1.2
ASP.NET MVC implmente le modle d'architecture dit MVC (Modle Vue Contrleur) de la faon suivante :
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
l'action choisie peut exploiter les paramtres parami que le [Front Controller] lui a transmis. Ceux-ci peuvent provenir
de plusieurs sources :
dans le traitement de la demande de l'utilisateur, l'action peut avoir besoin de la couche [metier] [2b]. Une fois la
demande du client traite, celle-ci peut appeler diverses rponses. Un exemple classique est :
l'action demande une certaine vue de s'afficher [3]. Cette vue va afficher des donnes qu'on appelle le modle de la
vue. C'est le M de MVC. L'action va crer ce modle M [2c] et demander une vue V de s'afficher [3] ;
http://tahe.developpez.com
7/362
3. rponse - la vue V choisie utilise le modle M construit par l'action pour initialiser les parties dynamiques de la rponse HTML
qu'elle doit envoyer au client puis envoie cette rponse.
Maintenant, prcisons le lien entre architecture Web MVC et architecture en couches. Selon la dfinition qu'on donne au modle,
ces deux concepts sont lis ou non. Prenons une application Web ASP.NET MVC une couche :
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
Sgbd
2c
Si nous implmentons la couche [Web] avec ASP.NET MVC, nous aurons bien une architecture Web MVC mais pas une
architecture multicouche. Ici, la couche [Web] s'occupera de tout : prsentation, mtier, accs aux donnes. Ce sont les actions qui
feront ce travail.
Maintenant, considrons une architecture Web multicouche :
Utilisateur
Couche
[Web]
Couche
[metier]
Couche
[ADO]
Couche
[ORM]
Sgbd
Spring
La couche [Web] peut tre implmente sans framework et sans suivre le modle MVC. On a bien alors une architecture
multicouche mais la couche Web n'implmente pas le modle MVC.
Ainsi, la couche [Web] ci-dessus peut tre implmente avec ASP.NET MVC et on a alors une architecture en couches avec une
couche [Web] de type MVC. Ceci fait, on peut remplacer cette couche ASP.NET MVC par une couche ASP.NET classique
(WebForms) tout en gardant le reste (mtier, DAO, ORM) l'identique. On a alors une architecture en couches avec une couche
[Web] qui n'est plus de type MVC.
Dans MVC, nous avons dit que le modle M tait celui de la vue V, c.a.d. l'ensemble des donnes affiches par la vue V. Une autre
dfinition du modle M de MVC est donne :
Utilisateur
Couche
[Web]
Couche
[metier]
Couche
[DAO]
Couche
[ORM]
Sgbd
Spring
Beaucoup d'auteurs considrent que ce qui est droite de la couche [Web] forme le modle M du MVC. Pour viter les ambigits
on peut parler :
du modle du domaine lorsqu'on dsigne tout ce qui est droite de la couche [Web]
du modle de la vue lorsqu'on dsigne les donnes affiches par une vue V
Dans la suite, le terme " modle M " dsignera exclusivement le modle d'une vue V.
http://tahe.developpez.com
8/362
1.3
Dans la suite, nous utilisons (septembre 2013) les versions Express de Visual Studio 2012 :
aller sur le site de [Google Web store] (https://chrome.google.com/webstore) avec le navigateur Chrome ;
chercher l'application [Advanced Rest Client] :
pour l'obtenir, il vous faudra crer un compte Google. [Google Web Store] demande ensuite confirmation [1] :
http://tahe.developpez.com
9/362
1.4
en [2], l'extension ajoute est disponible dans l'option [Applications] [3]. Cette option est affiche sur chaque nouvel onglet
que vous crez (CTRL-T) dans le navigateur.
Les exemples
Application web
couche [web]
1
2a
Front Controller
Navigateur
4b
Vue1
Vue2
Vuen
Contrleurs/
Actions
2c
Modles
Une fois termin cet apprentissage, nous prsenterons une application Web multicouche :
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
http://tahe.developpez.com
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
10/362
Numro
Rle
Exemples courants
Unix, Linux, Windows
OS Serveur
Serveur Web
Codes excuts ct serveur. Ils peuvent l'tre par des modules du serveur
ou par des programmes externes au serveur (CGI).
JAVASCRIPT (Node.js)
PHP (Apache, IIS)
JAVA (Tomcat, Websphere, JBoss,
Weblogic, ...)
C#, VB.NET (IIS)
Base de donnes - Celle-ci peut tre sur la mme machine que le programme
qui l'exploite ou sur une autre via Internet.
OS Client
Navigateur Web
http://tahe.developpez.com
11/362
2.1
Numro
1
Rle
Le navigateur demande une URL pour la 1re fois (http://machine/url). Auncun paramtre n'est pass.
Le serveur Web lui envoie la page Web de cette URL. Elle peut tre statique ou bien dynamiquement gnre par un
script serveur (SA) qui a pu utiliser le contenu de bases de donnes (SB, SC). Ici, le script dtectera que l'URL a t
demande sans passage de paramtres et gnrera la page Web initiale.
Le navigateur reoit la page et l'affiche (CA). Des scripts ct navigateur (CB) ont pu modifier la page initiale envoye
par le serveur. Ensuite par des interactions entre l'utilisateur (CD) et les scripts (CB) la page Web va tre modifie.
Les formulaires vont notamment tre remplis.
L'utilisateur valide les donnes du formulaire qui doivent alors tre envoyes au serveur Web. Le navigateur
redemande l'URL initiale ou une autre selon les cas et transmet en mme temps au serveur les valeurs du formulaire.
Il peut utiliser pour ce faire deux mthodes appeles GET et POST. A rception de la demande du client, le serveur
dclenche le script (SA) associ l'URL demande, script qui va dtecter les paramtres et les traiter.
Le serveur dlivre la page Web construite par programme (SA, SB, SC). Cette tape est identique l'tape 2
prcdente. Les changes se font dsormais selon les tapes 2 et 3.
http://tahe.developpez.com
12/362
2.2
Une page statique est reprsente par un fichier HTML. Une page dynamique est une page HTML gnre " la vole" par le
serveur Web.
2.2.1
Construisons un premier projet Web avec Visual Studio Express 2012. On utilise l'option [Fichier / Nouveau projet] :
4
3
2
en [1], on indique que l'on veut construire une application ASP.NET vide ;
en[2], le nom de la solution Visual Studio. Tous les exemples de ce document seront dans la mme solution ;
en [3], le dossier parent de celui du projet qui va tre cr ;
en [4], le nom du projet.
On valide.
Le projet rsultant est prsent en [5]. Nous allons l'utiliser pour illustrer les grands principes de la programmation Web.
Commenons par crer une page HTML statique :
http://tahe.developpez.com
13/362
http://tahe.developpez.com
14/362
5.
<title>essai 1 : une page statique</title>
6. </head>
7. <body>
8.
<h1>Une page statique...</h1>
9. </body>
10. </html>
ligne 5 : dfinit le titre de la page sera affich comme titre de la fentre du navigateur affichant la page ;
ligne 8 : un texte en gros caractres (<h1>).
2.2.2
en [2], le navigateur a reu la page HTML que nous avions construite. Il l'a interprte et en a fait un affichage graphique.
Crons maintenant une page ASP.NET. C'est une page HTML qui peut contenir du code excut ct serveur et qui gnre
certaines parties de la page. On suit une dmarche analogue celui de la cration de la page HTML :
http://tahe.developpez.com
15/362
2
3
On retrouve des balises HTML dj rencontres. Les balises qui ont l'attribut [runat= "server "] sont des balises qui vont tre
traites par le serveur et transformes en balises HTML pures. Donc ce qu'on voit ci-dessus, n'est pas comme dans le cas de la page
statique prcdente le code HTML que va recevoir le navigateur. On parle alors de page dynamique : le flux HTML envoy au
serveur est produit par du code excut ct serveur. Modifions la page comme suit :
http://tahe.developpez.com
16/362
4
1
Si on rafrachit la page (F5), nous obtenons un autre affichage (nouvelle heure) alors que l'URL ne change pas. C'est l'aspect
dynamique de la page : son contenu peut changer au fil du temps. Regardons maintenant le code HTML reu par le navigateur :
http://tahe.developpez.com
17/362
2.2.3
Conclusion
On retriendra de ce qui prcde la nature fondamentalement diffrente des pages dynamiques et statiques.
2.3
Scripts ct navigateur
Une page HTML peut contenir des scripts qui seront excuts par le navigateur. Le principal langage de script ct navigateur est
actuellement (sept 2013) Javascript. Des centaines de bibliothques ont t construites avec ce langage pour faciliter la vie du
dveloppeur.
Construisons une nouvelle page HTML [1] dans le projet dj cr :
http://tahe.developpez.com
18/362
ligne 13 : dfinit un bouton (attribut type) avec le texte " Cliquez-moi " (attribut value). Lorsqu'on clique dessus, la
fonction Javascript [ragir] est excute (attribut onclick) ;
lignes 6-10 : un script Javascript ;
lignes 7-9 : la fonction [ragir] ;
ligne 8 : affiche une bote de dialogue avec le message [Vous avez cliqu sur le bouton].
Lorsqu'on clique sur le bouton, il n'y a pas d'changes avec le serveur. Le code Javascript est excut par le navigateur.
Avec les trs nombreuses bibliothques Javascript disponibles, on peut dsormais embarquer de vritables applications sur le
navigateur. On tend alors vers les architectures suivantes :
Serveur de donnes
Navigateur client
Serveur HTML
1-4 : le serveur HTML est un serveur de pages statiques HTML5 / CSS / Javascript ;
5-6 : les pages HTML5 / CSS / Javascript dlivres interagissent directement avec le serveur de donnes. Celui-ci dlivre
uniquement des donnes sans habillage HTML. C'est le Javascript qui les insre dans des pages HTML dj prsentes sur
le navigateur.
Dans cette architecture, le code Javascript peut devenir lourd. On cherche alors le structurer en couches comme on le fait pour le
code ct serveur :
Utilisateur
Couche
[UI]
Couche
[metier]
Couche
[DAO]
Navigateur
Serveur de donnes
http://tahe.developpez.com
19/362
2.4
la couche [mtier] rassemble les procdures mtier qui n'interagissent ni avec l'utilisateur, ni avec le serveur de donnes.
Cette couche peut ne pas exister.
Revenons notre schma de dpart qui illustrait les acteurs d'une application Web :
Nous nous intressons ici aux changes entre la machine cliente et la machine serveur. Ceux-ci se font au travers d'un rseau et il est
bon de rappeler la structure gnrale des changes entre deux machines distantes.
2.4.1
Le modle OSI
Le modle de rseau ouvert appel OSI (Open Systems Interconnection Reference Model) dfini par l'ISO (International
Standards Organisation) dcrit un rseau idal o la communication entre machines peut tre reprsente par un modle sept
couches :
7
6
5
4
3
2
1
|-------------------------------------|
|
Application
|
|-------------------------------------|
|
Prsentation
|
|-------------------------------------|
|
Session
|
|-------------------------------------|
|
Transport
|
|-------------------------------------|
|
Rseau
|
|-------------------------------------|
|
Liaison
|
|-------------------------------------|
|
Physique
|
|-------------------------------------|
http://tahe.developpez.com
20/362
Chaque couche reoit des services de la couche infrieure et offre les siens la couche suprieure. Supposons que deux applications
situes sur des machines A et B diffrentes veulent communiquer : elles le font au niveau de la couche Application. Elles n'ont pas
besoin de connatre tous les dtails du fonctionnement du rseau : chaque application remet l'information qu'elle souhaite
transmettre la couche du dessous : la couche Prsentation. L'application n'a donc connatre que les rgles d'interfaage avec la
couche Prsentation. Une fois l'information dans la couche Prsentation, elle est passe selon d'autres rgles la couche Session et ainsi
de suite, jusqu' ce que l'information arrive sur le support physique et soit transmise physiquement la machine destination. L, elle
subira le traitement inverse de celui qu'elle a subi sur la machine expditeur.
A chaque couche, le processus expditeur charg d'envoyer l'information, l'envoie un processus rcepteur sur l'autre machine
apartenant la mme couche que lui. Il le fait selon certaines rgles que l'on appelle le protocole de la couche. On a donc le
schma de communication final suivant :
7
6
5
4
3
2
1
Machine A
Machine B
+-------------------------------------+
+----------------------------+
Application
v
^
Application
+------------------------------------
+---------------------------
Prsentation
v
^
Prsentation
+------------------------------------
+---------------------------
Session
v
^
Session
+------------------------------------
+---------------------------
Transport
v
^
Transport
+------------------------------------
+---------------------------
Rseau
v
^
Rseau
+------------------------------------
+---------------------------
Liaison
v
^
Liaison
+------------------------------------
+---------------------------
Physique
v
^
Physique
+------------------------------------+
+---------------------------+
^
+-->------->------>-----+
Assure la transmission de bits sur un support physique. On trouve dans cette couche des quipements
terminaux de traitement des donnes (E.T.T.D.) tels que terminal ou ordinateur, ainsi que des quipements de
terminaison de circuits de donnes (E.T.C.D.) tels que modulateur/dmodulateur, multiplexeur,
concentrateur. Les points d'intrt ce niveau sont :
Liaison de
donnes
Rseau
Masque les particularits physiques de la couche Physique. Dtecte et corrige les erreurs de transmission.
Transport
Permet la communication entre deux applications alors que les couches prcdentes ne permettaient que la
communication entre machines. Un service fourni par cette couche peut tre le multiplexage : la couche
transport pourra utiliser une mme connexion rseau (de machine machine) pour transmettre des
informations appartenant plusieurs applications.
Session
On va trouver dans cette couche des services permettant une application d'ouvrir et de maintenir une
session de travail sur une machine distante.
Prsentation
Elle vise uniformiser la reprsentation des donnes sur les diffrentes machines. Ainsi des donnes
provenant d'une machine A, vont tre "habilles" par la couche Prsentation de la machine A, selon un format
standard avant d'tre envoyes sur le rseau. Parvenues la couche Prsentation de la machine destinatrice B
qui les reconnatra grce leur format standard, elles seront habilles d'une autre faon afin que l'application
de la machine B les reconnaisse.
Application
A ce niveau, on trouve les applications gnralement proches de l'utilisateur telles que la messagerie
lectronique ou le transfert de fichiers.
http://tahe.developpez.com
Gre le chemin que doivent suivre les informations envoyes sur le rseau. On appelle cela le routage :
dterminer la route suivre par une information pour qu'elle arrive son destinataire.
21/362
2.4.2
Le modle TCP/IP
Le modle OSI est un modle idal. La suite de protocoles TCP/IP s'en approche sous la forme suivante :
+----------------+
+---------------------------+
Application
Application
+----------------+
+---------------------------+
<----------- messages ou streams ---------->
+----------------+
+---------------------------+
Transport
Transport
(Udp/Tcp)
(Udp/tcp)
+----------------+
+---------------------------+
<----------- datagrammes (UDP) ----------->
+----------------+
ou
+---------------------------+
Rseau (IP)
segments (TCP)
Rseau (IP)
+----------------+
+---------------------------+
<----------- datagrammes IP -------------->
+----------------+
+----------------------------+
Interface rseau
Interface rseau
+---------------+
+----------------------------+
<---------- trames rseau ------------->
+----------------------------------------------+
rseau physique
l'interface rseau (la carte rseau de l'ordinateur) assure les fonctions des couches 1 et 2 du modle OSI
la couche IP (Internet Protocol) assure les fonctions de la couche 3 (rseau)
la couche TCP (Transfer Control Protocol) ou UDP (User Datagram Protocol) assure les fonctions de la couche 4
(transport). Le protocole TCP s'assure que les paquets de donnes changs par les machines arrivent bien destination. Si
ce n'est pas les cas, il renvoie les paquets qui se sont gars. Le protocole UDP ne fait pas ce travail et c'est alors au
dveloppeur d'applications de le faire. C'est pourquoi sur l'internet qui n'est pas un rseau fiable 100%, c'est le protocole
TCP qui est le plus utilis. On parle alors de rseau TCP-IP.
la couche Application recouvre les fonctions des niveaux 5 7 du modle OSI.
Les applications Web se trouvent dans la couche Application et s'appuient donc sur les protocoles TCP-IP. Les couches Application
des machines clientes et serveur s'changent des messages qui sont confies aux couches 1 4 du modle pour tre achemines
destination. Pour se comprendre, les couches application des deux machines doivent "parler" un mme langage ou protocole. Celui
des applications Web s'appelle HTTP (HyperText Transfer Protocol). C'est un protocole de type texte, c.a.d. que les machines
changent des lignes de texte sur le rseau pour se comprendre. Ces changes sont normaliss, --d. que le client dispose d'un
certain nombre de messages pour indiquer exactement ce qu'il veut au serveur et ce dernier dispose galement d'un certain nombre
de messages pour donner au client sa rponse. Cet change de messages a la forme suivante :
http://tahe.developpez.com
22/362
le navigateur se connecte au serveur Web et demande la page qu'il souhaite. Les ressources demandes sont dsignes
de faon unique par des URL (Uniform Resource Locator). Le navigateur n'envoie que des enttes HTTP et aucun
document.
le serveur lui rpond. Il envoie tout d'abord des enttes HTTP indiquant quel type de rponse il envoie. Ce peut tre
une erreur si la page demande n'existe pas. Si la page existe, le serveur dira dans les enttes HTTP de sa rponse
qu'aprs ceux-ci il va envoyer un document HTML (HyperText Markup Language). Ce document est une suite de
lignes de texte au format HTML. Un texte HTML contient des balises (marqueurs) donnant au navigateur des
indications sur la faon d'afficher le texte.
le client sait d'aprs les enttes HTTP du serveur qu'il va recevoir un document HTML. Il va analyser celui-ci et
s'apercevoir peut-tre qu'il contient des rfrences d'images. Ces dernires ne sont pas dans le document HTML. Il
fait donc une nouvelle demande au mme serveur Web pour demander la premire image dont il a besoin. Cette
demande est identique celle faite en 1, si ce n'est que la resource demande est diffrente. Le serveur va traiter cette
demande en envoyant son client l'image demande. Cette fois-ci, dans sa rponse, les enttes HTTP prciseront que
le document envoy est une image et non un document HTML.
le client rcupre l'image envoye. Les tapes 3 et 4 vont tre rptes jusqu' ce que le client (un navigateur en
gnral) ait tous les documents lui permettant d'afficher l'intgralit de la page.
2.
3.
4.
2.4.3
Le protocole HTTP
Dcouvrons le protocole HTTP sur des exemples. Que s'changent un navigateur et un serveur Web ?
Le service Web ou service HTTP est un service TCP-IP qui travaille habituellement sur le port 80. Il pourrait travailler sur un autre
port. Dans ce cas, le navigateur client serait oblig de prciser ce port dans l'URL qu'il demande. Une URL a la forme gnrale
suivante :
protocole://machine[:port]/chemin/infos
avec
protocole
machine
port
chemin
infos
http pour le service Web. Un navigateur peut galement servir de client des services ftp, news, telnet, ..
nom de la machine o officie le service Web
port du service Web. Si c'est 80, on peut omettre le n du port. C'est le cas le plus frquent
chemin dsignant la ressource demande
informations complmentaires donnes au serveur pour prciser la demande du client
http://tahe.developpez.com
23/362
Pour dcouvrir les changes entre un client et un serveur Web, nous allons utiliser l'extension [Advanced Rest Client] du navigateur
Chrome que nous avons installe page 9. Nous serons dans la situation suivante :
Le serveur Web pourra tre quelconque. Nous cherchons ici dcouvrir les changes qui vont se produire entre navigateur et le
serveur Web. Prcdemment, nous avons cr la page HTML statique suivante :
1. <!DOCTYPE html>
2. <html xmlns="http://www.w3.org/1999/xhtml">
3. <head>
4.
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5.
<title>essai 1 : une page statique</title>
6. </head>
7. <body>
8.
<h1>Une page statique...</h1>
9. </body>
10. </html>
On voit que l'URL demande est : http://localhost:56376/HtmlPage1.html . La machine du service Web est donc localhost (=machine
locale) et le port 56376. Utilisons l'application [Advanced Rest Client] pour demander la mme URL :
http://tahe.developpez.com
24/362
3
2
4
5
en [1], on lance l'application (dans l'onglet [Applications] d'un nouvel onglet Chrome) ;
en [2], on slectionne l'option [Request] ;
en [3], on prcise le serveur interrog : http://localhost:56376;
en [4], on prcise l'URL demande : /HtmlPage1.html ;
en [5], on ajoute d'ventuels paramtres l'URL. Aucun ici ;
en [6], on prcise la commande HTTP utilise pour la requte, ici GET.
La requte ainsi prpare [7] est envoye au serveur par [8]. La rponse obtenue est alors la suivante :
http://tahe.developpez.com
25/362
Nous avons dit plus haut que les changes client-serveur avaient la forme suivante :
en [1], on voit les enttes HTTP envoys par le navigateur dans sa requte. Il n'avait pas de document envoyer ;
en [2], on voit les enttes HTTP envoys par le serveur en rponse. En [3], on voit le document qu'il a envoy.
En [3], on reconnat la page HTML statique que nous avons place sur le serveur web.
Examinons la requte HTTP du navigateur :
1. GET /HtmlPage1.html HTTP/1.1
2. User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/29.0.1547.66 Safari/537.36
3. Content-Type: text/plain; charset=utf-8
4. Accept: */*
5. Accept-Encoding: gzip,deflate,sdch
6. Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
http://tahe.developpez.com
26/362
2.4.4
Conclusion
Nous avons dcouvert la structure de la demande d'un client Web et celle de la rponse qui lui est faite par le serveur Web sur
quelques exemples. Le dialogue se fait l'aide du protocole HTTP, un ensemble de commandes au format texte changes par les
deux partenaires. La requte du client et la rponse du serveur ont la mme structure suivante :
Les deux commandes usuelles pour demander une ressource sont GET et POST. La commande GET n'est pas accompagne d'un
document. La commande POST elle, est accompagne d'un document qui est une chane de caractres rassemblant l'ensemble des
valeurs saisies dans le formulaire. La commande HEAD permet de demander seulement les enttes HTTP et n'est pas
accompagne de document.
A la demande d'un client, le serveur envoie une rponse qui a la mme structure. La ressource demande est transmise dans la
partie [Document] sauf si la commande du client tait HEAD, auquel cas seuls les enttes HTTP sont envoys.
2.5
Un navigateur Web peut afficher divers documents, le plus courant tant le document HTML (HyperText Markup Language). Celuici est un texte format avec des balises de la forme <balise>texte</balise>. Ainsi le texte <B>important</B> affichera le texte
important en gras. Il existe des balises seules telles que la balise <hr/> qui affiche une ligne horizontale. Nous ne passerons pas en
revue les balises que l'on peut trouver dans un texte HTML. Il existe de nombreux logiciels WYSIWYG permettant de construire
une page Web sans crire une ligne de code HTML. Ces outils gnrent automatiquement le code HTML d'une mise en page faite
l'aide de la souris et de contrles prdfinis. On peut ainsi insrer (avec la souris) dans la page un tableau puis consulter le code
HTML gnr par le logiciel pour dcouvrir les balises utiliser pour dfinir un tableau dans une page Web. Ce n'est pas plus
compliqu que cela. Par ailleurs, la connaissance du langage HTML est indispensable puisque les applications Web dynamiques
http://tahe.developpez.com
27/362
doivent gnrer elles-mmes le code HTML envoyer aux clients Web. Ce code est gnr par programme et il faut bien sr savoir
ce qu'il faut gnrer pour que le client ait la page Web qu'il dsire.
Pour rsumer, il n'est nul besoin de connatre la totalit du langage HTML pour dmarrer la programmation Web. Cependant cette
connaissance est ncessaire et peut tre acquise au travers de l'utilisation de logiciels WYSIWYG de construction de pages Web tels
que DreamWeaver et des dizaines d'autres. Une autre faon de dcouvrir les subtilits du langage HTML est de parcourir le Web et
d'afficher le code source des pages qui prsentent des caractristiques intressantes et encore inconnues pour vous.
2.5.1
Un exemple
Considrons l'exemple suivant qui prsente quelques lments qu'on peut trouver dans un document Web tels que :
un tableau ;
une image ;
un lien.
L'ensemble du document est encadr par les balises <html>...</html>. Il est form de deux parties :
1.
<head>...</head> : c'est la partie non affichable du document. Elle donne des renseignements au navigateur qui va
afficher le document. On y trouve souvent la balise <title>...</title> qui fixe le texte qui sera affich dans la barre de
titre du navigateur. On peut y trouver d'autres balises notamment des balises dfinissant les mots cls du document, mot
http://tahe.developpez.com
28/362
cls utiliss ensuite par les moteurs de recherche. On peut trouver galement dans cette partie des scripts, crits le plus
souvent en javascript ou vbscript et qui seront excuts par le navigateur.
2.
<body attributs>...</body> : c'est la partie qui sera affiche par le navigateur. Les balises HTML contenues dans cette
partie indiquent au navigateur la forme visuelle "souhaite" pour le document. Chaque navigateur va interprter ces balises
sa faon. Deux navigateurs peuvent alors visualiser diffremment un mme document Web. C'est gnralement l'un des
casse-ttes des concepteurs Web.
titre du
document
<title>balises</title> (ligne 5)
le texte balises apparatra dans la barre de titre du navigateur qui affichera le document
http://tahe.developpez.com
29/362
barre
horizontale
tableau
image
<img border="0" src="/images/cerisier.jpg"/> (ligne 38) : dfinit une image sans bordure
(border=0") dont le fichier source est /images/cerisier.jpg sur le serveur Web (src="/images/cerisier.jpg"). Ce
lien se trouve sur un document Web obtenu avec l'URL http://localhost:port/html/balises.htm. Aussi, le
navigateur demandera-t-il l'URL http://localhost:port/images/cerisier.jpg pour avoir l'image rfrence ici.
lien
<a href="http://istia.univ-angers.fr">ici</a> (ligne 42) : fait que le texte ici sert de lien vers l'URL
http://istia.univ-angers.fr.
fond de
page
<body style="height:400px;width:400px;background-image:url(images/standard.jpg)">
(ligne 8) : indique que l'image qui doit servir de fond de page se trouve l'URL /images/standard.jpg du serveur
Web.
Dans
le
contexte
de
notre
exemple,
le
navigateur
demandera
l'URL
http://localhost:port/images/standard.jpg pour obtenir cette image de fond. Par ailleurs, le corps du document
sera affich dans un rectangle de hauteur 400 pixels et de largeur 400 pixels.
On voit dans ce simple exemple que pour construire l'intralit du document, le navigateur doit faire trois requtes au serveur :
1.
2.
3.
2.5.2
Un formulaire HTML
http://tahe.developpez.com
30/362
http://tahe.developpez.com
31/362
28.
<input type="checkbox" name="C2" value="deux" checked="checked" />2
29.
<input type="checkbox" name="C3" value="trois" />3
30.
</td>
31.
</tr>
32.
<tr>
33.
<td>Champ de saisie</td>
34.
<td>
35.
<input type="text" name="txtSaisie" size="20" value="qqs mots" />
36.
</td>
37.
</tr>
38.
<tr>
39.
<td>Mot de passe</td>
40.
<td>
41.
<input type="password" name="txtMdp" size="20" value="unMotDePasse" />
42.
</td>
43.
</tr>
44.
<tr>
45.
<td>Bote de saisie</td>
46.
<td>
47.
<textarea rows="2" name="areaSaisie" cols="20">
48. ligne1
49. ligne2
50. ligne3
51. </textarea>
52.
</td>
53.
</tr>
54.
<tr>
55.
<td>combo</td>
56.
<td>
57.
<select size="1" name="cmbValeurs">
58.
<option>choix1</option>
59.
<option selected="selected">choix2</option>
60.
<option>choix3</option>
61.
</select>
62.
</td>
63.
</tr>
64.
<tr>
65.
<td>liste choix simple</td>
66.
<td>
67.
<select size="3" name="lst1">
68.
<option selected="selected">liste1</option>
69.
<option>liste2</option>
70.
<option>liste3</option>
71.
<option>liste4</option>
72.
<option>liste5</option>
73.
</select>
74.
</td>
75.
</tr>
76.
<tr>
77.
<td>liste choix multiple</td>
78.
<td>
79.
<select size="3" name="lst2" multiple="multiple">
80.
<option>liste1</option>
81.
<option>liste2</option>
82.
<option selected="selected">liste3</option>
83.
<option>liste4</option>
84.
<option>liste5</option>
85.
</select>
86.
</td>
87.
</tr>
88.
<tr>
89.
<td>bouton</td>
90.
<td>
http://tahe.developpez.com
32/362
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
balise HTML
formulaire
champ de
saisie
champ de
saisie cache
champ de
saisie
multilignes
boutons radio
liste
slection
unique
liste
slection
multiple
http://tahe.developpez.com
33/362
<option>liste4</option>
<option>liste5</option>
</select>
bouton de type <input type="submit" value="Envoyer" name="cmdRenvoyer"/>
submit
bouton de type <input type="reset" value="Rtablir" name="cmdRtablir"/>
reset
bouton de type <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()"/>
button
2.5.2.1
Le formulaire
formulaire
2.5.2.2
champ de
saisie
attributs
La balise input existe pour divers contrles. C'est l'attribut type qui permet de diffrencier ces diffrents contrles
entre eux.
type="text" : prcise que c'est un champ de saisie
type="password" : les caractres prsents dans le champ de saisie sont remplacs par des caractres *. C'est la
seule diffrence avec le champ de saisie normal. Ce type de contrle convient pour la saisie des mots de passe.
size="20" : nombre de caractres visibles dans le champ - n'empche pas la saisie de davantage de caractres
http://tahe.developpez.com
34/362
2.5.2.3
champ de
saisie
multilignes
2.5.2.4
2.5.2.5
http://tahe.developpez.com
35/362
2.5.2.6
Combo
<option [selected="selected"]>...</option>
...
</select>
attributs
affiche dans une liste les textes compris entre les balises <option>...</option>
name="cmbValeurs" : nom du contrle.
size="1" : nombre d'lments de liste visibles. size="1" fait de la liste l'quivalent d'un combobox.
selected="selected" : si ce mot cl est prsent pour un lment de liste, ce dernier apparat slectionn dans la
liste. Dans notre exemple ci-dessus, l'lment de liste choix2 apparat comme l'lment slectionn du combo
lorsque celui-ci est affich pour la premire fois.
2.5.2.7
liste
slection
http://tahe.developpez.com
36/362
unique
<option selected="selected">liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
<option [selected="selected"]>...</option>
...
</select>
attributs
2.5.2.8
affiche dans une liste les textes compris entre les balises <option>...</option>
les mmes que pour la liste droulante n'affichant qu'un lment. Ce contrle ne diffre de la liste droulante
prcdente que par son attribut size>1.
liste
slection
unique
<option [selected="selected"]>...</option>
...
</select>
affiche dans une liste les textes compris entre les balises <option>...</option>
http://tahe.developpez.com
37/362
attributs
2.5.2.9
multiple : permet la slection de plusieurs lments dans la liste. Dans l'exemple ci-dessus, les lments liste1 et
liste3 sont tous deux slectionns.
2.5.2.10
http://tahe.developpez.com
38/362
2.5.2.11
type="submit" : dfinit le bouton comme un bouton d'envoi des donnes du formulaire au serveur Web.
Lorsque le client va cliquer sur ce bouton, le navigateur va envoyer les donnes du formulaire l'URL dfinie dans
l'attribut action de la balise <form> selon la mthode dfinie par l'attribut method de cette mme balise.
value="Envoyer" : le texte affich sur le bouton
2.5.2.12
type="reset" : dfinit le bouton comme un bouton de rinitialisation du formulaire. Lorsque le client va cliquer
sur ce bouton, le navigateur va remettre le formulaire dans l'tat o il l'a reu.
value="Rtablir" : le texte affich sur le bouton
Champ cach
champ cach
type="hidden" : prcise que c'est un champ cach. Un champ cach fait partie du formulaire mais n'est pas
prsent l'utilisateur. Cependant, si celui-ci demandait son navigateur l'affichage du code source, il verrait la
prsence de la balise <input type="hidden" value="..."> et donc la valeur du champ cach.
value="uneValeur" : valeur du champ cach.
Quel est l'intrt du champ cach ? Cela peut permettre au serveur Web de garder des informations au fil des
requtes d'un client. Considrons une application d'achats sur le Web. Le client achte un premier article art1 en
quantit q1 sur une premire page d'un catalogue puis passe une nouvelle page du catalogue. Pour se souvenir
que le client a achet q1 articles art1, le serveur peut mettre ces deux informations dans un champ cach du
formulaire Web de la nouvelle page. Sur cette nouvelle page, le client achte q2 articles art2. Lorsque les donnes
de ce second formulaire vont tre envoyes au serveur (submit), celui-ci va non seulement recevoir l'information
(q2,art2) mais aussi (q1,art1) qui fait partie galement partie du formulaire en tant que champ cach. Le serveur
Web va alors mettre dans un nouveau champ cach les informations (q1,art1) et (q2,art2) et envoyer une nouvelle
page de catalogue. Et ainsi de suite.
2.5.3
Envoi un serveur Web par un client Web des valeurs d'un formulaire
Nous avons dit dans l'tude prcdente que le client Web disposait de deux mthodes pour envoyer un serveur Web les valeurs
d'un formulaire qu'il a affich : les mthodes GET et POST. Voyons sur un exemple la diffrence entre les deux mthodes.
2.5.3.1
Mthode GET
Faisons un premier test, o dans le code HTML du document, la balise <form> est dfinie de la faon suivante :
http://tahe.developpez.com
39/362
Lorsque l'utilisateur va cliquer sur le bouton [1], les valeurs saisies dans le formulaire vont tre envoyes la page ASP.NET [2].
Celle-ci ne fait rien de ces paramtres et renvoie une page vide. On veut seulement savoir comment le navigateur transmet les
valeurs saisies au serveur web. Pour cela, nous allons utiliser un outil de dbogage disponible dans Chrome. On l'active en tapant
CTRL-I (majuscule) [3] :
Comme nous nous intressons aux changes rseau entre le navigateur et le serveur web, nous activons ci-dessus l'onglet [Network]
puis nous cliquons sur le bouton [Envoyer] du formulaire. Celui-ci est un bouton de type [submit] l'intrieur d'une balise [form].
Le navigateur ragit au clic en demandant l'URL [FormulaireGet.aspx] indique dans l'attribut [action] de la balise [form], avec la
mthode GET indique dans l'attribut [method]. Nous obtenons alors les informations suivantes :
http://tahe.developpez.com
40/362
La copie d'cran ci-dessus nous montre l'URL demande par le navigateur l'issue du clic sur le bouton [envoyer]. Il demande bien
l'URL prvue [FormulaireGet.aspx] mais derrire il rajoute des informations qui sont les valeurs saisies dans le formulaire. Pour
avoir plus d'informations, nous cliquons sur le lien ci-dessus :
1
4
2
Ci-dessus [1, 2], nous voyons les enttes HTTP envoys par le navigateur. Ils ont t ici mis en forme. Pour voir le texte brut de ces
enttes, nous suivons le lien [view source] [3, 4]. Le texte complet est le suivant :
1. GET /FormulaireGet.aspx?
R1=Oui&C1=un&C2=deux&txtSaisie=programmation+web&txtMdp=ceciestsecret&areaSaisie=les+bases+
de%0D%0Ala+programmation+web%0D
%0A&cmbValeurs=choix3&lst1=liste3&lst2=liste1&lst2=liste3&cmdRenvoyer=Envoyer&secret=uneVal
eur HTTP/1.1
2. Host: localhost:56376
3. Connection: keep-alive
4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5. User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/29.0.1547.66 Safari/537.36
6. Referer: http://localhost:56376/HtmlFormulaire.html
7. Accept-Encoding: gzip,deflate,sdch
8. Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Nous retrouvons des lments dj rencontrs prcdemment. D'autres apparaissent pour la premire fois :
Connection: keepalive
Referer
le client demande au serveur de ne pas fermer la connexion aprs sa rponse. Cela lui permettra d'utiliser
la mme connexion pour une demande ultrieure. La connexion ne reste pas ouverte indfiniment. Le
serveur la fermera aprs un trop long dlai d'inutilisation.
l'URL qui tait affiche dans le navigateur lorsque la nouvelle demande a t faite.
La nouveaut est ligne 1 dans les informations qui suivent l'URL. On constate que les choix faits dans le formulaire se retrouvent
dans l'URL. Les valeurs saisies par l'utilisateur dans le formulaire ont t passes dans la commande GET URL?
param1=valeur1¶m2=valeur2&... HTTP/1.1 o les parami sont les noms (attribut name) des contrles du formulaire Web et valeuri
les valeurs qui leur sont associes. Nous prsentons ci-dessous un tableau trois colonnes :
http://tahe.developpez.com
41/362
visuel
valeur(s) renvoye(s)
R1=Oui
- la valeur de l'attribut value du bouton
radio coch par l'utilisateur.
C1=un
C2=deux
- valeurs des attributs value des cases
coches par l'utilisateur
txtSaisie=programmation+Web
txtMdp=ceciestsecret
areaSaisie=les+bases+de+la%0D%0A
ligne1
programmation+Web
</textarea>
cmbValeurs=choix3
<option>choix1</option>
ligne2
ligne3
<option
selected="selected">choix2</option>
<option>choix3</option>
</select>
<select size="3" name="lst1">
<option
selected="selected">liste1</option>
<option>liste2</option>
lst1=liste3
- valeur choisie par l'utilisateur dans la liste
slection unique
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
<select size="3" name="lst2"
multiple="multiple">
<option
selected="selected">liste1</option>
<option>liste2</option>
lst2=liste1
lst2=liste3
- valeurs choisies par l'utilisateur dans la
liste slection multiple
<option selected>liste3</option>
http://tahe.developpez.com
42/362
<option>liste4</option>
<option>liste5</option>
</select>
<input type="submit" value="Envoyer"
name="cmdRenvoyer"/>
cmdRenvoyer=Envoyer
secret=uneValeur
2.5.3.2
Mthode POST
Nous changeons le document HTML pour que le navigateur utilise maintenant la mthode POST pour envoyer les valeurs du
formulaire au serveur Web :
<form method="post" action="FormulairePost.aspx">
Nous remplissons le formulaire tel que pour la mthode GET et nous transmettons les paramtres au serveur avec le bouton
[Envoyer]. Comme il a t fait au paragraphe prcdent page 39, nous avons accs dans Chrome aux enttes HTTP de la requte
envoye par le navigateur :
1.
2.
3.
4.
5.
6.
7.
8.
la requte GET a laiss place une requte POST. Les paramtres ne sont plus prsents dans cette
premire ligne de la requte. On peut constater qu'ils sont maintenant placs (ligne 14) derrire la
requte HTTP aprs une ligne vide. Leur encodage est identique celui qu'ils avaient dans la requte
GET.
Content-Length
nombre de caractres "posts", c.a.d. le nombre de caractres que devra lire le serveur Web aprs avoir
reu les enttes HTTP pour rcuprer le document que lui envoie le client. Le document en question
est ici la liste des valeurs du formulaire.
Content-type
prcise le type du document que le client enverra aprs les enttes HTTP. Le type [application/x-wwwform-urlencoded] indique que c'est un document contenant des valeurs de formulaire.
Il y a deux mthodes pour transmettre des donnes un serveur Web : GET et POST. Y-a-t-il une mthode meilleure que l'autre ?
Nous avons vu que si les valeurs d'un formulaire taient envoyes par le navigateur avec la mthode GET, le navigateur affichait
dans son champ Adresse l'URL demande sous la forme URL?param1=val1¶m2=val2&.... On peut voir cela comme un avantage
ou un inconvnient :
http://tahe.developpez.com
43/362
un avantage si on veut permettre l'utilisateur de placer cette URL paramtre dans ses liens favoris ;
un inconvnient si on ne souhaite pas que l'utilisateur ait accs certaines informations du formulaire tels, par exemple, les
champs cachs.
Par la suite, nous utiliserons quasi exclusivement la mthode POST dans nos formulaires.
2.6
Conclusion
Nous avons pu voir sur un exemple comment un client pouvait envoyer des informations au serveur Web. Nous n'avons pas
prsent comment le serveur pouvait
les traiter ;
http://tahe.developpez.com
44/362
Application web
couche [web]
1
Vue1
Vue2
Navigateur
4b
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Dans ce chapitre, nous regardons le processus qui amne la requte [1] au contrleur et l'action [2a] qui vont la traiter, un
mcanisme qu'on appelle le routage. Nous prsentons par ailleurs les diffrentes rponses [3] que peut faire une action au
navigateur. Ce peut tre autre chose qu'une vue V [4b].
3.1
Construisons un premier projet ASP.NET MVC avec Visual Studio Express 2012. Nous allons l'ajouter [1] la solution utilise dans
le chapitre prcdent :
http://tahe.developpez.com
45/362
Le projet rsultant est prsent en [5]. On en fera [6] le projet de dmarrage de la solution :
http://tahe.developpez.com
46/362
1
2
Layout = "~/Views/Shared/_Layout.cshtml";
ligne 1 : le caractre @ annonce une squence C# dans la vue. En effet, on peut inclure du code C# dans une vue ;
ligne 2 : dfinit une variable Layout qui dfinit la vue parente de toutes les vues. Elle correspond la page matre du
framework ASP.NET classique.
Lorsqu'une vue du dossier [Views] sera rendue, son corps sera rendu par la ligne 11 ci-dessus. Cela signifie que la vue n'a pas
inclure les balises <html>, <head>, <body>. Celles-ci sont fournies par le fichier ci-dessus. Celui-ci est pour l'instant assez
hermtique. Simplifions-le :
1.
2.
3.
4.
5.
6.
7.
8.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Tutoriel ASP.NET MVC</title>
</head>
<body>
http://tahe.developpez.com
47/362
9.
<h2>Tutoriel ASP.NET MVC</h2>
10. @RenderBody()
11. </body>
12. </html>
[web.config] est le fichier de configuration de l'application Web. Il est complexe. On sera amen le modifier lorsqu'on
utilisera le framework [Spring.net] dans une architecture multicouche.
[Global.asax] contient le code excut au dmarrage de l'application. En gnral, ce code exploite les diffrents fichiers de
configuration de l'application dont [web.config].
3.2
ligne 6, le namespace de la classe. Il vient directement du nom du projet et est prsent dans les proprits du projet :
http://tahe.developpez.com
48/362
On fixe en [1], l'espace de noms par dfaut. Il est alors utilis par dfaut pour toutes les classes qui seront cres dans le projet.
Revenons au code de [Global.asax] :
1. using System.Web.Http;
2. using System.Web.Mvc;
3. using System.Web.Optimization;
4. using System.Web.Routing;
5.
6. namespace Exemple_01
7. {
8.
9.
public class MvcApplication : System.Web.HttpApplication
10. {
11.
protected void Application_Start()
12.
{
13.
...
14.
}
15. }
16. }
ligne 9 : la classe [MvcApplication] drive de la classe [HttpApplication]. Le nom [MvcApplication] peut tre chang ;
ligne 11 : la mthode [Application_Start] est la mthode excute au dmarrage de l'application Web. Elle n'est excute
qu'une fois. C'est l qu'on initialise l'application.
http://tahe.developpez.com
49/362
5.
6.
7.
8.
9. }
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Pour l'instant, on n'a pas besoin de comprendre l'intgralit de ce code. Les lignes 5-8 dfinissent les routes acceptes par
l'application Web. Revenons l'architecture d'une application ASP.NET MVC :
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Nous avons expliqu que le [Front Controller] devait router une URL vers l'action charge de la traiter. Une route sert faire le lien
entre un modle d'URL et une action. Ces routes sont dfinies dans le dossier [App_Start] du projet par les classes [WebApiConfig,
FilterConfig, RouteConfig, BundleConfig] :
Les lignes 12-16 dfinissent la forme des URL acceptes par l'application. On appelle cela une route. Il peut y avoir plusieurs routes
possibles (= plusieurs modles d'URL possibles). Elles se distinguent entre-elles par leur nom (ligne 13). La forme des URL de la
route est dfinie ligne 14. Ici, l'URL aura trois composantes :
http://tahe.developpez.com
50/362
{controller} : le nom d'une classe drive de [Controller]. Elle sera cherche dans le dossier [Controllers] du projet. Par
convention, si l'URL est /X/Y/Z, le contrleur charg de traiter cette URL sera la classe XController. Le suffixe
Controller est ajout au nom du contrleur prsent dans l'URL ;
{action} : le nom d'une mthode dans le contrleur dsign ci-dessus. C'est elle qui va recevoir les paramtres qui
accompagnent l'URL et qui va les traiter. Cette mthode peut rendre divers rsultats :
ContentResult : renvoie un flux HTML au cient sans passer par une vue ;
FilePathResult : ...
{id} : un paramtre qui sera transmis l'action. Pour cela l'action devra avoir un paramtre nomm id.
La ligne 15 dfinit des valeurs par dfauts lorsque l'URL n'a pas la forme attendue /{controller}/{action}/{id}. Elle indique
galement que le paramtre {id} dans l'URL est facultatif. Voici une liste d'URL incompltes et l'URL complte avec les valeurs
par dfaut :
URL d'origine
URL complte
/Home/Index
/Do
/Do/Index
/Do/Something
/Do/Something
/Do/Something/4
/Do/Something/4
3.3
http://tahe.developpez.com
51/362
en [3], il a t cr.
2
1
en [1], on clique droit dans le code de l'action et on prend l'option [Ajouter une vue] ;
en [2], l'assistant propose une vue portant le nom de l'action. C'est ce qu'on veut ici ;
en [3], par dfaut l'utilisation de la page matre [_Layout.cshtml] est propose ;
en [4], une fois valid, l'assistant cre la vue dans un sous-dossier du dossier [Views] portant le nom du contrleur (First).
http://tahe.developpez.com
52/362
Rsumons :
nous avons une action appele [Index] qui demande l'affichage d'une vue appele [Index] ;
/First/Index ;
/First, car [Index] est galement l'action par dfaut dans les routes.
Excutons l'application (CTRL-F5). Nous obtenons la page suivante :
1
En [1], l'URL demande a t http://localhost:49302. Il n'y a pas de chemin. On sait que notre routeur attend des URL de la
forme /{controller}/{action}/{id}. Ces lments tant absents, les valeurs par dfaut sont utilises. L'URL devient
http://localhost:49302/Home/Index. Le contrleur [Home] n'existe pas. Donc l'URL est rejete.
Essayons maintenant l'URL http://localhost:49302/First/Index en la tapant directement dans le navigateur :
2
1
La page ci-dessus a t gnre par l'action [Index] du contrleur [First]. La page gnre par cette action est la vue [Index] dont le
code tait le suivant :
<strong>Vue [Index]...</strong>
http://tahe.developpez.com
53/362
Elle gnre la partie [1]. La partie [2] elle, provient de la page matre [_Layout] que nous avons dfinie un peu plus tt :
1. <!DOCTYPE html>
2. <html>
3. <head>
4.
<meta charset="utf-8" />
5.
<meta name="viewport" content="width=device-width" />
6.
<title>Tutoriel ASP.NET MVC</title>
7. </head>
8. <body>
9.
<h2>Tutoriel ASP.NET MVC</h2>
10. @RenderBody()
11. </body>
12. </html>
La ligne 9 a gnr la partie [2] de la page. La vue [Index] n'intervient elle, qu'en ligne 10.
Si on affiche le code source de la page reue, on retrouve bien la page [Index] incluse (ligne 10 ci-dessous) dans la page [Layout] :
1. <!DOCTYPE html>
2. <html>
3. <head>
4.
<meta charset="utf-8" />
5.
<meta name="viewport" content="width=device-width" />
6.
<title>Tutoriel ASP.NET MVC</title>
7. </head>
8. <body>
9.
<h2>Tutoriel ASP.NET MVC</h2>
10. <strong>Vue [Index]...</strong>
11. </body>
12. </html>
L'URL [/First] tait incomplte. Elle a t complte avec les valeurs par dfaut de la route et est devenue [/First/Index]. On
obtient donc le mme rsultat que prcdemment.
3.4
http://tahe.developpez.com
54/362
11.
12.
13.
14.
15.
16.
17.
18. }
19. }
return View();
}
// Action01
public ContentResult Action01()
{
return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
}
La nouvelle action est dfinie lignes 14-18. Elle se contente de rendre une chane de caractres l'aide de la mthode [Content]
(ligne 16) de la classe [Controller] (ligne 6). Les paramtres de la mthode sont :
1. la chane de caractres de la rponse ;
2. un indicateur de la nature du texte envoy : " text/plain ", "text/html ", " text/xml ", ... Cet indicateur est appel type
MIME (http://fr.wikipedia.org/wiki/Type_MIME ) ;
3. le troisime paramtre permet de prciser le type d'encodage utilis pour le texte.
Plutt que d'utiliser le type abstrait [ActionResult], nos mthodes prcisent le type rel rendu (lignes 9 et 14).
Demandons l'URL [/First/Action01]. On obtient la page suivante :
Le navigateur n'a reu que le texte envoy par l'action et rien d'autre. Ce mode est intressant lorsqu'on veut demander au serveur
Web des donnes pures sans l'habillage HTML autour. On notera ci-dessus, que le navigateur n'a pas interprt la balise HTML
<h1>. Pour comprendre pourquoi, regardons dans Chrome, les changes HTTP :
Le navigateur a envoy les enttes HTTP suivants :
1.
2.
3.
4.
5.
6.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMQ==?=
10. X-Powered-By: ASP.NET
http://tahe.developpez.com
55/362
3.5
ligne 3 : fixe la nature du document. On retrouve les attributs fixs dans la mthode [Action01]. C'est parce qu'on lui a dit
que le document tait de type "text/plain" et non "text/html" que le navigateur n'a pas interprt la balise <h1> qui tait
dans le document reu.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMg==?=
10. X-Powered-By: ASP.NET
11. Date: Mon, 23 Sep 2013 15:29:34 GMT
12. Content-Length: 176
http://tahe.developpez.com
56/362
ligne 3 : fixe la nature du document. On retrouve les attributs fixs dans la mthode [Action02] ;
ligne 12 : la taille en octets du document envoy par le serveur.
3.6
// Action03
public JsonResult Action03()
{
dynamic personne = new ExpandoObject();
personne.nom = "someone";
personne.age = 20;
return Json(personne,JsonRequestBehavior.AllowGet);
ligne 4 : une variable de type dynamic. A l'excution, on peut librement ajouter des proprits une telle variable. La
proprit est cre en mme temps qu'elle est initialise ;
lignes 5-6 : on initialise deux proprits nom et age ;
ligne 7 : on retourne la reprsentation JSON (Javascript Object Notation) de l'objet. Le JSON permet de srialiser un objet
en chane de caractres et inversement de dsrialiser une chane en objet. C'est une alternative la srialisation /
dsrialisation XML ;
ligne 2 : l'action retourne un type [JsonResult]. Ce type ne peut tre retourn que pour une requte POST. Si on veut la
retourner pour une mthode GET, il faut donner au second paramtre du constructeur de la class Json (ligne 7), la valeur
JsonRequestBehavior.AllowGet.
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMw==?=
8. X-Powered-By: ASP.NET
9. Date: Mon, 23 Sep 2013 15:48:53 GMT
10. Content-Length: 58
http://tahe.developpez.com
57/362
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]
L'lment dynamique [personne] est vu par JSON comme un tableau de dictionnaires o chaque dictionnaire :
a deux cls "Key" et "Value". "Key" a pour valeur associe le nom du champ et "Value" a pour valeur la valeur du champ.
3.7
// Action04
public string Action04()
{
return "<h3>Contrleur=First, Action=Action04</h3>";
Lorsque nous demandons l'URL [/First/Action04] avec Chrome, on obtient la rponse suivante :
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNA==?=
10. X-Powered-By: ASP.NET
11. Date: Tue, 24 Sep 2013 07:49:00 GMT
12. Content-Length: 156
et le document suivant :
<h3>Contrleur=First, Action=Action04</h3>
On voit ligne 3, que le serveur a indiqu envoyer du texte au format HTML. C'est pourquoi le navigateur a interprt la balise
<h3>. Lorsqu'on veut envoyer du texte pur, il est donc prfrable de rendre un [ContentResult] plutt qu'un [string]. Le
[ContentResult] nous permet en effet de prciser un type MIME "text/plain" pour indiquer qu'on envoie du texte non format,
donc non susceptible d'interprtation de la part du navigateur.
3.8
http://tahe.developpez.com
// Action05
public EmptyResult Action05()
58/362
3.
4.
5. }
{
return new EmptyResult();
L'action se contente de rendre un type [EmptyResult]. Dans ce cas, le serveur envoie une rponse vide au client comme le montre
sa rponse HTTP :
1.
2.
3.
4.
5.
6.
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNQ==?=
7. X-Powered-By: ASP.NET
8. Date: Tue, 24 Sep 2013 08:11:12 GMT
9. Content-Length: 0
3.9
ligne 9 : le serveur indique son client qu'il lui envoie un document vide.
// Action06
public RedirectResult Action06()
{
return new RedirectResult("/First/Action05");
L'action rend un type [RedirectResult]. Ce type permet d'envoyer au client un ordre de redirection vers l'URL paramtre du
constructeur (ligne 4). Le client va alors mettre une nouvelle requte GET vers [/First/Action05]. Le client fait donc deux
requtes au total.
Le navigateur affiche le rsultat de la seconde requte :
On voit ci-dessus, les deux requtes du navigateur. Examinons la premire requte [Action06]. La rponse HTTP du serveur est la
suivante :
http://tahe.developpez.com
59/362
1.
2.
3.
4.
5.
6.
7.
8.
ligne 1 : le serveur a rpondu avec un code 302 Found. Pour l'instant, c'tait 200 OK qui veut dire que le document
demand a t trouv. Le code 302 indique qu'une redirection est demande. L'adresse de redirection est donne par la
ligne 4. On retrouve l l'adresse de redirection que nous avions donne dans le code de l'action ;
ligne 11 : le serveur indique qu'avec sa rponse HTTP, il envoie un document de type text/html (ligne 3) de 132 octets
(ligne 11). Lorsqu'on examine dans Chrome la rponse la requte [Action06], elle est vide comme on pouvait s'y attendre.
Il y a probablement une explication mais je ne la connais pas.
A cause de la redirection, le navigateur fait une nouvelle requte GET vers l'URL prcise ligne 4 ci-dessus comme on peut le voir
dans Chrome ligne 1 ci-dessous :
1.
2.
3.
4.
5.
3.10
// Action07
public RedirectResult Action07()
{
return new RedirectResult("/First/Action05",true);
Ligne 4, on a ajout un second paramtre au constructeur du type [RedirectResult]. C'est un boolen qui par dfaut est false.
Lorsqu'on le passe true, on modifie la rponse HTTP envoye au client. Elle devient :
HTTP/1.1 301 Moved Permanently
Ainsi le code de rponse envoy au client est maintenant 301 Moved Permanently. La redirection se passe comme prcdemment mais
on indique que cette redirection est permanente. Cela permet aux moteurs de recherche de remplacer dans leurs rsultats l'ancienne
URL par la nouvelle.
3.11
// Action08
public RedirectToRouteResult Action08()
{
return new RedirectToRouteResult("Default",new RouteValueDictionary(new
{controller="First",action="Action05"}));
http://tahe.developpez.com
60/362
5. }
ligne 2 : l'action rend un type [RedirectToRouteResult]. Ce type permet de rediriger un client vers une URL spcifie non
pas par une chane de caractres comme prcdemment mais par une route.
ligne 4, on demande au client de se rediriger vers la route nomme [Default] avec la variable controller=First et la variable
action=Action05. Le systme de routage va alors gnrer l'URL de redirection /First/Action05. C'est ce que montre la
rponse HTTP du serveur :
1.
2.
3.
4.
5.
6.
7.
8.
3.12
ligne 1 : la redirection ;
ligne 4 : l'adresse de redirection gnre par le systme de routage des URL.
// Action09
public void Action09()
{
string nom = Request.QueryString["nom"] ?? "inconnu";
Response.AddHeader("Content-Type", "text/plain");
Response.Write(string.Format("<h3>Action09</h3>nom={0}", nom));
ligne 2 : l'action ne rend aucun rsultat. Elle crit elle-mme dans le flux de la rponse envoye au client ;
ligne 4 : on rcupre un ventuel paramtre nomm [nom] dans la requte. Celle-ci est accessible via la proprit
[Request] du contrleur [Controller] dont hrite le contrleur [First]. Le paramtre [nom] pass sous la forme
[/First/Action09?nom=quelquechose], est disponible dans Request.QueryString["nom"]. La syntaxe de la ligne 4 est
quivalente :
string nom=Request.QueryString["nom"];
if(nom==null){
nom="inconnu";
}
ligne 5 : la rponse envoye au client est accessible via la proprit [Response] du contrleur [Controller] dont hrite le
contrleur [First] ;
ligne 5 : on fixe l'entte HTTP [Content-Type] qui indique la nature du document que le serveur s'apprte envoyer au
client. Ici "text/plain" indique que le document est du texte pur qui ne doit pas tre interprt par le navigateur ;
http://tahe.developpez.com
61/362
ligne 6 : on crit dans le flux de la rponse, une chane de caractres. On y a inclus des balises HTML qui ne doivent pas
tre interprtes par le navigateur puisque celui-ci aura reu auparavant l'entte HTTP [Content-Type : text/plain"]. C'est
ce qu'on veut vrifier.
Compilons le projet et demandons l'URL [/First/Action09?nom=someone ][1] puis l'URL [/First/Action09 ] [2] :
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
ligne 3 : on retrouve l'entte HTTP que nous avions nous-mme fix dans le code de l'action.
3.13
Un second contrleur
Crons dans le projet un second contrleur. On suivra la mthode dcrite au paragraphe 3.1, page 45. On l'appellera [Second].
using System.Text;
using System.Web.Mvc;
namespace Exemple_01.Controllers
{
public class SecondController : Controller
http://tahe.developpez.com
62/362
7.
{
8.
// /Second/Action01
9.
public ContentResult Action01()
10.
{
11.
return Content("Contrleur=Second, Action=Action01", "text/plain", Encoding.UTF8);
12.
}
13.
14. }
15. }
Puis demandons l'URL [/Second/Action01] avec un navigateur. Nous obtenons la rponse suivante :
Cette URL a t demande avec une commande HTTP GET comme le montrent les logs HTTP de la requte dans Chrome :
GET /Second/Action01 HTTP/1.1
L'URL peut tre demande galement avec une commande HTTP POST. Pour le montrer, utilisons de nouveau l'application
[Advanced Rest Client] :
3
2
4
en [1], on lance l'application (dans l'onglet [Applications] d'un nouvel onglet Chrome) ;
en [2], on slectionne l'option [Request] ;
en [3], on prcise l'URL demande ;
en [4], on indique que l'URL doit tre demande avec un POST ;
On active les logs de Chrome par (CTRL-I) afin d'avoir la rponse HTTP du serveur. Lorsqu'on excute [Send] la requte
prcdente, les changes HTTP sont les suivants :
Le navigateur envoie la requte suivante :
1.
2.
3.
4.
5.
6.
http://tahe.developpez.com
63/362
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?
RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxTZWNvbmRcQWN0aW9uMDE=?=
10. X-Powered-By: ASP.NET
11. Date: Tue, 24 Sep 2013 10:47:59 GMT
12. Content-Length: 148
3.14
// /Second/Action02
[HttpPost]
public ContentResult Action02()
{
return Content("Contrleur=Second, Action=Action02", "text/plain",
Encoding.UTF8);
6. }
L'action [Action02] est analogue l'action [Action01] mais on indique qu'elle n'est accessible que par une commande HTTP POST
(ligne 2). D'autres attributs sont utilisables :
HttpGet
HttpHead
HttpDelete
Demandons l'URL [/Second/Action02] directement dans le navigateur. Elle est alors demande par un GET. Le navigateur affiche
alors la rponse suivante :
http://tahe.developpez.com
64/362
3.15
ligne 1 : le code HTTP 404 Not Found indique que le serveur n'a pas trouv le document demand. Ici, l'action
[Action02] n'a pu servir la requte GET car elle ne sert que les commandes POST ;
ligne 3 : la taille du document renvoy. C'est la page qui a t affiche par le navigateur :
Dans les deux actions dcrites prcdemment, on crivait quelque chose du genre :
1. public ContentResult Action02()
2.
{
3.
return Content("Contrleur=Second, Action=Action02", "text/plain",
Encoding.UTF8);
4. }
Les noms du contrleur et de l'action taient crits en dur dans le code. Si on change ces noms, le code n'est plus correct. On peut
avoir accs au contrleur et l'action de la faon suivante :
1.
2.
3.
http://tahe.developpez.com
// /Second/Action03
public ContentResult Action03()
{
65/362
4.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
5. );
Ligne 3, les trois lments de la route peuvent tre obtenus par RouteData.Values["lment"] avec lment dans [controller,
action, id].
Demandons l'URL [http://localhost:49302/Second/Action03] :
http://tahe.developpez.com
66/362
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Dans le chapitre prcdent, nous avons regard le processus qui amne la requte [1] au contrleur et l'action [2a] qui vont la
traiter, un mcanisme qu'on appelle le routage. Nous avons prsent par ailleurs les diffrentes rponses que peut faire une action
au navigateur. Nous avons pour l'instant prsent des actions qui n'exploitaient pas la requte qui leur tait prsente. Une requte
[1] transporte avec elle diverses informations que ASP.NET MVC prsente [2a] l'action sous forme d'un modle. On ne
confondra pas ce terme avec le modle M d'une vue V [2c] qui est produit par l'action :
Requte
1
Liaison
2
Modle
d'action
3
Action
4
Modle
de Vue
Vue
Rponse
Dans le modle MVC, l'action [4] fait partie du C (contrleur), le modle de la vue [5] est le M et la vue [6] est le V.
Ce chapitre tudie les mcanismes de liaison entre les informations transportes par la requte, qui sont par nature des chanes de
caractres et le modle de l'action qui peut tre une classe avec des proprits de divers types.
4.1
Nous ajoutons [1] la solution existante un nouveau projet ASP.NET MVC de base :
http://tahe.developpez.com
67/362
La nouveaut rside la ligne 8 : la mthode [Action01] a un paramtre. On s'intresse dans ce chapitre aux diffrentes faons
d'initialiser les paramtres d'une action. Le paramtre [nom] ci-dessus est initialis dans l'ordre avec les valeurs suivantes :
http://tahe.developpez.com
68/362
Request.Form["nom"]
RouteData.Values["nom"]
Examinons ces diffrents cas. Demandons directement dans le navigateur l'URL [/First/Action01?nom=someone]. On obtient la
rponse suivante :
ligne 1 : la requte est un GET. L'URL demande embarque le paramtre [nom]. Ct serveur, la requte arrive l'action
[Action01] qui a la signature suivante :
Pour donner une valeur au paramtre nom, ASP.NET MVC essaie successivement et dans l'ordre les valeurs Request.Form["nom"],
RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"]. Il s'arrte ds qu'il a trouv une valeur. Le paramtre [nom]
embarqu dans l'URL du GET a t plac par le framework dans Request.QueryString["nom"]. C'est avec cette valeur [someone] que
va tre initialis le paramtre [nom] de [Action01]. Ensuite le code de [Action01] s'excute :
return Content(string.Format("Contrleur=First, Action=Action01, nom={0}", nom), "text/plain",
Encoding.UTF8);
Note : le mcanisme de routage est insensible la casse. Ainsi si notre action est dfinie comme :
public ContentResult Action01(string NOM)
et que le paramtre pass est [?NoM=zbulon], la liaison aura bien lieu. Le paramtre [NOM] de [Action01] recevra la valeur
[zbulon].
Maintenant, demandons la mme URL avec un POST. Pour cela, nous utilisons l'application [Advanced Rest Client] :
http://tahe.developpez.com
69/362
1
2
Envoyons cette requte et regardons les logs HTTP. La requte HTTP est la suivante :
en [1], le POST ;
en [2], les paramtres du POST. Techniquement, ils ont t envoys derrire les enttes HTTP aprs la ligne vide signalant
la fin de ces enttes ;
en [3], la rponse obtenue. On rcupre bien le paramtre [nom] du POST. Dans les valeurs essayes pour le paramtre
nom Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], c'est la premire qui a
march.
Maintenant, modifions la route par dfaut dans [App_Start/RouteConfig]. Actuellement, cette route est la suivante :
1.
2.
3.
4.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
5. );
Changeons-la en :
1.
2.
3.
4.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{nom}",
defaults: new { controller = "Home", action = "Index", nom =
UrlParameter.Optional }
5. );
http://tahe.developpez.com
70/362
Maintenant, recompilons l'application et demandons l'URL [/First/Action01/zbulon] directement dans le navigateur. Nous
obtenons, la rponse suivante :
Dans les valeurs essayes pour le paramtre nom Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"],
Request.Files["nom"], c'est la deuxime qui a march.
Faisons la mme requte avec un POST et [Advanced Rest Client] :
1
4.2
Si une action a un paramtre nomm [p], ASP.NET MVC va essayer de lui affecter l'une des valeurs Request.Form["p"],
RouteData.Values["p"], Request.QueryString["p"], Request.Files["p"]. Les trois premires valeurs sont des chanes de caractres. Si le
paramtre [p] n'est pas de type [string], des problmes peuvent survenir.
Crons la nouvelle action suivante :
1.
2.
3.
4.
// Action02
public ContentResult Action02(int age)
{
string texte = string.Format("Contrleur={0}, Action={1}, ge={2}",
RouteData.Values["controller"], RouteData.Values["action"],age);
5.
return Content(texte, "text/plain", Encoding.UTF8);
6. }
http://tahe.developpez.com
71/362
ligne 2, l'action [Action02] admet un paramtre nomm [age] de type int. Il faudra que la chane de caractres rcupre
soit convertible en int.
Cette fois-ci, on a rcupr une page d'erreur. Il est intressant de regarder les enttes HTTP envoys par le serveur dans ce cas :
1.
2.
3.
4.
5.
ligne 1 : le serveur a rpondu avec un code [500 Internal Server Error] et a envoy une page HTML (ligne 3) de 12438
octets (ligne 5) pour expliquer les raisons possibles de cette erreur.
// Action03
public ContentResult Action03(int? age)
{
...
[Action03] est identique [Action02] si ce n'est qu'on a chang le type du paramtre [age] en int?, ce qui signifie entier ou null.
Demandons l'URL [http://localhost:55483/First/Action03?age=21x]. On obtient la page suivante :
http://tahe.developpez.com
72/362
ASP.NET MVC n'a pas russi convertir [21x] en type int. Il a alors affect la valeur null au paramtre [age] comme l'autorise son
type int?. Il est cependant possible de savoir si le paramtre a pu recevoir une valeur de la requte ou non.
Nous construisons la nouvelle action [Action04] suivante :
1.
2.
3.
4.
5.
// Action04
public ContentResult Action04(int? age)
{
bool valide = ModelState.IsValid;
string texte = string.Format("Contrleur={0}, Action={1}, ge={2}, valide={3}",
RouteData.Values["controller"], RouteData.Values["action"], age, valide);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
ligne 2 : on a gard le type [int?]. Cela permet en particulier la requte de ne pas fournir le paramtre [age] qui reoit
alors la valeur null ;
ligne 4 : on teste si le modle de l'action est valide. Le modle de l'action est form de l'ensemble de ses paramtres, ici
[nom]. Le modle est valide si tous les paramtres ont pu obtenir une valeur de la requte ou bien la valeur null si le type
du paramtre le permet ;
ligne 5 : on ajoute la valeur de la variable [valide] dans le text envoy au client.
ASP.NET MVC n'a pas russi convertir [21x] en type int. Il a alors affect la valeur null au paramtre [age] comme l'autorise son
type int?. Mais il y a eu des erreurs de conversion comme le montre la valeur de [valide].
Il est possible d'avoir le message d'erreur associ une conversion rate. Examinons la nouvelle action suivante :
1.
2.
3.
4.
5.
// Action05
public ContentResult Action05(int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, ge={2}, valide={3},
erreurs={4}", RouteData.Values["controller"], RouteData.Values["action"], age,
ModelState.IsValid, erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
La nouveaut est ligne 4. On y appelle une mthode prive [getErrorMessagesFor] laquelle on passe l'tat du modle de l'action.
Elle rend une chane de caractres rassemblant les messages de toutes les erreurs qui se sont produites. Cette mthode est la
suivante :
1. private string getErrorMessagesFor(ModelStateDictionary tat)
2.
{
3.
List<String> erreurs = new List<String>();
4.
string messages = string.Empty;
http://tahe.developpez.com
73/362
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
if (!tat.IsValid)
{
foreach (ModelState modelState in tat.Values)
{
foreach (ModelError error in modelState.Errors)
{
erreurs.Add(getErrorMessageFor(error));
}
}
foreach (string message in erreurs)
{
messages += string.Format("[{0}]", message);
}
}
return messages;
}
ligne 1 : on reoit un type [ModelError] qui encapsule une erreur sur l'un des lments du modle de l'action. On va
chercher le message d'erreur dans trois endroits diffrents :
http://tahe.developpez.com
74/362
Durant les tests, on remarque que le message d'erreur est trouv dans ces trois endroits selon la nature de l'lment du modle. Il
doit y avoir une rgle qui permette d'obtenir coup sr le message d'erreur associ un lment du modle, mais je ne la connais
pas. Je le cherche donc aux diffrents endroits o je peux le trouver et ce dans un certain ordre. Ds qu'un message non vide a t
trouv, il est retourn.
Demandons l'URL [http://localhost:55483/First/Action05?age=21x]. On obtient la page suivante :
4.3
// Action06
public ContentResult Action06(double? poids, int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, poids={2}, ge={3},
valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"],
poids, age, ModelState.IsValid, erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
Les rgles dcrites prcdemment s'appliquent maintenant aux deux paramtres. Voici quelques exemples d'excution :
http://tahe.developpez.com
75/362
4.4
Dfinissons une classe qui sera le modle d'une action. Nous la mettons dans le dossier [Models] [1].
1
Notre classe a comme proprits automatiques, les deux paramtres [Poids] et [Age] tudis prcdemment. Cette classe sera le
paramtre d'entre de l'action [Action07] :
1.
2.
3.
4.
5.
// Action07
public ContentResult Action07(ActionModel01 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, poids={2}, ge={3},
valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"],
modle.Poids, modle.Age, ModelState.IsValid, erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
On remarquera que le routage n'est pas sensible la casse. Les paramtres de la requte taient [age] et [poids]. Ils ont aliment les
proprits [Age] et [Poids] de la classe [ModelAction01].
http://tahe.developpez.com
76/362
Par ailleurs, nous avons judqu' maintenant utiliser des requtes HTTP [GET]. Montrons que les requtes [POST] ont le mme
comportement. Pour cela utilisons de nouveau l'application [Advanced Rest Client] :
1
2
4.5
les paramtres [poids] et [age] peuvent tre absents de la requte. Dans ce cas, les proprits [Poids] et [Age] reoivent la valeur
[null] et aucune erreur n'est signale. On pourrait vouloir transformer le modle comme suit :
1. namespace Exemple_02.Models
2. {
3.
public class ActionModel01
4.
{
5.
public double Poids { get; set; }
6.
public int Age { get; set; }
7.
}
8. }
Lignes 5 et 6, les proprits [Poids] et [Age] ne peuvent plus avoir la valeur [null]. Voyons ce qui se passe avec ce nouveau modle
lorsque les paramtres [poids] et [age] sont absents de la requte.
http://tahe.developpez.com
77/362
Il n'y a pas eu d'erreurs et les proprits [Poids] et [Age] ont gard leur valeur d'initialisation : 0. ASP.NET MVC a
cr une instance du modle par un new ActionModel01. C'est l que les proprits [Poids] et [Age] ont reu leur valeur 0 ;
n'a affect aucune valeur ces deux proprits car il n'y avait aucun paramtre portant leur nom.
Le premier modle nous permet de vrifier l'absence d'un paramtre : la proprit correspondante a alors la valeur [null]. Le second
ne nous le permet pas. Il est possible d'ajouter d'autres contraintes de validation que le simple type des paramtres. Nous allons
maintenant les prsenter.
Considrons le nouveau modle d'action suivant :
1. using System.ComponentModel.DataAnnotations;
2. namespace Exemple_02.Models
3. {
4.
public class ActionModel02
5.
{
6.
[Required]
7.
[Range(1, 200)]
8.
public double? Poids { get; set; }
9.
[Required]
10.
[Range(1, 150)]
11.
public int? Age { get; set; }
12. }
13. }
// Action08
public ContentResult Action08(ActionModel02 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, poids={2}, ge={3},
valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"],
modle.Poids, modle.Age, ModelState.IsValid, erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
http://tahe.developpez.com
78/362
Les erreurs sont bien dtectes. Maintenant, faisons voluer le modle comme suit :
1. using System.ComponentModel.DataAnnotations;
2. namespace Exemple_02.Models
3. {
4.
public class ActionModel02
5.
{
6.
[Required]
7.
[Range(1, 200)]
8.
public double Poids { get; set; }
9.
[Required]
10.
[Range(1, 150)]
11.
public int Age { get; set; }
12. }
13. }
Lignes 8 et 11, les proprits ne peuvent plus avoir la valeur [null]. Compilons et refaisons le test sans paramtres :
http://tahe.developpez.com
79/362
L'absence de paramtres a fait que les proprits [Poids] et [Age] ont gard leur valeur acquise lors de l'instanciation du modle : 0.
La validation intervient ensuite. L'attribut [Required] est alors satisfait. On voit que le message d'erreur ci-dessus est celui de
l'attribut [Range]. Donc pour vrifier la prsence d'un paramtre, il faut que la proprit associe soit nullable, --d puisse recevoir la
valeur null.
Revenons au modle [ActionModel02] initial et considrons une action dont le modle est constitu d'une instance
[ActionModel02] et d'un type [DateTime] nullable :
1.
2.
3.
4.
5.
// Action09
public ContentResult Action09(ActionModel02 modle, DateTime? date)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, poids={2}, ge={3},
date={4}, valide={5}, erreurs={6}", RouteData.Values["controller"],
RouteData.Values["action"], modle.Poids, modle.Age, date, ModelState.IsValid,
erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
On n'a pas pass de paramtres l'action. Les attributs [Required] des proprits [Poids] et [Age] ont jou leur rle. La date elle, a
reu la valeur null et aucune erreur n'a t signale.
On passe maintenant des paramtres invalides :
http://tahe.developpez.com
80/362
1. using System.ComponentModel.DataAnnotations;
2. namespace Exemple_02.Models
3. {
4.
public class ActionModel03
5.
{
6.
[Required(ErrorMessage = "Le paramtre email est requis")]
7.
[EmailAddress(ErrorMessage = "Le paramtre email n'a pas un format valide")]
8.
public string Email { get; set; }
9.
10.
[Required(ErrorMessage = "Le paramtre jour est requis")]
11.
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramtre jour doit avoir 1 ou 2
chiffres")]
12.
public string Jour { get; set; }
13.
14.
[Required(ErrorMessage = "Le paramtre info1 est requis")]
15.
[MaxLength(4, ErrorMessage = "Le paramtre info1 ne peut avoir plus de 4 caractres")]
16.
public string Info1 { get; set; }
17.
18.
[Required(ErrorMessage = "Le paramtre info2 est requis")]
19.
[MinLength(2, ErrorMessage = "Le paramtre info2 ne peut avoir moins de 2 caractres")]
20.
public string Info2 { get; set; }
21.
22.
[Required(ErrorMessage = "Le paramtre info3 est requis")]
23.
[MinLength(4, ErrorMessage = "Le paramtre info3 doit avoir 4 caractres exactement")]
24.
[MaxLength(4, ErrorMessage = "Le paramtre info3 doit avoir 4 caractres exactement")]
25.
public string Info3 { get; set; }
26. }
27. }
ligne 6 : l'attribut [Required] avec cette fois, un message d'erreur que nous fixons nous mmes ;
ligne 7 : l'attribut [EMailAddress] demande ce que le champ [Email] contienne une adresse lectronique de format
valide ;
ligne 11 : l'attribut [RegularExpression] demande ce que le champ [Jour] contienne une chane d'un ou deux chiffres. Le
premier paramtre est l'expression rgulire que doit vrifier le champ ;
ligne 15 : l'attribut [MaxLength] demande ce que le champ [Info1] ait au plus 4 caractres ;
ligne 19 : l'attribut [MinLength] demande ce que le champ [Info2] ait au moins 2 caractres ;
lignes 23-24 : les attributs [MaxLength] et [MinLength] combins demandent ce que le champ [Info3] ait exactement 4
caractres ;
http://tahe.developpez.com
81/362
3.
4.
5.
4.6
Nous prsentons d'autres contraintes d'intgrit. Le nouveau modle de l'action sera la classe [ActionModel04] suivante :
1. using System.ComponentModel.DataAnnotations;
http://tahe.developpez.com
82/362
2.
3. namespace Exemple_02.Models
4. {
5.
public class ActionModel04
6.
{
7.
[Required(ErrorMessage="Le paramtre url est requis")]
8.
[Url(ErrorMessage="URL invalide")]
9.
public string Url { get; set; }
10.
[Required(ErrorMessage = "Le paramtre info1 est requis")]
11.
public string Info1 { get; set; }
12.
[Required(ErrorMessage = "Le paramtre info2 est requis")]
13.
[Compare("Info1",ErrorMessage="Les paramtres info1 et info2 doivent tre identiques")]
14.
public string Info2 { get; set; }
15.
[Required(ErrorMessage = "Le paramtre cc est requis")]
16.
[CreditCard(ErrorMessage = "Le paramtre cc n'est pas un n de carte de crdit
valide")]
17.
public string Cc { get; set; }
18. }
19. }
// Action11
public ContentResult Action11(ActionModel04 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("URL={0}, Info1={1}, Info2={2}, CC={3},erreurs={4}",
modle.Url, modle.Info1, modle.Info2, modle.Cc, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
Pour tester l'action [Action11], nous utilisons l'application [Advanced Rest Client] :
http://tahe.developpez.com
83/362
1
2
http://tahe.developpez.com
84/362
4.7
Parfois les contraintes d'intgrit disponibles ne suffisent pas. On peut alors en crer soit mme. On peut notamment utiliser un
modle implmentant l'interface [IValidatableObject]. Dans ce cas, on ajoute nos propres vrifications du modle dans la mthode
[Validate] de cette interface. Voyons un exemple. Le nouveau modle de l'action sera la classe [ActionModel05] suivante :
1. using System.Collections.Generic;
2. using System.ComponentModel.DataAnnotations;
3.
4. namespace Exemple_02.Models
5. {
6.
public class ActionModel05 : IValidatableObject
7.
{
8.
[Required(ErrorMessage = "Le paramtre taux est requis")]
9.
public double? Taux { get; set; }
10.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
11.
{
12.
List<ValidationResult> rsultats = new List<ValidationResult>();
13.
bool ok = Taux < 4.2 || Taux > 6.7;
http://tahe.developpez.com
85/362
14.
15.
16.
if (!ok)
{
rsultats.Add(new ValidationResult("Le paramtre taux doit tre < 4.2 ou > 6.7",
new string[] { "Taux" }));
17.
}
18.
return rsultats;
19.
}
20. }
21. }
// Action12
public ContentResult Action12(ActionModel05 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
4.8
ligne 2 : le modle de l'action est constitu d'un tableau de [string]. Il nous permet de rcuprer un paramtre nomm
[data] qui peut tre prsent plusieurs fois dans les paramtres de la requte comme dans [?
data=data1&data=data2&data=data3]. Les diffrents paramtres [data] de la requte vont alimenter le tableau [data] du
modle de l'action. Ce cas se rencontre avec les listes choix multiple. Le navigateur envoie alors les diffrentes valeurs
slectionnes par l'utilisateur avec le mme nom de paramtre.
http://tahe.developpez.com
86/362
Voici un exemple :
// Action14
public ContentResult Action14(List<int> data)
{
string erreurs = getErrorMessagesFor(ModelState);
string strData = "";
if (data != null && data.Count != 0)
{
strData = string.Join(",", data);
}
string texte = string.Format("data=[{0}], erreurs=[{1}]", strData, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
Le modle est ici une liste d'entiers (ligne 2). Voici une premire excution :
et une seconde :
4.9
Parfois nous disposons d'un modle mais nous souhaitons que seuls certains lments du modle soient initialiss par la requte
HTTP. Considrons le modle d'action [ActionModel06] suivant :
1.
2.
3.
4.
5.
6.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_02.Models
{
[Bind(Exclude = "Info2")]
http://tahe.developpez.com
87/362
7.
public class ActionModel06
8.
{
9.
[Required(ErrorMessage = "Le paramtre [info1] est requis")]
10.
public string Info1 { get; set; }
11.
12.
public string Info2 { get; set; }
13. }
14. }
// Action15
public ContentResult Action15(ActionModel06 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("valide={0}, info1={1}, info2={2}, erreurs={3}",
ModelState.IsValid, modle.Info1, modle.Info2, erreurs);
6.
return Content(texte, "text/plain", Encoding.UTF8);
7. }
4.10
Navigateur
Serveur web
1
Action
Html
2
La classe de l'action est instancie au dbut de la requte du client et dtruite la la fin de celle-ci. Aussi ne peut-elle servir
mmoriser des donnes entre deux requtes mme si elle est appele de faon rpte. On peut vouloir mmoriser deux types de
donnes :
des donnes partages par tous les utilisateurs de l'application web. Ce sont en gnral des donnes en lecture seule.
Trois fichiers sont utiliss pour mettre en oeuvre ce partage de donnes :
http://tahe.developpez.com
88/362
[Global.asax, Global.asax.cs] : permettent de dfinir une classe, appele classe globale d'application, dont
la dure de vie est celle de l'application, ainsi que des gestionnaires pour certains vnements de cette mme
application.
La classe globale d'application permet de dfinir des donnes qui seront disponibles pour toutes les requtes de tous les
utilisateurs.
des donnes partages par les requtes d'un mme client. Ces donnes sont mmorises dans un objet appel
Session. On parle alors de session client pour dsigner la mmoire du client. Toutes les requtes d'un client ont accs
cette session. Elles peuvent y stocker et y lire des informations.
Navigateur
Serveur web
Mmoire
Application
Action
Mmoire
Session
Html
2
Ci-dessus, nous montrons les types de mmoire auxquels a accs une action :
la mmoire de l'application qui contient la plupart du temps des donnes en lecture seule et qui est accessible tous les
utilisateurs ;
la mmoire d'un utilisateur particulier, ou session, qui contient des donnes en lecture / criture et qui est accessible aux
requtes successives d'un mme utilisateur ;
non reprsente ci-dessus, il existe une mmoire de requte, ou contexte de requte. La requte d'un utilisateur peut tre
traite par plusieurs actions successives. Le contexte de la requte permet une action 1 de transmettre de l'information
une action 2.
Regardons un premier exemple mettant en lumire ces diffrentes mmoires :
Tout d'abord, nous modifions le fichier [Web.config] du projet [Exemple-02] de la faon suivante :
1.
<appSettings>
2.
<add key="webpages:Version" value="2.0.0.0" />
3.
...
4.
<add key="infoAppli1" value="infoAppli1"/>
5. </appSettings>
Nous rajoutons la ligne 4 qui la cl [infoAppli1] associe la valeur [infoappli1]. Ce sera notre donne de porte [Application] : elle
sera accessible toutes les requtes de tous les utilisateurs.
Ensuite nous modifions la mthode [Application_Start] dui fichier [Global.asax]. Cette mthode s'excute une unique fois au
dmarrage de l'application. C'est l qu'il faut exploiter le fichier [Web.config] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11. }
elle l'enregistre dans le dictionnaire [HttpApplication.Application], associe la cl [infoAppli1]. Toutes les actions ont
accs ce dictionnaire.
Dans le mme fichier [Gloabal.asax], on ajoute la mthode [Session_Start] suivante :
http://tahe.developpez.com
89/362
1.
2.
3.
4.
5. }
La mthode [Session_Start] est excute pour tout nouvel utilisateur. Qu'est-ce qu'un nouvel utilisateur ? Un utilisateur est " suivi "
par un jeton de session. Ce jeton est :
cr par le serveur web et envoy au nouvel utilisateur dans les enttes HTTP de la rponse ;
renvoy par le navigateur de l'utilisateur chaque nouvelle requte. Cela permet au serveur de reconnatre l'utilisateur et de
grer une mmoire pour lui qu'on appelle la session de l'utilisateur.
Le serveur web reconnat qu'il a affaire un nouvel utilisateur lorsque celui-ci ne lui envoie pas de jeton de session. Le serveur lui en
cre alors un.
Ligne 4 ci-dessus, on met dans la session de l'utilisateur un compteur qui sera incrment chaque requte de cet utilisateur. Cela
illustrera la mmoire associe un utilisateur. La classe [Session] s'utilise comme un dictionnaire (ligne 4).
Ceci fait, nous crivons l'action [Action16] suivante :
1. // Action16
2.
public ContentResult Action16()
3.
{
4.
// on rcupre le contexte de la requte HTTP
5.
HttpContextBase contexte = ControllerContext.HttpContext;
6.
// on rcupre les infos de porte Application
7.
string infoAppli1 = contexte.Application["infoAppli1"] as string;
8.
// et celles de porte Session
9.
int? compteur = contexte.Session["compteur"] as int?;
10.
compteur++;
11.
contexte.Session["compteur"] = compteur;
12.
// la rponse au client
13.
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1,
compteur);
14.
return Content(texte, "text/plain", Encoding.UTF8);
15. }
ligne 5 : on rcupre le contexte de la requte HTTP en cours de traitement. Ce contexte va nous donner accs aux
donnes de porte [Application] et [Session] ;
ligne 7 : on rcupre l'information de porte [Application] ;
ligne 9 : on rcupre le compteur dans la session ;
lignes 10-11 : il est incrment puis remis dans la session ;
lignes 13-14 : les deux informations sont envoyes au client.
En [2], le client a fait au total trois requtes. A chaque fois, il a pu rcuprer le compteur mis jour par la prcdente requte.
Pour simuler un second utilisateur, nous utilisons un deuxime navigateur pour demander la mme URL :
http://tahe.developpez.com
90/362
En [3], le second utilisateur rcupre bien la mme information de porte [Application] mais a son propre compteur de porte
[Session].
Revenons au code de l'action [Action16] :
1. // Action16
2.
public ContentResult Action16()
3.
{
4.
// on rcupre le contexte de la requte HTTP
5.
HttpContextBase contexte = ControllerContext.HttpContext;
6.
// on rcupre les infos de porte Application
7.
string infoAppli1 = contexte.Application["infoAppli1"] as string;
8.
// et celles de porte Session
9.
int? compteur = contexte.Session["compteur"] as int?;
10.
compteur++;
11.
contexte.Session["compteur"] = compteur;
12.
// la rponse au client
13.
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
14.
return Content(texte, "text/plain", Encoding.UTF8);
15. }
L'un des buts du framework ASP.NET MVC est de rendre les contrleurs et les actions testables de faon isole sans serveur web.
Or on voit ligne 5, que le contexte de la requte HTTP est ncessaire pour rcuprer les informations de porte [Application] et de
porte [Session]. On se propose de crer une nouvelle action [Action17] qui recevrait les donnes de porte [Application] et
[Session] en paramtres :
1.
2.
// Action17
public ContentResult Action17(ApplicationModel applicationData, SessionModel
sessionData)
3.
{
4.
// on rcupre les infos de porte Application
5.
string infoAppli1 = applicationData.InfoAppli1;
6.
// et celles de porte Session
7.
int compteur = sessionData.Compteur++;
8.
// la rponse au client
9.
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1,
compteur);
10.
return Content(texte, "text/plain", Encoding.UTF8);
11. }
Le code ne comporte dsormais plus de dpendances envers la requte HTTP. Elle peut donc tre teste isolment d'un serveur
web.
Voyons comment y arriver. Tout d'abord, il nous faut crer les classes [ApplicationModel] et [SessionModel] qui vont encapsuler
respectivement les donnes de porte [Application] et [Session]. Ce sont les suivantes :
http://tahe.developpez.com
91/362
1. namespace Exemple_02.Models
2. {
3.
public class ApplicationModel
4.
{
5.
public string InfoAppli1 { get; set; }
6.
}
7. }
1. namespace Exemple_02.Models
2. {
3.
public class SessionModel
4.
{
5.
public int Compteur { get; set; }
6.
public SessionModel()
7.
{
8.
Compteur = 0;
9.
}
10. }
11. }
Ensuite, il nous faut modifier les mthodes [Application_Start] et [Session_Start] du fichier [Global.asax] :
1. public class MvcApplication : System.Web.HttpApplication
2.
{
3.
protected void Application_Start()
4.
{
5.
AreaRegistration.RegisterAllAreas();
6.
7.
WebApiConfig.Register(GlobalConfiguration.Configuration);
8.
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
9.
RouteConfig.RegisterRoutes(RouteTable.Routes);
10.
BundleConfig.RegisterBundles(BundleTable.Bundles);
11.
// intialisation application - cas 1
12.
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
13.
// intialisation application - cas 2
14.
ApplicationModel data=new ApplicationModel();
15.
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
16.
Application["data"] = data;
17.
}
18.
19.
protected void Session_Start()
20.
{
21.
// initialisation compteur - cas 1
22.
Session["compteur"] = 0;
23.
// initialisation compteur - cas 2
24.
Session["data"] = new SessionModel();
25.
}
26. }
http://tahe.developpez.com
92/362
ligne 16 : et place dans le dictionnaire de [Application], asssocie la cl [data]. [Application] est une proprit de la classe
[HttpApplication] de la ligne 1 ;
ligne 24 : une instance de [SessionModel] est cre et place dans le dictionnaire de [Session], asssocie la cl [data].
[Session] est une proprit de la classe [HttpApplication] de la ligne 1 ;
signifie que la requte HTTP traite par l'action devra comporter des paramtres nomms [applicationData] et [sessionData]. Ce ne
sera pas le cas. Nous devons crer un nouveau modle de liaison de donnes pour que lorsqu'une action reoit en paramtre un
type :
ligne 5 : la classe implmente l'interface [IModelBinder]. Pour comprendre son code, il faut savoir qu'elle sera appele
chaque fois qu'une action aura un paramtre de type [ApplicationModel]. Cette liaison [ApplicationModel] -->
[ApplicationModelBinder] sera faite au dmarrage de l'application, dans la mthode [Application_Start] de [Global.asax] ;
ligne 7 : l'unique mthode de l'interface [IModelBinder] ;
ligne 7 : le paramtre de type [ControllerContext] nous donne accs la requte HTTP en cours de traitement ;
ligne 7 : le paramtre de type [ModelBindingContext] nous donne accs des informations sur le modle construire, ici
le type [ApplicationModel] ;
ligne 7 : le rsultat de [BindModel] est l'objet qui va tre affect au paramte li, ici un paramtre de type
[ApplicationModel] ;
ligne 10 : nous nous contentons de rendre l'objet de porte [Application] et de cl [data].
http://tahe.developpez.com
93/362
7.
Il ne nous reste plus qu' associer chacun des modles [XModel] son binder [XModelBinder]. Ceci est fait dans la mthode
[Application_Start] de [Global.asax] :
1.
protected void Application_Start()
2.
{
3. ....
4.
// intialisation application - cas 2
5.
ApplicationModel data=new ApplicationModel();
6.
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
7.
Application["data"] = data;
8.
// model binders
9.
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
10.
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
11. }
ligne 9 : lorsqu'une action aura un paramtre de type [ApplicationModel], la mthode [ApplicationModelBinder.Bind] sera
appele. On sait qu'elle rend la donne de porte [Application] associe la cl [data] ;
ligne 10 : idem pour le type [SessionModel].
// Action17
public ContentResult Action17(ApplicationModel applicationData, SessionModel
sessionData)
3.
{
4.
// on rcupre les infos de porte Application
5.
string infoAppli1 = applicationData.InfoAppli1;
6.
// et celles de porte Session
7.
sessionData.Compteur++;
8.
int compteur = sessionData.Compteur;
9.
// la rponse au client
10.
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1,
compteur);
11.
return Content(texte, "text/plain", Encoding.UTF8);
12. }
Ces deux donnes peuvent tre aussi complexes que l'on veut et regrouper pour l'une toutes les donnes de porte [Application] et
pour l'autre toutes les donnes de porte [Session].
Voici un exemple d'excution de l'action [Action17] :
http://tahe.developpez.com
94/362
4.11
cre une instance de type [ActionModel05] en utilisant son constructeur sans paramtres ;
l'initialise avec les informations de la requte qui ont le mme nom que l'une des proprits de [ActionModel05].
Parfois ce comportement ne nous convient pas. C'est notamment le cas lorsqu'on veut utiliser un constructeur particulier du modle
de l'action. On peut alors procder de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9. }
// Action18
public ContentResult Action18()
{
ActionModel05 modle = new ActionModel05();
TryUpdateModel(modle);
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
ligne 2 : l'action ne reoit plus de paramtres. Il n'y a donc plus de liaison automatique de donnes ;
ligne 4 : nous crons nous-mme une instance du modle de l'action. C'est l qu'on pourrait utiliser un constructeur
diffrent ;
ligne 5 : on initialise le modle avec les informations de la requte. C'est ASP.NET MVC qui fait ce travail. Il le fait de la
mme faon qu'il l'aurait fait si le modle avait t en paramtre ;
ligne 6 : on est dsormais dans la mme situation que dans l'action [Action12].
4.12
Conclusion
http://tahe.developpez.com
95/362
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Une requte [1] transporte avec elle diverses informations que ASP.NET MVC prsente [2a] l'action sous forme d'un modle que
nous avons appel modle d'action.
Requte
1
Liaison
2
Modle
3 d'action
Action
4
Modle
de Vue
Vue
Rponse
Dans le modle MVC, l'action [4] fait partie du C (contrleur), le modle de la vue [5] est le M et la vue [6] est le V.
Ce chapitre a tudi les mcanismes de liaison entre les informations transportes par la requte, qui sont par nature des chanes de
caractres et le modle de l'action qui peut tre une classe avec des proprits de divers types. Nous avons vu galement qu'il tait
possible de vrifier la validit du modle prsent l'action. Enfin, nous avons vu comment largir ce modle aux donnes de
porte [Session] et [Application].
Nous allons nous intresser maintenant la fin de la chane de traitement de la requte [1] : la cration de la vue [6] et de son
modle [5]. Ces deux lments sont produits par l'action [4].
http://tahe.developpez.com
96/362
Introduction
Application web
couche [web]
1
Navigateur
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Dans le chapitre prcdent, nous avons tudi comment ASP.NET MVC prsentait les informations de la requte [1] une action
[2a] sous la forme d'un modle qui pouvait contenir des contraintes de validation. Ce modle tait fourni en entre l'action et nous
l'avons appel le modle de l'action. Nous nous intressons maintenant au rsultat le plus courant d'une action, le type [ViewResult]
qui correspond une vue V [3] accompagne de son modle M [2c]. Ce modle sera appel modle de la vue V, ne pas
confondre avec le modle de l'action que nous venons d'tudier. L'un est en entre de l'action, l'autre est en sortie.
Commenons par crer un nouveau projet [Exemple-03] [1] toujours l'intrieur de la mme solution, de type ASP.NET MVC de
base :
2
1
Crons un contrleur nomm [First] [2]. Le code gnr pour ce contrleur est le suivant :
1. using System.Web.Mvc;
2.
3. namespace Exemple_03.Controllers
4. {
5.
public class FirstController : Controller
6.
{
7.
public ActionResult Index()
8.
{
9.
return View();
10.
}
11.
http://tahe.developpez.com
97/362
12. }
13. }
lignes 7-10 : une action [Index] a t cre. Le type du rsultat de la mthode [Index] est celui de la classe [ActionResult]
dont drivent la plupart des rsultats possibles d'une action ;
ligne 9 : la mthode [View] de la classe [Controller] (ligne 5) rend un type [ViewResult] qui drive de [ActionResult]. Cette
mthode admet de nombreuses surcharges. Nous en verrons quelques unes. La principale est la suivante :
le premier paramtre est le nom de la vue. S'il est absent, la vue utilise est celle qui porte le mme nom que l'action qui
produit le [ViewResult] et qui sera cherche dans le dossier [/Views/{controller}] o {controller} est le nom du
contrleur ;
le second est le modle de la vue. S'il est absent, le modle n'a pas de vue.
4.
demande la vue [/Views/First/Index.cshtml] de s'afficher. Elle ne lui transmet aucun modle. Crons [1] le dossier
[/Views/First] :
http://tahe.developpez.com
98/362
4
2
On indique le nom de la vue en [3]. Celle-ci est cre en [4]. Le code gnr est le suivant :
1. @{
2.
Layout = null;
3. }
4.
5. <!DOCTYPE html>
6.
7. <html>
8. <head>
9.
<meta name="viewport" content="width=device-width" />
10.
<title>Index</title>
11. </head>
12. <body>
13.
<div>
14.
15.
</div>
16. </body>
17. </html>
On a l du HTML classique sauf pour les lignes 1-3 qui sont du code C#. Le programme qui gre les vues est appel un moteur de
vue. Il s'occupe de grer tout ce qui n'est pas du HTML pour en faire du HTML. Au final en effet, c'est ce qui va tre envoy au
client. Le moteur de vue s'appelle ici [Razor]. Il permet d'inclure du code C# dans une vue. [Razor] va interprter ce code C# et
produire partir de lui du code HTML. Voici quelques rgle de base pour l'inclusion de code C# dans une vue :
le basculement du HTML vers le C# se fait la rencontre du caractre @ (ligne 1). Si ce caractre introduit un bloc de
code, on mettra les accolades (lignes 1 et 2). S'il introduit une variable dont on veut rcuprer la valeur, on crira
simplement @variable ;
le basculement du C# vers le HTML se fait la rencontre du caractre < (ligne 5). Parfois, on est amen forcer ce
basculement notamment lorsqu'on inclut dans la page du texte brut sans balise HTML. On utilisera alors la balise <text>
pour introduire le texte : <text>ici du texte brut</text>.
La ligne 2 ci-dessus indique la vue [Index] n'a pas de page matre. Nous avons vu ce concept page 47.
Modifions la vue de la faon suivante :
1. @{
2.
Layout = null;
3.
string vue = "Index";
4. }
5.
6. <!DOCTYPE html>
7.
8. <html>
9. <head>
10. <meta name="viewport" content="width=device-width" />
http://tahe.developpez.com
99/362
11. <title>Index</title>
12. </head>
13. <body>
14. <div>
15.
<h3>Vue @vue</h3>
16. </div>
17. </body>
18. </html>
5.2
Nous crons une nouvelle action appele [Action01] associe la vue [Action01.cshtml] :
// Action01
public ViewResult Action01()
{
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
http://tahe.developpez.com
100/362
5.
6. }
return View();
ligne 4 : on utilise la proprit [ViewBag] du contrleur. C'est un objet dynamique auquel on peut ajouter des proprits
comme il est fait ligne 4. Cet objet a la particularit d'tre galement accessible la vue. C'est donc une faon de lui
transmettre de l'information ;
ligne 5 : la vue par dfaut de l'action est demande. C'est la vue [/First/Action01.cshtml]. Aucun modle ne lui est
transmis.
5.3
La mthode prcdente a l'inconvnient de ne pas permettre la dtection d'erreurs avant l'excution. Ainsi si la vue
[Action01.cshtml] utilise le code
<h4>@ViewBag.Info</h4>
on aura une erreur car la proprit [Info] n'existe pas. Celle cre par l'action [Action01] s'appelle [info]. On peut alors utiliser un
modle fortement typ pour viter ce dsagrment.
Dans un des exemples tudis prcdemment, l'action tait la suivante :
1.
2.
3.
4.
5.
// Action10
public ContentResult Action10(ActionModel03 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3},
info3={4}, erreurs={5}",
6.
modle.Email, modle.Jour, modle.Info1, modle.Info2, modle.Info3, erreurs);
http://tahe.developpez.com
101/362
7.
8. }
L'action [Action10] transmettait son client six informations (Email, Jour, Info1, Info2, Info3, erreurs) sous la forme d'une chane
de caractres. Nous allons transmettre ces informations dans un modle de vue [ViewModel01]. Comme ce modle reprend des
informations de [ActionModel03] nous allons le faire driver cette classe.
Nous commenons par recopier [ActionModel03] du projet [Exemple-02] dans le projet [Exemple-03] actuel :
et nous changeons son espace de noms pour reprendre celui du projet [Exemple-03] :
1. using System.ComponentModel.DataAnnotations;
2. namespace Exemple_03.Models
3. {
4.
public class ActionModel03
5.
{
6.
[Required(ErrorMessage = "Le paramtre email est requis")]
7.
[EmailAddress(ErrorMessage = "Le paramtre email n'a pas un format valide")]
8.
public string Email { get; set; }
9.
10.
[Required(ErrorMessage = "Le paramtre jour est requis")]
11.
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramtre jour doit avoir 1 ou 2
chiffres")]
12.
public string Jour { get; set; }
13.
14.
[Required(ErrorMessage = "Le paramtre info1 est requis")]
15.
[MaxLength(4, ErrorMessage = "Le paramtre info1 ne peut avoir plus de 4 caractres")]
16.
public string Info1 { get; set; }
17.
18.
[Required(ErrorMessage = "Le paramtre info2 est requis")]
19.
[MinLength(2, ErrorMessage = "Le paramtre info2 ne peut avoir moins de 2 caractres")]
20.
public string Info2 { get; set; }
21.
22.
[Required(ErrorMessage = "Le paramtre info3 est requis")]
23.
[MinLength(4, ErrorMessage = "Le paramtre info3 doit avoir 4 caractres exactement")]
24.
[MaxLength(4, ErrorMessage = "Le paramtre info3 doit avoir 4 caractres exactement")]
25.
public string Info3 { get; set; }
26. }
27. }
http://tahe.developpez.com
102/362
ligne 3 : la classe hrite de [ActionModel03] et donc des proprits [Email, Jour, Info1, Info2, Info3] ;
ligne 5 : on lui rajoute la proprit [Erreurs].
// Action02
public ViewResult Action02(ActionModel03 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
return View(new ViewModel01(){Email=modle.Email, Jour=modle.Jour,
Info1=modle.Info1, Info2=modle.Info2, Info3=modle.Info3, Erreurs=erreurs});
6. }
ligne 1 : [Action02] reoit le modle d'action [ActionModel03]. Elle rend un rsultat de type [ViewResult] ;
ligne 4 : les erreurs lies au modle d'action [ActionModel03] sont agrges dans la chane de caractres [erreurs]. La
mthode [getErrorMessagesFor] a t dcrite page 73 et a t incluse dans le contrleur [First] du nouveau projet ;
ligne 5 : la mthode [View] est appele avec un paramtre. Celui est le modle de la vue. Cette dernire n'est pas prcise.
Ce sera donc la vue par dfaut [/Views/First/Action02] qui sera utilise. Le modle de vue [ViewModel01] est instanci et
initialis avec les cinq informations du modle d'action [ActionModel03] et l'information [erreurs] construite ligne 4.
http://tahe.developpez.com
103/362
19.
<li>Info2 : @Model.Info2</li>
20.
<li>Info3 : @Model.Info3</li>
21.
<li>Erreurs : @Model.Erreurs</li>
22. </ul>
23. </body>
24. </html>
la nouveaut rside en ligne 1. La notation [@model] fixe le type du modle de la vue. Ce modle est ensuite rfrenc par
la notation [@Model] (lignes 16-21) ;
lignes 16-21 : les informations du modle sont affiches dans une liste.
http://tahe.developpez.com
104/362
Dans cet exemple, le modle de la vue [ViewModel01] reprend les informations du modle d'action [ActionModel03]. C'est souvent
le cas. On peut alors utiliser un unique modle qui servira la fois de modle d'action et de vue. Nous crons un nouveau modle
[ActionModel04] :
http://tahe.developpez.com
105/362
33. }
lignes 8-28 : le modle de l'action avec ses contraintes d'intgrit. Ces champs feront galement partie de la vue ;
ligne 31 : une proprit propre au modle de la vue. Elle a t exclue du modle de l'action par l'annotation de la ligne 5.
// Action03
public ViewResult Action03(ActionModel04 modle)
{
modle.Erreurs = getErrorMessagesFor(ModelState);
return View(modle);
2
1
3
en [1] : clic droit dans le code de [Action03] puis [Ajouter une vue] ;
en [2] : le nom de la vue propose par dfaut ;
en [3] : indiquer qu'on cre une vue fortement type ;
en [4] : prendre dans la liste droulante la bonne classe, ici la classe [ActionModel04] ;
en [5] : la vue cre.
Nous donnons la vue [Action03] le mme code qu' la vue [Action02]. Seul le modle de vue (ligne 1) et le titre de page (ligne 11)
changent :
1. @model Exemple_03.Models.ActionModel04
2. @{
3.
Layout = null;
4. }
5.
6. <!DOCTYPE html>
7.
8. <html>
9. <head>
10. <meta name="viewport" content="width=device-width" />
11. <title>Action03</title>
12. </head>
13. <body>
14. <h3>Informations du modle de vue</h3>
15. <ul>
16.
<li>Email : @Model.Email</li>
17.
<li>Jour : @Model.Jour</li>
18.
<li>Info1 : @Model.Info1</li>
19.
<li>Info2 : @Model.Info2</li>
http://tahe.developpez.com
106/362
20.
<li>Info3 : @Model.Info3</li>
21.
<li>Erreurs : @Model.Erreurs</li>
22. </ul>
23. </body>
24. </html>
Les rsultats sont les mmes qu'auparavant. Il est frquent d'utiliser le mme modle pour l'action et la vue car le modle de la vue
reprend souvent des informations du modle de l'action. On utilise alors un modle plus large, utilisable la fois par l'action et la
vue que celle-ci gnre. On prendra soin d'exclure de la liaison de donnes, les informations qui n'appartiennent pas au modle de
l'action. Sinon, un utilisateur bien renseign pourrait initialiser des parties du modle de la vue notre insu.
5.4
Nous allons maintenant prsenter quelques lments des vues [Razor], principalement les instructions foreach et if.
Supposons qu'on veuille prsenter une liste de personnes dans un tableau HTML. Le modle de la vue pourrait tre le suivant
[ViewModel02] :
1. namespace Exemple_03.Models
2. {
3.
public class ViewModel02
4.
{
5.
public Personne[] Personnes { get; set; }
6.
public ViewModel02()
7.
{
8.
Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne
{ Nom = "Pauline", Age = 12 } };
9.
}
10. }
11.
12. public class Personne
13. {
14.
public string Nom { get; set; }
15.
public int Age { get; set; }
16. }
http://tahe.developpez.com
107/362
17. }
// Action04
public ViewResult Action04()
{
return View(new ViewModel02());
http://tahe.developpez.com
108/362
34. </html>
D'autres lments d'une vue peuvent tre aliments par une collection : les listes droulantes ou non, les boutons radio, les cases
cocher. Considrons le nouvel exemple suivant qui affiche une liste droulante.
Le modle [ViewModel05] sera le suivant :
1. namespace Exemple_03.Models
2. {
3.
public class ViewModel05
4.
{
5.
public Personne2[] Personnes { get; set; }
6.
public int SelectedId { get; set; }
7.
8.
public ViewModel05()
9.
{
10.
Personnes = new Personne2[] {
11.
new Personne2 { Id = 1, Prnom = "Pierre", Nom = "Martino" },
12.
new Personne2 { Id = 2, Prnom = "Pauline", Nom = "Pereiro" },
13.
new Personne2 { Id = 3, Prnom = "Jacques", Nom = "Alfonso" } };
14.
SelectedId = 2;
15.
}
16. }
17.
18. public class Personne2
19. {
20.
public int Id { get; set; }
21.
public string Nom { get; set; }
22.
public string Prnom { get; set; }
23. }
24. }
http://tahe.developpez.com
109/362
Les caractristiques de la liste droulante HTML ont t prsentes au paragraphe 2.5.2.6, page 36. Rappelons-les :
Exemple HTML
Affichage
http://tahe.developpez.com
110/362
attributs
<option [selected="selected"]>...</option>
...
</select>
affiche dans une liste les textes compris entre les balises <option>...</option>
name="cmbValeurs" : nom du contrle.
size="1" : nombre d'lments de liste visibles. size="1" fait de la liste l'quivalent d'un combobox.
selected="selected" : si ce mot cl est prsent pour un lment de liste, ce dernier apparat slectionn dans la
liste. Dans notre exemple ci-dessus, l'lment de liste choix2 apparat comme l'lment slectionn du combo
lorsque celui-ci est affich pour la premire fois.
Le code des lignes 17-25 gnre les balises <option> qui prennent place dans la balise <select> de la ligne 16.
1
2
lignes 10-12 : les trois balises <option> gnres par le code [Razor] ;
ligne 11 : c'est bien la personne d'[Id]=2 qui a t slectionne.
Les deux exemples ci-dessus nous suffiront. Lorsqu'on crit une vue [Razor], il faut rsister la tentation de mettre de la logique
dedans. Le code C# nous le permettrait. Cependant, dans le modle MVC, la logique doit tre dans l'action ou dans les couches
basses [Metier, DAO] mais pas dans la vue. Mme en respectant le modle MVC, on peut se retrouver avec beaucoup de logique
dans la vue pour calculer des valeurs intermdiaires. Cela peut vouloir signifier que le modle utilis n'est pas assez dtaill. Celui-ci
doit contenir les valeurs finales dont la vue a besoin afin qu'elle n'ait pas les calculer elle-mme. Une bonne vue est une vue o il y
a un minimum de logique et o la structure HTML de la vue reste claire. Si on insre trop de code C#, la structure HTML peut
devenir illisible.
http://tahe.developpez.com
111/362
Dans l'exemple ci-dessus, la liste droulante pourrait tre utilise par un internaute et on voudrait alors savoir quelle personne il a
slectionne. Il nous faut pour cela un formulaire.
5.5
Le modle de la vue sera le modle [ViewModel05] dj utilis prcdemment. L'action qui affichera cette vue sera la suivante :
1.
2.
3.
4.
5.
6. }
// Action06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
ligne 2 : l'action ne peut tre demande que par une commande HTTP GET ;
ligne 5 : la vue [/First/Action06Get.cshtml] sera affiche avec pour modle une instance de type [ViewModel05].
1. @model Exemple_03.Models.ViewModel05
2. @using Exemple_03.Models
3.
4. @{
5.
Layout = null;
6. }
7.
8. <!DOCTYPE html>
9.
10. <html>
11. <head>
12. <meta name="viewport" content="width=device-width" />
13. <title>Action06-GET</title>
14. </head>
15. <body>
http://tahe.developpez.com
112/362
ligne 18 : pour que le navigateur puisse transmettre les informations saisies par un utilisateur, il nous faut un formulaire.
C'est la balise <form> des lignes 18 et 31 qui le dlimitent.
La balise HTML <form> a t prsente au paragraphe 2.5.2.1, page 34. Rappelons ses caractristiques :
formulaire
ligne 18 : on voit que les valeurs du formulaire seront envoyes l'URL [/First/Action06] par une commande HTTP
POST ;
ligne 30 : un formulaire doit avoir un bouton de type [submit]. C'est lui qui dclenche l'envoi des valeurs saisies l'URL
prcise par l'attribut [action] de la balise <form>.
Que va au juste transmettre le navigateur lorsque l'internaute va cliquer sur le bouton [Valider] ? Ceci a t expliqu au paragraphe
2.5.3.1, page 39. Rappelons ce qui avait t dit :
contrle HTML
<input type="radio" value="Oui"
name="R1"/>Oui
<input type="radio" name="R1"
value="non" checked="checked"/>Non
http://tahe.developpez.com
visuel
valeur(s) envoye(s)
R1=Oui
- la valeur de l'attribut value du bouton
radio coch par l'utilisateur.
113/362
C1=un
C2=deux
- valeurs des attributs value des cases
coches par l'utilisateur
txtSaisie=programmation+Web
txtMdp=ceciestsecret
areaSaisie=les+bases+de+la%0D%0A
ligne1
programmation+Web
</textarea>
cmbValeurs=choix3
<option>choix1</option>
ligne2
ligne3
<option
selected="selected">choix2</option>
<option>choix3</option>
</select>
<select size="3" name="lst1">
<option
selected="selected">liste1</option>
<option>liste2</option>
lst1=liste3
- valeur choisie par l'utilisateur dans la liste
slection unique
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
<select size="3" name="lst2" multiple>
lst2=liste1
<option
selected="selected">liste1</option>
lst2=liste3
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
<input type="submit" value="Envoyer"
name="cmdRenvoyer"/>
cmdRenvoyer=Envoyer
secret=uneValeur
http://tahe.developpez.com
114/362
Dans notre formulaire, nous avons deux balises susceptibles d'envoyer une valeur :
1.
<select name="personneId">
2. ...
3. </select>
et
1. <input name="valider" type="submit" value="Valider" />
Les noms des paramtres sont ceux des attributs [name] des balises concernes par le POST. Sans cet attribut, les balises n'mettent
pas de valeur. Ainsi ci-dessus, on pourrait omettre l'attribut name="valider" du bouton [submit]. La valeur envoye est l'attribut
[value] du bouton. Ici cette information ne nous intresse pas. Parfois les formulaires ont plusieurs boutons de type [submit]. Il est
alors important de savoir quel bouton a t cliqu. On mettra alors l'attribut [name] aux diffrents boutons.
La balise <select> est compose d'une suite de balises <option> :
1.
<select name="personneId">
2.
<option value="1" >Pierre Martino</option>
3.
<option value="2" selected="selected">Pauline Pereiro</option>
4.
<option value="3" >Jacques Alfonso</option>
5. </select>
C'est la valeur de l'attribut [value] de l'option slectionne qui est poste. En d'absence de cet attribut, c'est le texte affich par
l'option, par exemple [Pierre Martino], qui est post.
La chane
personneId=2&valider=Valider
// Action06-POST
[HttpPost]
public ViewResult Action06(ActionModel06 modle)
{
return View("Action06Post",modle);
// Action06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
Il est possible d'avoir deux actions de mme nom condition qu'elle ne traite pas les mmes commandes HTTP :
Il nous faut un modle d'action pour encapsuler ces valeurs. Ce sera le modle [ActionModel06] suivant :
http://tahe.developpez.com
115/362
1. using System.ComponentModel.DataAnnotations;
2. namespace Exemple_03.Models
3. {
4.
public class ActionModel06
5.
{
6.
[Required(ErrorMessage = "Le paramtre [personneId] est requis")]
7.
public int PersonneId { get; set; }
8.
9.
[Required(ErrorMessage = "Le paramtre [valider] est requis")]
10.
public string Valider { get; set; }
11. }
12. }
L'action [Action06] reoit ce modle et le transmet tel quel la vue [Action06Post] (ligne 5 de l'action) suivante :
1. @model Exemple_03.Models.ActionModel06
2.
3. @{
4.
Layout = null;
5. }
6.
7. <!DOCTYPE html>
8.
9. <html>
10. <head>
11. <meta name="viewport" content="width=device-width" />
12. <title>Action06Post</title>
13. </head>
14. <body>
15. <h3>Action06 - POST</h3>
16. Valeurs postes :
17. <ul>
18.
<li>ID de la personne slectionne : @Model.PersonneId</li>
19.
<li>Commande utilise : @Model.Valider</li>
20. </ul>
21. </body>
22. </html>
http://tahe.developpez.com
116/362
3
1
En [1] on slectionne la troisime personne d'[Id] gal 3. En [2] on poste le formulaire. En [3], les valeurs reues. En [4,5], on voit
que la mme URL a t appele, l'une par un GET [4], l'autre par un POST [5]. Ceci ne se voit pas dans l'URL.
Dans la vue affiche la suite du POST, on pourrait vouloir les nom et prnom de la personne slectionne plutt que son numro. Il
faut alors faire voluer la vue du POST et son modle.
Nous crons une action [Action07] pour traiter ce cas. Cette action va devoir utiliser la session de l'utilisateur pour y stocker la liste
des personnes. Nous allons suivre le modle tudi au paragraphe 4.10, page 88 qui permet d'inclure les donnes de porte
[Application] et [Session] dans le modle de l'action.
Le modle de la session sera la classe [SessionModel] suivante :
1. namespace Exemple_03.Models
2. {
3.
public class SessionModel
4.
{
5.
public Personne2[] Personnes { get; set; }
6.
}
7. }
ligne 2 : la session mmorisera la liste des personnes affiches dans la liste droulante ;
Il nous faut lier le type prcdent [SessionModel] un binder que nous appellerons [SessionModelBinder]. Celui-ci sera le mme
que celui dcrit page 93 :
1. using System.Web.Mvc;
2.
3. namespace Exemple_03.Infrastructure
4. {
5.
public class SessionModelBinder : IModelBinder
http://tahe.developpez.com
117/362
6.
7.
La liaison entre le modle [SessionModel] et son binder [SessionModelBinder] se fait dans [Global.asax] :
1. public class MvcApplication : System.Web.HttpApplication
2.
{
3.
protected void Application_Start()
4.
{
5.
...
6.
7.
// model binders
8.
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
9.
}
10.
// Session
11.
public void Session_Start()
12.
{
13.
Session["data"] = new SessionModel();
14.
}
15. }
// Action07-GET
[HttpGet]
public ViewResult Action07(SessionModel session)
{
ViewModel05 modleVue = new ViewModel05();
session.Personnes= modleVue.Personnes;
return View("Action07Get", modleVue);
ligne 3 : l'action rcupre un type [SessionModel], donc la donne de porte [Session] associe la cl [data] ;
ligne 5 : on cre le modle de la vue ;
ligne 6 : on met dans la session le tableau des personnes. On en aura besoin dans la requte suivante, celle du POST. Le
protocole HTTP est un protocole sans tat. Il faut utiliser une session pour avoir de la mmoire entre les requtes. Une
session est propre un utilisateur et est gre par le serveur web ;
ligne 7 : la vue [Action07Get.cshtml] est affiche. C'est la suivante :
1. @model Exemple_03.Models.ViewModel05
2. @using Exemple_03.Models
3. ...
4. <body>
5.
<h3>Action07 - GET</h3>
6.
<p>Choisissez une personne</p>
7.
<form method="post" action="/First/Action07">
8. ....
9.
</form>
10. </body>
11. </html>
Elle est identique la vue [Action06Get.cshtml] dj tudie. La principale diffrence est ligne 7 : l'URL laquelle seront postes les
valeurs du formulaire. Celles-ci seront traites par l'action [Action07] suivante :
http://tahe.developpez.com
118/362
1.
2.
3.
4.
5.
// Action07-POST
[HttpPost]
public ViewResult Action07(SessionModel session, ActionModel06 modle)
{
Personne2 personne = session.Personnes.Where(p => p.Id ==
modle.PersonneId).First<Personne2>();
6.
string strPersonne = string.Format("{0} {1}", personne.Prnom, personne.Nom);
7.
return View("Action07Post", (object)strPersonne);
8. }
ligne 3 : les valeurs postes sont encapsules dans le modle d'action [ActionModel06] dj utilis prcdemment :
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel06
{
[Required(ErrorMessage = "Le paramtre [personneId] est requis")]
public int PersonneId { get; set; }
[Required(ErrorMessage = "Le paramtre [valider] est requis")]
public string Valider { get; set; }
}
http://tahe.developpez.com
119/362
5.6
Nous avons tudi au paragraphe 2.5.2.1, page 34, le formulaire HTML suivant :
Nous allons tudier une action [Action08Get] qui affiche (GET) ce formulaire et une action [Action08Post] qui traite (POST) les
valeurs saisies par l'utilisateur. Un schma classique.
Le modle de la vue [1] ci-dessus sera une instance de la classe [ViewModel08]. Cette classe sera la fois :
http://tahe.developpez.com
120/362
Requte
POST
GET
5.6.1
Liaison
Modle
Modle
d'action
d'action
[ViewModel08]
Action08 POST
GET
Modle
de Vue
[ViewModel08]
Vue1
Vue2
Rponse
Nous allons supposer que les lments affichs par les boutons radio, les cases cocher et les diffrentes listes sont des donnes de
porte [Application]. Un cas frquent. Ces informations proviennent d'un fichier de configuration ou d'une base de donnes
exploits au dmarrage de l'application dans la mthode [Application_Start] de [Global.asax]. Cette mthode volue de la faon
suivante :
1.
protected void Application_Start()
2.
{
3. ....
4.
5.
// model binders
6.
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
7.
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
8.
9.
// donnes de porte [Application]
10.
Application["data"] = new ApplicationModel();
11. }
ligne 7 : le type [ApplicationModel] que nous allons dcrire bientt est associ au lieur de donnes
[ApplicationModelBinder] que nous avons dj prsent page 93 ;
ligne 10 : une instance de type [ApplicationModel] est enregistre dans le dictionnaire de l'application, associe la cl
[data].
La classe [ApplicationModel] sert encapsuler toutes les donnes de porte [Application]. Ici elle encapsulera les donnes que doit
afficher le formulaire :
1. namespace Exemple_03.Models
2. {
3.
public class ApplicationModel
4.
{
5.
// les collections afficher dans le formulaire
6.
public Item[] RadioButtonFieldItems { get; set; }
7.
public Item[] CheckBoxesFieldItems { get; set; }
8.
public Item[] DropDownListFieldItems { get; set; }
9.
public Item[] SimpleChoiceListFieldItems { get; set; }
10.
public Item[] MultipleChoiceListFieldItems { get; set; }
11.
12.
// initialisation des champs et collections
13.
public ApplicationModel()
14.
{
15.
RadioButtonFieldItems = new Item[]{
http://tahe.developpez.com
121/362
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51. }
52. }
lignes 45-49 : l'lment des diffrentes collections du formulaire. [Label] est le texte affich par l'lment du formulaire,
[Value] la valeur poste par cet lment lorsqu'il est slectionn ;
ligne 6 : la collection affiche par le bouton radio ;
ligne 7 : la collection affiche par les cases cocher ;
ligne 8 : la collection affiche par la liste droulante ;
ligne 9 : la collection affiche par la liste slection unique ;
ligne 10 : la collection affiche par la liste slection multiple ;
lignes 13-43 : ces collections sont intialises par le constructeur sans paramtres de la classe.
http://tahe.developpez.com
122/362
RadioFieldItems
CheckBoxesFieldItems
DropdownListFieldItems
SimpleChoiceListFieldItems
MultipleChoiceListFieldItems
5.6.2
// Action08-GET
[HttpGet]
public ViewResult Action08Get(ApplicationModel application)
{
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
6.
return View("Formulaire", new ViewModel08(application));
7. }
5.6.3
http://tahe.developpez.com
123/362
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49. }
50. }
// constructeurs
public ViewModel08()
{
}
public ViewModel08(ApplicationModel application)
{
// initialisation collections
RadioButtonFieldItems = application.RadioButtonFieldItems;
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
DropDownListFieldItems = application.DropDownListFieldItems;
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
// initialisation champs
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
}
dans un formulaire, il y a deux types d'lments : ceux qu'on affiche et ceux qui font l'objet d'une saisie ;
les lignes 20-24 dfinissent les lments afficher. Ce sont les diffrentes collections du formulaire. Celles-ci sont trouves
dans le modle de l'application (lignes 34-38) ;
lignes 10-17 : dfinissent les champs de saisie du formulaire ;
ligne 10 : [RadioButtonField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
<!-- les boutons radio -->
2.
<tr>
3.
<td>Etes-vous mari(e)</td>
4.
<td>
5. <input type="radio" name="RadioButtonField" value="1" />oui
6. <input type="radio" name="RadioButtonField" value="2" checked="checked"/>non
7.
</td>
8. </tr>
On notera lignes 5 et 6 que l'attribut [name] des deux boutons radio est le nom de la proprit qui va tre
initialise. Dans les donnes postes, on trouvera une chane de la forme :
param1=val1&RadioButtonField=2¶m2=val2
si l'utilisateur a coch l'option libelle [non]. C'est en effet l'attribut [value] de l'option coche qui est post.
ligne 11 : [CheckBoxesField] rcuprera les valeurs postes par les lignes suivantes du formulaire :
1.
<!-- les cases cocher -->
2.
<tr>
3.
<td>Cases cocher</td>
4.
<td>
5. <input type="checkbox" name="CheckBoxesField" value="1" />1
6. <input type="checkbox" name="CheckBoxesField" value="2" checked="checked"/>2
7. <input type="checkbox" name="CheckBoxesField" value="3" />3
http://tahe.developpez.com
124/362
8. </td>
On notera lignes 5 et 6 que l'attribut [name] des cases cocher est le nom de la proprit qui va tre initialise.
Dans les donnes postes, on trouvera une chane de la forme :
param1=val1&CheckBoxesField=2&CheckBoxesField=3¶m2=val2
si l'utilisateur a coch les cases cocher libelles [2] et [3]. C'est l'attribut [value] des options coches qui est
post. C'est parce que plusieurs paramtres de mme nom peuvent tre posts que [CheckBoxesField] est un
tableau de valeurs et non une valeur simple. Si aucune case n'est coche, le paramtre [CheckBoxesField] sera
absent de la chane poste et la proprit de mme nom du modle ne sera pas initialise. Ce peut tre gnant
comme nous le verrons.
ligne 12 : [TextField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
2.
3.
4.
5.
6.
7. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. Dans les donnes
postes, on trouvera une chane de la forme :
param1=val1&TextField=abcdef¶m2=val2
ligne 13 : [PasswordField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
2.
3.
4.
5.
6.
7. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. Dans les donnes
postes, on trouvera une chane de la forme :
param1=val1&PasswordField=abcdef¶m2=val2
ligne 14 : [TextAreaField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
<!-- le champ de saisie texte multilignes -->
2.
<tr>
3.
<td>Bote de saisie</td>
4.
<td>
5.
<textarea name="TextAreaField" cols="40" rows="3">ligne1
6. ligne2</textarea>
7.
</td>
8. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. Dans les donnes
postes, on trouvera une chane de la forme :
param1=val1&TextAreaField=abcdef%0D%OAhijk¶m2=val2
si l'utilisateur a saisi [abcdef] suivi d'un saut de ligne et de [ijk] dans le champ de saisie.
ligne 15 : [DropDownListField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
2.
3.
4.
http://tahe.developpez.com
125/362
5.
<select name="DropDownListField">
6. <option value="1" >choix1</option>
7. <option value="2" selected="selected">choix2</option>
8. <option value="3" >choix3</option>
9.
</select>
10. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. Dans les donnes
postes, on trouvera une chane de la forme :
param1=val1&DropDownListField=1¶m2=val2
si l'utilisateur a slectionn l'option [choix1]. C'est l'attribut [value] de l'option slectionne qui est poste.
ligne 16 : [SingleChoiceListField] rcuprera la valeur poste par les lignes suivantes du formulaire :
1.
<!-- la liste choix unique -->
2.
<tr>
3.
<td>Liste choix unique</td>
4.
<td>
5.
<select name="SimpleChoiceListField" size="3">
6. <option value="1" >liste1</option>
7. <option value="2" >liste2</option>
8. <option value="3" selected="selected">liste3</option>
9. <option value="4" >liste4</option>
10. <option value="5" >liste5</option>
11.
12.
</select>
13. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. C'est l'attribut
[size="3"] qui fait qu'on n'a pas une liste droulante. Dans les donnes postes, on trouvera une chane de la
forme :
param1=val1&SimpleChoiceListField=3¶m2=val2
si l'utilisateur a slectionn l'option [liste3]. C'est l'attribut [value] de l'option slectionne qui est poste. Le
paramtre [SingleChoiceListField] peut tre absent de la chane poste si aucun lment n'a t slectionn.
ligne 17 : [MultipleChoiceListField] rcuprera les valeurs postes par les lignes suivantes du formulaire :
1.
<!-- la liste choix multiple -->
2.
<tr>
3.
<td>Liste choix multiple</td>
4.
<td>
5.
<select name="MultipleChoiceListField" size="3" multiple="multiple">
6. <option value="1" selected="selected">liste1</option>
7. <option value="2" >liste2</option>
8. <option value="3" selected="selected">liste3</option>
9. <option value="4" >liste4</option>
10. <option value="5" >liste5</option>
11.
</select>
12. </tr>
Ligne 5, l'attribut [name] du champ de saisie est le nom de la proprit qui va tre initialise. C'est l'attribut
[size="3"] qui fait qu'on n'a pas une liste droulante et l'attribut [multiple] qui fait que l'utilisateur peut
slectionner plusieurs lments en maintenant appuye la touche [Ctrl]. Dans les donnes postes, on trouvera
une chane de la forme :
param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3¶m2=val2
si l'utilisateur a slectionn les options [liste1] et [liste3]. C'est l'attribut [value] des options slectionnes qui est
poste. C'est parce que plusieurs paramtres de mme nom peuvent tre posts que [MultipleChoiceListField]
est un tableau de valeurs et non une valeur simple. Si aucune case n'est coche, le paramtre
[MultipleChoiceListField] sera absent de la chane poste et la proprit de mme nom du modle ne sera pas
initialise.
Les diffrents champs de saisie prsents prcdemment vont recevoir les valeurs postes par le formulaire. On peut galement les
initialiser avant d'envoyer le formulaire. C'est ce qui a t fait ici :
http://tahe.developpez.com
126/362
1.
2.
3.
4.
5.
6.
7.
8.
9.
// initialisation champs
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
Si ces valeurs avaient t obtenues aprs un POST du formulaire, cela signifierait que l'utilisateur a :
Nous allons faire comme si un POST avait eu lieu et que l'on voulait renvoyer le formulaire tel qu'il a t saisi. C'est notamment ce
qui est fait lorsqu'on renvoie l'utilisateur un formulaire erron. Celui-ci est renvoy tel qu'il a t saisi.
5.6.4
La vue [Formulaire]
1. @model Exemple_03.Models.ViewModel08
2. @using Exemple_03.Models
3. @{
4.
Layout = null;
5. }
6. <html>
7. <head>
8.
<meta name="viewport" content="width=device-width" />
9.
<title>Formulaire</title>
10. </head>
11. <body>
12. <form method="post" action="Action08Post">
13.
<h2>Formulaire ASP.NET MVC</h2>
14.
<h3>Affich par : @ViewBag.info</h3>
15.
<table>
16.
<thead></thead>
17.
<tbody>
18.
<!-- les boutons radio -->
19.
<tr>
20.
<td>Etes-vous mari(e)</td>
http://tahe.developpez.com
127/362
21.
22.
23.
24.
<td>
@foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
{
string strChecked = item.Value == @Model.RadioButtonField ?
"checked=\"checked\"" : "";
25.
<input type="radio" name="RadioButtonField" value="@item.Value"
@strChecked/>@item.Label
26.
<text/>
27.
}
28.
</td>
29.
</tr>
30. ...
31.
</tbody>
32.
</table>
33.
<input type="submit" value="Valider" />
34. </form>
35. </body>
36. </html>
http://tahe.developpez.com
128/362
http://tahe.developpez.com
129/362
14.
</tr>
5.6.5
// Action08-POST
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel08 modle = new ViewModel08(application);
TryUpdateModel(modle,posted);
return View("Formulaire", modle);
}
ligne 3 : le modle de l'application est en paramtre ainsi que les valeurs postes. Celles-ci sont disponibles dans un type
[FormCollection]. La valeur du paramtre [RadioButtonField] post est obtenue par l'expression posted["
RadioButtonField"]. On obtient l une chane de caractres ou le pointeur null. Si on crit
posted["
CheckBoxesField"], on aura un tableau de chane de caractres ou le pointeur null ;
pourquoi ne pas crire :
public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)
Il y a deux raisons.
la premire est que le framework va instancier le modle [ViewModel08] avec le constructeur sans paramtres, ce
qui aura pour effet de ne pas initialiser les collections du modle ;
le seconde est qu'on veut contrler ce qui va dans le modle. On sait qu'il y a quatre sources possibles pour le
modle, les paramtres d'un GET, d'un POST, de la route utilise et ceux d'un fichier upload. Ici, on veut
initialiser le modle uniquement avec les valeurs postes.
ligne 6 : on instancie le modle en utilisant le bon constructeur ;
http://tahe.developpez.com
130/362
ligne 7 : on l'initialise avec les valeurs postes. Aprs cette opration, le modle correspond la saisie qui a t faite par
l'utilisateur ;
ligne 8 : on affiche de nouveau le formulaire. L'utilisateur va le retrouver tel qu'il a t saisi.
Voyons un exemple :
5.6.6
Nous avons dit que si aucune valeur n'tait coche ou slectionne pour les champs [CheckBoxesField, SimpleChoiceListField,
MultipleChoiceListField] alors les paramtres correspondants ne faisaient pas pas partie de la chane poste et que donc les
proprits de mme nom du modle n'taient pas initialises.
Voyons l'exemple suivant :
1
2
http://tahe.developpez.com
131/362
parce qu'il n'y a aucune case coche, le paramtre [CheckBoxesField] ne fait pas partie des valeurs postes ;
l'action [Action08Post] procde de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8. }
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = ...
ViewModel08 modle = new ViewModel08(application);
TryUpdateModel(modle,posted);
return View("Formulaire", modle);
ligne 5 : le modle du formulaire est instanci. Or le constructeur utilis affecte le tableau ["2"] la proprit
[CheckBoxesField] ;
ligne 6 : les valeurs postes sont enregistres dans le modle. Comme le paramtre [CheckBoxesField] ne fait pas partie
des valeurs postes, la proprit de mme nom n'est pas affecte. Elle reste donc avec sa valeur ["2"], ce qui fait qu'
l'affichage la case n 2 est coche alors qu'elle ne devrait pas l'tre.
Ce problme peut se rgler de diverses faons. Nous choisissons de le rgler dans le code de l'action [Action08Post] :
1. // Action08-POST
2.
[HttpPost]
3.
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
4.
{
5.
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
6.
ViewModel08 modle = new ViewModel08(application);
7.
TryUpdateModel(modle,posted);
8.
// traitement des valeurs non postes
9.
if (posted["CheckBoxesField"] == null)
10.
{
11.
modle.CheckBoxesField = new string[] { };
12.
}
13.
if (posted["SimpleChoiceListField"] == null)
14.
{
15.
modle.SimpleChoiceListField = "";
16.
}
17.
if (posted["MultipleChoiceListField"] == null)
18.
{
19.
modle.MultipleChoiceListField = new string[] { };
20.
}
21.
// affichage formulaire
22.
return View("Formulaire", modle);
23.
}
lignes 9-20 : on vrifie si certains paramtres ont t posts ou non. Si ce n'est pas le cas, on les initialise avec la valeur qui
correspond la saisie faite par l'utilisateur. Le test n'a pas t fait pour la liste droulante qui elle, a toujours un lment
slectionn ce qui n'est pas le cas pour les autres listes.
5.7
5.7.1
Nous crons un nouveau formulaire [Formulaire2.cshtml] qui dlivrera un formulaire identique au prcdent :
http://tahe.developpez.com
132/362
le plus important est qu'on perd de vue la nature du composant, ici une liste droulante, cause de la complexit du code ;
ligne 5 : si on se trompe dans le nom de la proprit du modle utiliser comme attribut [name], on ne s'en apercevra qu'
l'excution.
ASP.NET MVC offre des mthodes spcialises appeles [HTML Helpers] qui, comme leur nom l'indique, vise faciliter la
gnration du HTML, notamment pour les formulaires. Avec ces classes, la liste droulante prcdente s'crit comme suit :
1.
2.
3.
4.
5.
6.
7. </tr>
La liste droulante est gnre par les lignes 4-5. Le code est nettement moins complexe. Le code HTML gnr pour la liste
droulante est le suivant :
1.
<!-- la liste droulante -->
2.
<tr>
3.
<td>Liste droulante</td>
4.
<td><select id="DropDownListField" name="DropDownListField"><option
value="1">choix1</option>
5. <option selected="selected" value="2">choix2</option>
6. <option value="3">choix3</option>
7. </select></td>
8. </tr>
http://tahe.developpez.com
133/362
le premier paramtre est une fonction lambda (c'est son nom) o m reprsente le modle de la vue et
m.DropDowListField est une proprit de ce modle. Le gnrateur de code HTML va utiliser le nom de cette proprit
pour gnrer les attributs [id] et [name] du [select] qui va tre gnr. Si on utilise une proprit inexistante, on aura une
erreur la compilation et non plus l'excution. C'est une amlioration vis vis de la solution prcdente o les erreurs de
nommage n'taient dtectes qu' l'excution ;
le second paramtre sert dsigner la collection d'lments qui va alimenter la liste droulante. La classe [SelectList]
permet de construire cette collection :
son premier paramtre est une collection quelconque d'lments. Ici on a une collection de type [Item] ;
son second paramtre est la proprit des lments qui fournira la valeur de la balise <option>. Ici, c'est
la proprit [Value] de la classe [Item] ;
son troisime paramtre est la proprit des lments qui fournira le libell de la balise <option>. Ici,
c'est la proprit [Label] de la classe [Item] ;
pour savoir quelle option doit tre slectionne (attribut selected), le framework fait comme nous : il compare la valeur de
l'option la valeur actuelle de la proprit [DropDownListField].
le premier paramtre est la proprit du modle qui va tre associe au bouton radio (attribut [name]) ;
le second paramtre est la valeur attribuer au bouton radio (attribut [value]).
Cases cocher
Le code volue de la faon suivante :
1.
http://tahe.developpez.com
134/362
2.
3.
4.
5.
6.
7.
8.
9.
10. </td>
<tr>
<td>Cases cocher</td>
<td>
@{
@Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
@Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
@Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
}
La paramtre est la proprit boolenne du modle qui sera associe la case cocher. Si [Proprit=true], la case sera coche. Si
[Proprit=false], la case ne sera pas coche. Dans tous les cas, l'attribut [value] vaut true. Le code HTML gnr est le suivant :
1. <input id="Proprit" name="Proprit" type="checkbox" value="true" />
2. <input name="Proprit" type="hidden" value="false" />
le premier paramtre prcise la proprit du modle associ au champ de saisie. Le nom de la proprit sera utilis dans les
attributs [name] et [id] de la balise <input> gnre et sa valeur sera affecte l'attribut [value] ;
le second paramtre est une classe anonyme prcisant certains attributs de la balise HTML gnre, ici l'attribut [size].
http://tahe.developpez.com
135/362
1.
2.
3.
4.
5.
6.
7. </tr>
"5" })
6.
7. </tr>
http://tahe.developpez.com
136/362
4.
<td>
5.
@Html.DropDownListFor(m => m.SimpleChoiceListField, new
SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
6. </tr>
Nous avons dj tudi la mthode [Html.DropDownListFor]. La seule diffrence est ici le troisime paramtre qui sert prciser
un attribut [size] diffrent de 1. C'est cette caractristique qui fait passer d'une liste droulante [size=1] une liste simple.
La liste choix multiple
Le nouveau code est le suivant :
1.
<!-- la liste choix multiple -->
2.
<tr>
3.
<td>Liste choix multiple</td>
4.
<td>
5.
@Html.ListBoxFor(m => m.MultipleChoiceListField, new
SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
6. </tr>
La mthode
@Html.ListBoxFor(m => m.MultipleChoiceListField, new
SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
fonctionne comme la mthode [Html.DropDownListFor] si ce n'est qu'elle gnre une liste slection multiple. Les options
slectionnes sont celles qui ont leur valeur (attribut value) dans le tableau [MultipleChoiceListField].
La balise <form> peut tre elle aussi, gnre avec une mthode :
1.
2.
http://tahe.developpez.com
137/362
3. ...
4. }
La mthode
Html.BeginForm("Action09Post", "First")
a pour premier paramtre le nom d'une action et pour second paramtre le nom d'un contrleur.
5.7.2
http://tahe.developpez.com
138/362
34.
{
35.
// initialisation collections
36.
RadioButtonFieldItems = application.RadioButtonFieldItems;
37.
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
38.
DropDownListFieldItems = application.DropDownListFieldItems;
39.
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
40.
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
41.
// initialisation champs
42.
RadioButtonField = "2";
43.
CheckBoxField2 = true;
44.
TextField = "quelques mots";
45.
PasswordField = "secret";
46.
TextAreaField = "ligne1\nligne2";
47.
DropDownListField = "2";
48.
SimpleChoiceListField = "3";
49.
MultipleChoiceListField = new string[] { "1", "3" };
50.
}
51. }
52. }
[ViewModel09] diffre de [ViewModel08] par sa gestion des cases cocher. Plutt que d'avoir un tableau de trois cases cocher, on
a utilis trois cases cocher spares (lignes 11-13).
Le formulaire sera trait par l'action [Action09Post] suivante :
1.
2.
3.
4.
5.
// Action09-POST
[HttpPost]
public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
6.
ViewModel09 modle = new ViewModel09(application);
7.
TryUpdateModel(modle, posted);
8.
// traitement des valeurs non postes
9.
if (posted["SimpleChoiceListField"] == null)
10.
{
11.
modle.SimpleChoiceListField = "";
12.
}
13.
if (posted["MultipleChoiceListField"] == null)
14.
{
15.
modle.MultipleChoiceListField = new string[] { };
16.
}
17.
// affichage formulaire
18.
return View("Formulaire2", modle);
19. }
L'action [Action09Post] est identique l'action [Action08Post] sauf sur deux points :
on n'a plus la gestion des cases cocher non coches. C'est dsormais gr correctement par la mthode
[Html.CheckBoxFor].
5.8
Il existe d'autres mthodes que les prcdentes pour gnrer un formulaire. L'une d'elles consiste associer des informations une
rubrique du modle grce auxquelles le framework MVC sait quelle balise de saisie il doit gnrer. On appelle ces informations des
mtadonnes.
Considrons le modle de vue [ViewModel10] suivant :
http://tahe.developpez.com
139/362
1. using System;
2. using System.ComponentModel.DataAnnotations;
3. using System.Drawing;
4.
5. namespace Exemple_03.Models
6. {
7.
public class ViewModel10
8.
{
9.
[Display(Name="Text")]
10.
[DataType(DataType.Text)]
11.
public string Text { get; set; }
12.
[Display(Name = "TextArea")]
13.
[DataType(DataType.MultilineText)]
14.
public string MultiLineText { get; set; }
15.
[Display(Name = "Number")]
16.
public int Number { get; set; }
17.
[Display(Name = "Decimal")]
18.
[UIHint("Decimal")]
19.
public double Decimal { get; set; }
20.
[Display(Name = "Tel")]
21.
[DataType(DataType.PhoneNumber)]
22.
public string Tel { get; set; }
23.
[Display(Name = "Date")]
24.
[DataType(DataType.Date)]
25.
public DateTime Date { get; set; }
26.
[Display(Name = "Time")]
27.
[DataType(DataType.Time)]
28.
public DateTime Time { get; set; }
29.
[Display(Name = "HiddenInput")]
30.
[UIHint("HiddenInput")]
31.
public string HiddenInput { get; set; }
32.
[Display(Name = "Boolean")]
33.
[UIHint("Boolean")]
34.
public bool Boolean { get; set; }
35.
[Display(Name = "Email")]
36.
[DataType(DataType.EmailAddress)]
37.
public string Email{ get; set; }
38.
[Display(Name = "Url")]
39.
[DataType(DataType.Url)]
40.
public string Url { get; set; }
41.
[Display(Name = "Password")]
42.
[DataType(DataType.Password)]
43.
public string Password { get; set; }
44.
[Display(Name = "Currency")]
45.
[DataType(DataType.Currency)]
46.
public double Currency { get; set; }
47.
[Display(Name = "CreditCard")]
48.
[DataType(DataType.CreditCard)]
49.
public string CreditCard { get; set; }
50.
http://tahe.developpez.com
140/362
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69. }
70. }
// constructeur
public ViewModel10()
{
Text = "tra la la";
MultiLineText = "ligne1\nligne2";
Number = 4;
Decimal = 10.2;
Tel = "0617181920";
Date = DateTime.Now;
Time = DateTime.Now;
HiddenInput = "cach";
Boolean = true;
Email = "x@y.z";
Url = "http://istia.univ-angers.fr";
Password = "mdp";
Currency = 4.2;
CreditCard = "0123456789012345";
}
// Action10-GET
[HttpGet]
public ViewResult Action10Get()
{
return View(new ViewModel10());
Ci-dessus ligne 5, on demande la vue par dfaut de l'action [/First/Action10Get.cshtml ] d'afficher le modle de vue de type
[ViewModel10]. Cette vue est la suivante :
1. @model Exemple_03.Models.ViewModel10
2.
3. @{
4.
Layout = null;
5. }
6.
7. <!DOCTYPE html>
8.
9. <html>
10. <head>
11. <meta name="viewport" content="width=device-width" />
12. <title>Action10Get</title>
http://tahe.developpez.com
141/362
13. </head>
14. <body>
15. <h3>Formulaire ASP.NET MVC - 2</h3>
16. @using (Html.BeginForm("Action10Post", "First"))
17. {
18.
<table>
19.
<thead>
20.
<tr>
21.
<th>LabelFor</th>
22.
<th>EditorFor</th>
23.
<th>DisplayFor</th>
24.
</tr>
25.
</thead>
26.
<tbody>
27.
<tr>
28.
<td>@Html.LabelFor(m => m.Text)</td>
29.
<td>@Html.EditorFor(m => m.Text)</td>
30.
<td>@Html.DisplayFor(m => m.Text)</td>
31.
</tr>
32.
<tr>
33.
<td>@Html.LabelFor(m => m.MultiLineText)</td>
34.
<td>@Html.EditorFor(m => m.MultiLineText)</td>
35.
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
36.
</tr>
37.
<tr>
38.
<td>@Html.LabelFor(m => m.Number)</td>
39.
<td>@Html.EditorFor(m => m.Number)</td>
40.
<td>@Html.DisplayFor(m => m.Number)</td>
41.
</tr>
42.
<tr>
43.
<td>@Html.LabelFor(m => m.Decimal)</td>
44.
<td>@Html.EditorFor(m => m.Decimal)</td>
45.
<td>@Html.DisplayFor(m => m.Decimal)</td>
46.
</tr>
47.
<tr>
48.
<td>@Html.LabelFor(m => m.Tel)</td>
49.
<td>@Html.EditorFor(m => m.Tel)</td>
50.
<td>@Html.DisplayFor(m => m.Tel)</td>
51.
</tr>
52.
<tr>
53.
<td>@Html.LabelFor(m => m.Date)</td>
54.
<td>@Html.EditorFor(m => m.Date)</td>
55.
<td>@Html.DisplayFor(m => m.Date)</td>
56.
</tr>
57.
<tr>
58.
<td>@Html.LabelFor(m => m.Time)</td>
59.
<td>@Html.EditorFor(m => m.Time)</td>
60.
<td>@Html.DisplayFor(m => m.Time)</td>
61.
</tr>
62.
<tr>
63.
<td>@Html.LabelFor(m => m.HiddenInput)</td>
64.
<td>@Html.EditorFor(m => m.HiddenInput)</td>
65.
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
66.
</tr>
67.
<tr>
68.
<td>@Html.LabelFor(m => m.Boolean)</td>
69.
<td>@Html.EditorFor(m => m.Boolean)</td>
70.
<td>@Html.DisplayFor(m => m.Boolean)</td>
71.
</tr>
72.
<tr>
73.
<td>@Html.LabelFor(m => m.Email)</td>
74.
<td>@Html.EditorFor(m => m.Email)</td>
75.
<td>@Html.DisplayFor(m => m.Email)</td>
http://tahe.developpez.com
142/362
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
</tr>
<tr>
<td>@Html.LabelFor(m => m.Url)</td>
<td>@Html.EditorFor(m => m.Url)</td>
<td>@Html.DisplayFor(m => m.Url)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Password)</td>
<td>@Html.EditorFor(m => m.Password)</td>
<td>@Html.DisplayFor(m => m.Password)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Currency)</td>
<td>@Html.EditorFor(m => m.Currency)</td>
<td>@Html.DisplayFor(m => m.Currency)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.CreditCard)</td>
<td>@Html.EditorFor(m => m.CreditCard)</td>
<td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>
</tbody>
</table>
<input type="submit" value="Valider" />
}
</body>
</html>
Html.EditorFor pour gnrer la balise HTML de saisie de la valeur de la proprit. Cette mthode va utiliser les
mtadonnes [DataType] et [UIHint] de la proprit ;
Html.DisplayFor pour afficher la valeur de la proprit selon le format indiqu par la mtadonne [DataType].
Voici un exemple d'excution avec le navigateur Chrome :
http://tahe.developpez.com
143/362
Selon le navigateur utilis, on peut avoir des pages diffrentes. En effet, la vue gnre utilise les nouvelles balises amenes par la
version 5 de HTML appele HTML5. Tous les navigateurs ne supportent pas encore cette version. Ci-dessus, le navigateur Chrome
la supporte en partie.
5.8.1
Le [POST] du formulaire
// Action10-POST
[HttpPost]
public ContentResult Action10Post(ViewModel10 modle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrleur={0}, Action={1}, valide={2},
erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"],
ModelState.IsValid, erreurs);
7.
return Content(texte, "text/plain", Encoding.UTF8);
8. }
Examinons maintenant les proprits du modle [ViewModel10] une par une et voyons comment les mtadonnes associes
influencent le HTML gnr et la validation des champs de saisie.
5.8.2
Proprit [Text]
Dfinition
1.
http://tahe.developpez.com
[Display(Name="Text")]
144/362
2.
[DataType(DataType.Text)]
3.
public string Text { get; set; }
4. ...
5. Text = "tra la la";
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Text)</td>
3.
<td>@Html.EditorFor(m => m.Text)</td>
4.
<td>@Html.DisplayFor(m => m.Text)</td>
5. </tr>
Visuel
HTML gnr
1.
<tr>
2.
<td><label for="Text">Text</label></td>
3.
<td><input class="text-box single-line" id="Text" name="Text" type="text"
value="tra la la" /></td>
4.
<td>tra la la</td>
5. </tr>
Commentaires
la mthode [Html.LabelFor] a gnr la balise <label> de la ligne 2. La valeur de l'attribut [for] est le nom de la proprit
paramtre de la mthode [Html.LabelFor]
public string Text { get; set; }
La mthode [Html.LabelFor] procde toujours ainsi. Nous ne reviendrons pas dessus pour les autres proprits.
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3. On notera qu'elle a un attribut [class] qui associe la
classe CSS [text-box single-line] la balise. Les attributs [id] et [name] ont pour valeur le nom [Text] de la proprit
paramtre de la mthode [Html.EditorFor]. L'attribut [type] a eu la valeur [text] cause de la mtadonne
[DataType(DataType.Text)]
la mthode [Html.DisplayFor] a gnr le texte de la ligne 4. C'est la valeur de la proprit paramtre de la mthode
[Html.DisplayFor ]. Cette mthode est influence par la mtadonne
[DataType(DataType.Text)]
qui fait que la valeur est affiche comme un texte non format.
5.8.3
Proprit [MultiLineText]
Dfinition
1.
2.
http://tahe.developpez.com
[Display(Name = "TextArea")]
[DataType(DataType.MultilineText)]
145/362
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.MultiLineText)</td>
3.
<td>@Html.EditorFor(m => m.MultiLineText)</td>
4.
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
4.
5.
6.
7.
8.
<tr>
<td><label for="MultiLineText">TextArea</label></td>
<td><textarea class="text-box multi-line" id="MultiLineText"
name="MultiLineText">
ligne1
ligne2</textarea></td>
<td>ligne1
ligne2</td>
</tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <textarea> de la ligne 3. On notera qu'elle a un attribut [class] qui associe
la classe CSS [text-box multi-line] la balise. Les attributs [id] et [name] ont pour valeur le nom [MultiLineText] de la
proprit paramtre de la mthode [Html.EditorFor]. C'est toujours ainsi. Nous ne le mentionnerons plus. La balise
gnre est <textarea> cause de la mtadonne
[DataType(DataType.MultilineText)]
5.8.4
Proprit [Number]
Dfinition
1.
[Display(Name = "Number")]
2. public int Number { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Number)</td>
3.
<td>@Html.EditorFor(m => m.Number)</td>
4.
<td>@Html.DisplayFor(m => m.Number)</td>
5. </tr>
Visuel
http://tahe.developpez.com
146/362
HTML gnr
1. <tr>
2.
<td><label for="Number">Number</label></td>
3.
<td><input class="text-box single-line" data-val="true" data-val-number="Le
champ Number doit tre un nombre." data-val-required="Le champ Number est requis."
id="Number" name="Number" type="number" value="4" /></td>
4.
<td>4</td>
5.
</tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [number].
Apparemment simplement parce que la proprit a le type [int]. Les attributs [data-val], [data-val-number] et [data-valrequired] sont des attributs non reconnus par HTML5. Ils sont utiliss par un framework Javascript de validation des
donnes ct client ;
la mthode [Html.DisplayFor] a gnr le texte de la ligne 4, la valeur de la proprit.
Validation
Les attributs [data-x] influencent la validation des donnes ct client. Voici deux exemples.
On saisit un nombre erron et on valide :
Ci-dessus, la validation a eu lieu ct client. Le formulaire ne sera pas post tant que l'erreur n'aura pas t corrige.
Autre exemple, on ne saisit rien :
En [1] ci-dessus, [Action10Post] signale une erreur. On se souvient peut-tre qu'on avait dj obtenu ce comportement en utilisant
l'attribut [Required] sur la proprit contrler (cf page 78), ici la proprit [Number]. Ici, on n'a pas eu le faire.
5.8.5
Proprit [Decimal]
Dfinition
1.
[Display(Name = "Decimal")]
2.
[UIHint("Decimal")]
3. public double Decimal { get; set; }
Vue
http://tahe.developpez.com
147/362
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Decimal)</td>
3.
<td>@Html.EditorFor(m => m.Decimal)</td>
4.
<td>@Html.DisplayFor(m => m.Decimal)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="Decimal">Decimal</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le
champ Decimal doit tre un nombre." data-val-required="Le champ Decimal est requis."
id="Decimal" name="Decimal" type="text" value="10,20" /></td>
4.
<td>10,20</td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [text]. Les autres
attributs sont identiques ceux gnrs pour la proprit [Number] prcdente. La mtadonne :
[UIHint("Decimal")]
fait que la valeur de la proprit est affiche avec deux dcimales pour les deux mthodes [Html.EditorFor] et
[Html.DisplayFor]
Validation
Aucune erreur de validation n'est signale ct client, contrairement au cas prcdent. L'erreur n'est signale que par l'action
[Action10Post]. L encore, le nombre dcimal est requis sans qu'on ait besoin de lui mettre l'attribut [Required].
5.8.6
Proprit [Tel]
Dfinition
1.
[Display(Name = "Tel")]
2.
[DataType(DataType.PhoneNumber)]
3. public string Tel { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Tel)</td>
3.
<td>@Html.EditorFor(m => m.Tel)</td>
4.
<td>@Html.DisplayFor(m => m.Tel)</td>
5. </tr>
Visuel
http://tahe.developpez.com
148/362
HTML gnr
1.
2.
3.
<tr>
<td><label for="Tel">Tel</label></td>
<td><input class="text-box single-line" id="Tel" name="Tel" type="tel"
value="0617181920" /></td>
4.
<td>0617181920</td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [tel]. Cette valeur a t
gnre cause de la mtadonne :
[DataType(DataType.PhoneNumber)]
Le type [tel] pour une balise <input> est une nouveaut de HTML5. Le navigateur Chrome l'a traite comme une balise
<input> avec le type [text].
Validation
Aucune erreur de validation n'est signale ct client ou ct serveur. On peut saisir n'importe quoi.
5.8.7
Proprit [Date]
Dfinition
1.
[Display(Name = "Date")]
2.
[DataType(DataType.Date)]
3. public DateTime Date { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Date)</td>
3.
<td>@Html.EditorFor(m => m.Date)</td>
4.
<td>@Html.DisplayFor(m => m.Date)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
http://tahe.developpez.com
<tr>
<td><label for="Date">Date</label></td>
<td><input class="text-box single-line" data-val="true" data-val-date="Le
champ Date doit tre une date." data-val-required="Le champ Date est requis."
id="Date" name="Date" type="date" value="11/10/2013" /></td>
149/362
4.
5. </tr>
<td>11/10/2013</td>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [date]. Cette valeur a
t gnre cause de la mtadonne :
[DataType(DataType.Date)]
Le type [date] pour une balise <input> est une nouveaut HTML5. Le navigateur Chrome la reconnat et permet de saisir
la date avec un calendrier. Par ailleurs, la date saisie est prsente au format [jj/mm/aaaa], --d que Chrome adapte le
format de date la [locale] du navigateur.
la mthode [Html.DisplayFor] a crit elle aussi la date sous la forme [jj/mm/aaaa] toujours cause de la prsence de la
mtadonne [Date].
Validation
Une date invalide est signale ct client [1] empchant le POST du formulaire au serveur.
2
1
5.8.8
Proprit [Time]
Dfinition
1.
[Display(Name = "Time")]
2.
[DataType(DataType.Time)]
3. public DateTime Time { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Time)</td>
3.
<td>@Html.EditorFor(m => m.Time)</td>
4.
<td>@Html.DisplayFor(m => m.Time)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="Time">Time</label></td>
<td><input class="text-box single-line" data-val="true" data-val-required="Le
champ Time est requis." id="Time" name="Time" type="time" value="11:17" /></td>
4.
<td>11:17</td>
http://tahe.developpez.com
150/362
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [time]. Cette valeur a
t gnre cause de la mtadonne :
[DataType(DataType.Time)]
Le type [time] pour une balise <input> est une nouveaut HTML5. Le navigateur Chrome la reconnat et permet de saisir
une heure sous la forme [hh:mm] ;
la mthode [Html.DisplayFor] a crit elle aussi l'heure sous la forme [hh:mm] toujours cause de la prsence de la
mtadonne [Time].
Validation
Il n'est techniquement pas possible de saisir une heure invalide. L'absence d'heure est signale ct serveur :
5.8.9
Proprit [HiddenInput]
Dfinition
1.
[Display(Name = "HiddenInput")]
2.
[UIHint("HiddenInput")]
3. public string HiddenInput { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.HiddenInput)</td>
3.
<td>@Html.EditorFor(m => m.HiddenInput)</td>
4.
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="HiddenInput">HiddenInput</label></td>
<td>caché<input id="HiddenInput" name="HiddenInput" type="hidden"
value="cach" /></td>
4.
<td>caché</td>
5. </tr>
Commentaires
http://tahe.developpez.com
151/362
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [hidden], --d un
champ cach (mais nanmoins post). Cette valeur a t gnre cause de la mtadonne :
[UIHint("HiddenInput")]
5.8.10
Proprit [Boolean]
Dfinition
1.
[Display(Name = "Boolean")]
2. public bool Boolean { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Boolean)</td>
3.
<td>@Html.EditorFor(m => m.Boolean)</td>
4.
<td>@Html.DisplayFor(m => m.Boolean)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="Boolean">Boolean</label></td>
<td><input checked="checked" class="check-box" data-val="true" data-valrequired="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox"
value="true" /><input name="Boolean" type="hidden" value="false" /></td>
4.
<td><input checked="checked" class="check-box" disabled="disabled"
type="checkbox" /></td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [checkbox], --d une
case cocher. Cette valeur a t gnre parce que la proprit est boolenne :
public bool Boolean { get; set; }
la mthode [Html.DisplayFor] a gnr la ligne 4, galement une case cocher (attribut type) mais dsactive (attribut
disabled).
5.8.11
Proprit [Email]
Dfinition
1.
[Display(Name = "Email")]
2.
[DataType(DataType.EmailAddress)]
3. public string Email{ get; set; }
Vue
1.
http://tahe.developpez.com
<tr>
152/362
2.
3.
4.
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="Email">Email</label></td>
<td><input class="text-box single-line" id="Email" name="Email" type="email"
value="x@y.z" /></td>
4.
<td><a href="mailto:x@y.z">x@y.z</a></td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [email]. Ce type est
nouveau dans HTML5. Ce type a t gnr cause de la mtadonne :
[DataType(DataType.EmailAddress)]
Validation
Une adresse invalide est signale ct client [1] :
1
5.8.12
Proprit [Url]
Dfinition
1.
[Display(Name = "Url")]
2.
[DataType(DataType.Url)]
3. public string Url { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Url)</td>
3.
<td>@Html.EditorFor(m => m.Url)</td>
4.
<td>@Html.DisplayFor(m => m.Url)</td>
5. </tr>
Visuel
http://tahe.developpez.com
153/362
HTML gnr
1.
2.
3.
<tr>
<td><label for="Url">Url</label></td>
<td><input class="text-box single-line" id="Url" name="Url" type="url"
value="http://istia.univ-angers.fr" /></td>
4.
<td><a href="http://istia.univ-angers.fr">http://istia.univangers.fr</a></td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [url]. Ce type est
nouveau dans HTML5. Il a t gnr cause de la mtadonne :
[DataType(DataType.Url)]
Validation
Une URL invalide est signale ct client [1] :
5.8.13
Proprit [Password]
Dfinition
1.
[Display(Name = "Password")]
2.
[DataType(DataType.Password)]
3. public string Password { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Password)</td>
3.
<td>@Html.EditorFor(m => m.Password)</td>
4.
<td>@Html.DisplayFor(m => m.Password)</td>
5. </tr>
Visuel
http://tahe.developpez.com
154/362
HTML gnr
1.
2.
3.
<tr>
<td><label for="Password">Password</label></td>
<td><input class="text-box single-line password" id="Password"
name="Password" type="password" value="mdp" /></td>
4.
<td>mdp</td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [password]. Ce type a
t gnr cause de la mtadonne :
[DataType(DataType.Password)]
5.8.14
Proprit [Currency]
Dfinition
1.
[Display(Name = "Currency")]
2.
[DataType(DataType.Currency)]
3. public double Currency { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.Currency)</td>
3.
<td>@Html.EditorFor(m => m.Currency)</td>
4.
<td>@Html.DisplayFor(m => m.Currency)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="Currency">Currency</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le
champ Currency doit tre un nombre." data-val-required="Le champ Currency est
requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
4.
<td>4,20 </td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [text] ;
la mthode [Html.DisplayFor] a gnr la ligne 4, un nombre deux dcimales avec un symbole montaire. Ce format a
t utilis cause de la mtadonne :
[DataType(DataType.Currency)]
Validation
Une valeur invalide [1] ou une absence de valeur [2] est signale ct serveur :
http://tahe.developpez.com
155/362
5.8.15
Proprit [CreditCard]
Dfinition
1.
[Display(Name = "CreditCard")]
2.
[DataType(DataType.CreditCard)]
3. public string CreditCard { get; set; }
Vue
1.
<tr>
2.
<td>@Html.LabelFor(m => m.CreditCard)</td>
3.
<td>@Html.EditorFor(m => m.CreditCard)</td>
4.
<td>@Html.DisplayFor(m => m.CreditCard)</td>
5. </tr>
Visuel
HTML gnr
1.
2.
3.
<tr>
<td><label for="CreditCard">CreditCard</label></td>
<td><input class="text-box single-line" id="CreditCard" name="CreditCard"
type="text" value="0123456789012345" /></td>
4.
<td>0123456789012345</td>
5. </tr>
Commentaires
la mthode [Html.EditorFor] a gnr la balise <input> de la ligne 3 avec un attribut [type] de type [text]. La mthode
[Html.DisplayFor] a gnr la ligne 4. On ne voit pas ici ce qu'apporte la mtadonne :
[DataType(DataType.CreditCard)]
Validation
Aucune vrification n'est faite, ni ct client, ni ct serveur.
5.9
Nous avons dj rencontr le problme de la validation du modle d'une action au paragraphe 4.5, page 77, et dans les paragraphes
suivants. Nous revenons sur cette problmatique dans le cadre d'un formulaire :
http://tahe.developpez.com
156/362
5.9.1
faire les validations aussi bien ct client que ct serveur afin de signaler plus rapidement les erreurs l'utilisateur.
Validation ct serveur
http://tahe.developpez.com
157/362
57.
58.
59.
60.
61.
62.
63.
64.
65.
// validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> rsultats = new List<ValidationResult>();
// Date 1
if (Date1.Date <= DateTime.Now.Date)
{
rsultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1"
}));
66.
}
67.
// Email1
68.
try
69.
{
70.
new MailAddress(Email1);
71.
}
72.
catch
73.
{
74.
rsultats.Add(new ValidationResult("Information incorrecte", new string[] {
"Email1" }));
75.
}
76.
// on rend la liste des erreurs
77.
return rsultats;
78.
}
79. }
80. }
Ce modle sera affich par la vue suivante [Action11Get.cshtml] :
1. @model Exemple_03.Models.ViewModel11
2. @{
3.
Layout = null;
4. }
5.
6. <!DOCTYPE html>
7.
8. <html>
9. <head>
10. <meta name="viewport" content="width=device-width" />
11. <title>Action11Get</title>
12. <link rel="stylesheet" href="~/Content/Site.css" />
13. </head>
14. <body>
15. <h3>Formulaire ASP.NET MVC Validation 1</h3>
16. @using (Html.BeginForm("Action11Post", "First"))
http://tahe.developpez.com
158/362
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
{
<table>
<thead>
<tr>
<th>Type attendu</th>
<th>Valeur saisie</th>
<th>Message d'erreur</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Chaine1)</td>
<td>@Html.EditorFor(m => m.Chaine1)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine2)</td>
<td>@Html.EditorFor(m => m.Chaine2)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine3)</td>
<td>@Html.EditorFor(m => m.Chaine3)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier1)</td>
<td>@Html.EditorFor(m => m.Entier1)</td>
<td>@Html.ValidationMessageFor(m => m.Entier1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier2)</td>
<td>@Html.EditorFor(m => m.Entier2)</td>
<td>@Html.ValidationMessageFor(m => m.Entier2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel1)</td>
<td>@Html.EditorFor(m => m.Reel1)</td>
<td>@Html.ValidationMessageFor(m => m.Reel1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel2)</td>
<td>@Html.EditorFor(m => m.Reel2)</td>
<td>@Html.ValidationMessageFor(m => m.Reel2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Email1)</td>
<td>@Html.EditorFor(m => m.Email1)</td>
<td>@Html.ValidationMessageFor(m => m.Email1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Regexp1)</td>
<td>@Html.EditorFor(m => m.Regexp1)</td>
<td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Date1)</td>
<td>@Html.EditorFor(m => m.Date1)</td>
<td>@Html.ValidationMessageFor(m => m.Date1)</td>
</tr>
</tbody>
</table>
<p>
http://tahe.developpez.com
159/362
80.
<input type="submit" value="Valider" />
81.
</p>
82. }
83. </body>
84. </html>
ligne 12 : on rfrence la feuille de style [Site.css]. Elle contient par dfaut des classes utilises pour mettre en relief les erreurs de
saisie du formulaire ;
lignes 18-25 : un tableau trois colonnes :
// Action11-GET
[HttpGet]
public ViewResult Action11Get()
{
return View("Action11Get", new ViewModel11());
L'action [Action11Post] sert rafficher le formulaire avec les ventuelles erreurs de saisie :
1.
2.
3.
4.
5.
6. }
// Action11-POST
[HttpPost]
public ViewResult Action11Post(ViewModel11 modle)
{
return View("Action11Get", modle);
ligne 3 : le modle [ViewModel11] est cr puis initialis avec les valeurs postes. Il peut alors se produire des erreurs. A chaque
proprit errone P du modle est associ un message d'erreur. C'est ce message que permet d'obtenir la mthode
[Html.ValidationMessageFor] du formulaire.
http://tahe.developpez.com
160/362
On notera que les deux dates sont errones (on est le 11/10/2013) mais que les erreurs ne sont pas signales. Ces erreurs sont dtectes
par la mthode [Validate] du modle :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31. }
// validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> rsultats = new List<ValidationResult>();
// Date 1
if (Date1.Date <= DateTime.Now.Date)
{
rsultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
}
// Email1
try
{
new MailAddress(Email1);
}
catch
{
rsultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
}
// Regexp1
try
{
DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
}
catch
{
rsultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
}
// on rend la liste des erreurs
return rsultats;
La mthode [Validate] n'est excute que lorsque toutes les validations par attributs sont passes. C'est ce que montre un dernier exemple :
http://tahe.developpez.com
161/362
5.9.2
Validation ct client
Toutes les validations prcdentes se sont faites ct serveur. Il faut donc un aller-retour entre le client et le serveur pour que l'utilisateur
s'aperoive de ses erreurs. La validation ct client utilise du code Javascript pour signaler l'utilisateur ses erreurs le plus tt possible et en
tout cas avant le POST. Celui-ci ne peut avoir lieu que lorsque toutes les erreurs dtectes ont t corriges.
Nous reprenons le modle [ViewModel11] prcdent mais nous l'affichons maintenant avec la vue [Action12Get.cshtml] suivante :
1. @model Exemple_03.Models.ViewModel11
2. @{
3.
Layout = null;
4. }
5.
6. <!DOCTYPE html>
7.
8. <html>
9. <head>
10. <meta name="viewport" content="width=device-width" />
11. <title>Action12Get</title>
12. <link rel="stylesheet" href="~/Content/Site.css" />
http://tahe.developpez.com
162/362
13.
14.
15.
Chaque script a une version normale [.js] et une version minifie [min.js]. Cette dernire version est plus lgre mais illisible. On l'utilise en
production. La version lisible est utilise en dveloppement.
La vue [Action12Get.cshtml] sera affiche par l'action [Action12Get] suivante :
1.
2.
3.
4.
5.
6. }
// Action12-GET
[HttpGet]
public ViewResult Action12Get()
{
return View("Action12Get", new ViewModel11());
http://tahe.developpez.com
// Action12-POST
[HttpPost]
public ViewResult Action12Post(ViewModel11 modle)
163/362
4.
5.
6. }
{
return View("Action12Get", modle);
Ds qu'on tape un caractre en [1], le message en [2] s'affiche parce que la valeur attendue doit avoir au moins quatre caractres. Ainsi la
validation est-elle faite chaque nouveau caractre tap. Le message d'erreur disparat au quatrime caractre tap. Ceci fait, validons le
formulaire :
L'URL [3] nous montre que le [POST] n'a pas eu lieu. Mais le clic sur le bouton [Valider] a dclench toutes les validations ct client et de
nouveaux messages d'erreur sont apparus.
Regardons le HTML gnr pour la premire saisie par exemple :
1.
<tr>
2.
<td><label for="Chaine1">Chaîne d'au moins quatre
caractères</label></td>
http://tahe.developpez.com
164/362
3.
<td><input class="text-box single-line" data-val="true" data-valregex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-valrequired="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
4.
<td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsgreplace="true"></span></td>
5. </tr>
ligne 3, on retrouve :
Les attributs [data-x] des balises gnres sont exploits par le Javascript que nous avons embarqu dans la vue. Si celui-ci est absent, ces
attributs sont tout simplement ignors et on n'a alors pas de validation ct client. On fonctionne comme dans l'exemple prcdent. D'o
le terme [unobtrusive] pour cette technique.
5.9.3
Nous allons crer les deux vues suivantes pour illustrer la gestion de liens dans une vue :
http://tahe.developpez.com
165/362
// Action16-GET
[HttpGet]
public ViewResult Action16Get()
{
ViewBag.info = string.Format("Contrleur={0}, Action={1}",
RouteData.Values["controller"], RouteData.Values["action"]);
6.
return View("Action16Get");
7.
}
8.
9.
// Action16-POST
10.
[HttpPost]
http://tahe.developpez.com
166/362
11.
12.
13.
ligne 6, l'action [Action16Get] gnre la vue [Action16Get.cshtml], --d la page 1 de l'exemple. Cette vue a pour modle
le [ViewBag] (ligne 5) ;
ligne 19, l'action [Action17Get] gnre la vue [Action17Get.cshtml], --d la page 2 de l'exemple. Cette vue a pour modle
le [ViewBag] (ligne 21) ;
ligne 11 : l'action [Action16Post] traite le POST du formulaire de la vue [Action16Get.cshtml]. Elle reoit le paramtre
nomm [data]. On se rappelle que c'est le nom du champ de saisie dans le formulaire ;
ligne 13 : une information est mise dans le [ViewBag] ;
ligne 14 : la vue [Action16Get.cshtml] est affiche.
http://tahe.developpez.com
167/362
Localisation (l10n)
Globalisation
Langue
langue parle dsigne par un code ISO (fr : franais, es : espagnol, en : anglais, ...)
Locale
une variante de la langue dsigne galement par un code ISO (en_GB : anglais de
Grande-Bretagne, en_US : anglais des Etats-Unis, ...)
6.1
Pour le nombre rel, nous avons tap [0,3] et cela n'a pas t accept. Il faut taper [0.3] :
Le format attendu est donc le format anglo-saxon et non le format franais. En recherchant sur internet, on trouve des solutions. En voici
une.
Les actions [GET] et [POST] deviennent les suivantes :
1.
2.
3.
4.
5.
6. }
// Action13-GET
[HttpGet]
public ViewResult Action13Get()
{
return View("Action13Get", new ViewModel11());
1.
2.
3.
4.
5.
6. }
// Action13-POST
[HttpPost]
public ViewResult Action13Post(ViewModel11 modle)
{
return View("Action13Get", modle);
La vue [Action13Get.cshtml] est identique la vue [Action12Get.cshtml] aux scripts Javascript prs :
http://tahe.developpez.com
168/362
1. <head>
2.
<meta name="viewport" content="width=device-width" />
3.
<title>Action13Get</title>
4.
<link rel="stylesheet" href="~/Content/Site.css" />
5.
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
6.
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
7.
<script type="text/javascript"
src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
8. ...
9.
<script type="text/javascript" src="~/Scripts/myscripts.js"></script>
10.
11. </head>
1. // http://blog.instance-factory.com/?p=268
2. $.validator.methods.number = function (value, element) {
3.
return this.optional(element) ||
4.
!isNaN(Globalize.parseFloat(value));
5. }
6.
7. $.validator.methods.date = function (value, element) {
8.
return this.optional(element) ||
9.
!isNaN(Globalize.parseDate(value));
10. }
11.
12. jQuery.extend(jQuery.validator.methods, {
13. range: function (value, element, param) {
14.
//Use the Globalization plugin to parse the value
15.
var val = Globalize.parseFloat(value);
16.
return this.optional(element) || (
17.
val >= param[0] && val <= param[1]);
18. }
19. });
20.
21. // au chargement du document
22. $(document).ready(function () {
23. var culture = 'fr-FR';
24. Globalize.culture(culture);
25. });
J'ai indiqu en ligne 1, o ce script avait t trouv. Je n'essaierai pas de l'expliquer car je ne le comprends pas. Le Javascript a parfois
des cts hermtiques. Lignes 4, 9, 15, un objet [Globalize] est utilis. Celui-ci est fourni par la bibliothque JQuery Globalization
qu'on peut obtenir avec [NuGet] :
http://tahe.developpez.com
169/362
3
2
Une fois le paquetage [Globalize] install, une nouvelle branche apparat dans le dossier [Scripts] :
1
Le script [globalize.js] et le script de notre culture [globalize.culture.fr-FR.js] doivent faire partie de la liste des scripts inclus dans
notre page [Action13Get.cshtml] :
1. <head>
2.
<meta name="viewport" content="width=device-width" />
3.
<title>Action13Get</title>
4. ...
5.
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
6.
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.frFR.js"></script>
7.
<script type="text/javascript" src="~/Scripts/myscripts.js"></script>
8. </head>
http://tahe.developpez.com
170/362
ligne 10 : la fonction JQuery [ready] est excute lorsque le document dans lequel se trouve le script a t entirement
charg par le navigateur ;
lignes 11-12 : on fixe la culture ct client [fr-FR]. Pour cela, il faut que le fichier [globalize.culture.fr-FR.js] soit inclus
dans la liste des scripts Javascript associs au document.
Maintenant, nous pouvons tester la nouvelle application :
On peut maintenant taper [0,3] pour le nombre rel, ce qu'on ne pouvait faire auparavant. On rencontre cependant une autre
anomalie :
Ci-dessus, la validation ct client nous laisse taper [11.2] avec la notation anglo-saxonne. Cette valeur n'est pas accepte ct
serveur lorsqu'on valide le formulaire :
Il faut taper [11,2] et l a fonctionne cts client et serveur. Il faudrait que ct client, la notation anglo-saxonne ne soit pas
accepte. Ca doit tre possible...
Abordons maintenant l'internationalisation des vues. Nous allons continuer avec l'exemple du formulaire prcdent en l'offrant en
deux langues : franais et anglais.
6.2
La langue des vues est contrle par l'objet [Thread.CurrentThread.CurrentUICulture]. Pour afficher les pages dans la culture [frFR], on crit :
http://tahe.developpez.com
171/362
Thread.CurrentThread.CurrentUICulture=new CultureInfo("fr-FR");
La localisation (dates, nombres, monnaies, heures, ...) est contrle par l'objet [Thread.CurrentThread.CurrentCulture]. De faon
similaire ce qui a t crit prcdemment, on crira :
Thread.CurrentThread.CurrentCulture=new CultureInfo("fr-FR");
Ces deux instructions pourraient tre dans le constructeur de chaque contrleur de l'application. Mais on pourrait vouloir galement
factoriser ce code commun tous les contrleurs. Nous suivons cette voie.
Nous crons deux nouveaux contrleurs :
http://tahe.developpez.com
172/362
40.
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
41.
}
42. }
43. }
6.3
ligne 7 : [SecondController] drive de [I18NController]. Ainsi est-on assur que la culture de la vue afficher aura t
initialise ;
ligne 13 : on utilise le modle de vue [ViewModel14] que nous allons prsenter ;
lignes 13 et 20 : la vue [Action14Get.cshtml] assure l'affichage du formulaire.
http://tahe.developpez.com
173/362
1. using Exemple_03.Resources;
2. using System;
3. using System.Collections.Generic;
4. using System.ComponentModel.DataAnnotations;
5. using System.Globalization;
6. using System.Net.Mail;
7.
8. namespace Exemple_03.Models
9. {
10. public class ViewModel14 : IValidatableObject
11. {
12.
13.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
14.
[Display(ResourceType = typeof(MyResources), Name = "chaineaumoins4")]
15.
[RegularExpression(@"^.{4,}$", ErrorMessageResourceType = typeof(MyResources),
ErrorMessageResourceName = "infoIncorrecte")]
16.
public string Chaine1 { get; set; }
17.
18.
[Display(ResourceType = typeof(MyResources), Name = "chaineauplus4")]
19.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
20.
[RegularExpression(@"^.{1,4}$", ErrorMessageResourceType = typeof(MyResources),
ErrorMessageResourceName = "infoIncorrecte")]
21.
public string Chaine2 { get; set; }
22.
23.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
24.
[Display(ResourceType = typeof(MyResources), Name = "chaine4exactement")]
25.
[RegularExpression(@"^.{4,4}$", ErrorMessageResourceType = typeof(MyResources),
ErrorMessageResourceName = "infoIncorrecte")]
26.
public string Chaine3 { get; set; }
27.
28.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
29.
[Display(ResourceType = typeof(MyResources), Name = "entier")]
30.
public int Entier1 { get; set; }
31.
32.
[Display(ResourceType = typeof(MyResources), Name = "entierentrebornes")]
33.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
34.
[Range(1, 100, ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName
= "infoIncorrecte")]
35.
public int Entier2 { get; set; }
36.
37.
[Display(ResourceType = typeof(MyResources), Name = "reel")]
38.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
39.
public double Reel1 { get; set; }
http://tahe.developpez.com
174/362
40.
41.
42.
http://tahe.developpez.com
175/362
94.
return rsultats;
95.
}
96. }
97. }
Ce modle est le modle prcdent [ViewModel11] internationalis. Nous allons dcrire le mcanisme d'internationalisation pour le
premier attribut de la premire proprit. Les autres attributs suivent le mme mcanisme.
1.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
2. public string Chaine1 { get; set; }
Dans la version internationalise, ligne 1, les textes afficher sont placs dans un fichier de ressources. Ici ce fichier s'appelle
[MyResources.resx] (typeof) et a t plac la racine du projet. On l'appelle un fichier de ressources.
[MyResources] : ressource par dfaut lorsqu'il n'y a pas de ressource pour la locale courante ;
[MyResources.fr-FR] : ressource pour la locale [fr-FR] ;
[MyResources.en-US] : ressource pour la locale [en-US] ;
http://tahe.developpez.com
176/362
Cela cre le fichier de ressources [MyResources2.resx]. Lorsqu'on double-clique dessus, on a la page suivante :
Un fichier de ressources est un dictionnaire avec des cls et des valeurs associes ces cls. On entre la cl en [1], la valeur en [2], la
porte de la ressource en [3]. Pour que ces ressources soient lisibles, il faut qu'elles aient la porte [Public]. Revenons la ligne :
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
[ErrorMessageResourceType] : dsigne le fichier des ressources. Le paramtre du [typeof] est le nom du fichier. Celui-ci est
transform en classe par le processus de compilation et son binaire inclus dans l'assembly du projet. Donc au final
[MyResources] est le nom de la classe des ressources ;
[ErrorMessageResourceName = "infoRequise"] : dsigne une cl dans le fichier des ressources. Au final, la ligne signifie
que le message d'erreur afficher est la valeur du fichier [MyResources] associe la cl [ infoRequise].
Pour crer la cl [infoRequise] et la valeur associe dans le fichier [MyResources] on procde comme suit :
http://tahe.developpez.com
177/362
En [1], nous dfinissons l'espace de noms de la classe [MyResources] qui va tre cre partir du fichier de ressources
[MyResources.resx]. Revenons la ligne internationalise tudie :
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
L'oprateur typeof attend une classe, ici la classe [MyResources]. Pour que celle-ci soit trouve, il faut importer son espace de noms
dans la classe [ViewModel14] :
using Exemple_03.Resources;
Pour que la classe [MyResources] soit visible, il faut qu'auparavant le projet ait t gnr au moins une fois depuis la cration du
fichier de ressources [MyResources]. Le code de cette classe est visible dans le fichier [MyResources.Designer.cs] :
namespace Exemple_03.Resources {
using System;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuild
er", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class MyResources2 {
6.
7.
8.
9.
10.
...
11.
12.
13.
14.
15.
16.
}
17. }
http://tahe.developpez.com
178/362
Nous avons cr [MyResources.resx]. C'est la ressource racine. Ensuite nous crons autant de fichier de ressources
[MyResources.locale.resx] qu'il y a de locales (langues) grer. Ici nous grons le franais [fr-FR] et l'anglais amricain [en-US].
Lorsque la locale courante n'est ni [fr-FR], ni [en-US], c'est la ressource racine [MyResources.resx] qui est utilise.
Le contenu final de [MyResources.resx] est le suivant :
Les messages seront en franais lorsque la locale ne sera pas reconnue. Le contenu final de [MyResources.fr-FR.resx] est identique
et obtenu par simple copie de fichier.
Le contenu final de [MyResources.en-US.resx] est obtenu lui aussi par copie de fichier puis modifi comme suit :
http://tahe.developpez.com
// validation
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
179/362
3.
4.
5.
6.
7.
{
// liste des erreurs
List<ValidationResult> rsultats = new List<ValidationResult>();
// le mme msg d'erreur pour tous
string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new
CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();
8.
9.
10.
11.
12.
13.
14. ...
15.
16.
17. }
// Date 1
if (Date1.Date <= DateTime.Now.Date)
{
rsultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
}
// on rend la liste des erreurs
return rsultats;
La ligne 7 montre comment rcuprer un message du fichier de ressources [MyResources]. Ici on veut rcuprer le message associ
la cl [infoIncorrecte] et ceci dans la culture du moment :
6.4
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action14Get</title>
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/myscripts2.js"></script>
<script>
$(document).ready(function () {
var culture = '@System.Threading.Thread.CurrentThread.CurrentCulture';
Globalize.culture(culture);
});
</script>
</head>
http://tahe.developpez.com
180/362
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
<body>
<h3>Formulaire ASP.NET MVC - Internationalisation</h3>
@using (Html.BeginForm("Action14Post", "Second"))
{
<table>
<thead>
<tr>
<th>@MyResources.type</th>
<th>@MyResources.value</th>
<th>@MyResources.error</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Chaine1)</td>
<td>@Html.EditorFor(m => m.Chaine1)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
</tr>
...
</tbody>
</table>
<p>
<input type="submit" value="Valider" />
</p>
}
</body>
</html>
<!-- choix d'une langue -->
@using (Html.BeginForm("Lang", "Second"))
{
<table>
<tr>
<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Franais</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
</tr>
</table>
}
Commenons par le plus simple, les lignes 36-38. Elles utilisent les proprits statiques de la classe [MyResources] que nous venons
de dcrire. Pour avoir accs la classe [MyResources], il faut importer son espace de noms (ligne 2).
Dans les messages internationaliss, il faut prvoir galement ceux qui sont affichs par le framework de validation ct client. Pour
cela, il faut utiliser les bibliothques JQuery des lignes 17-19. Nous utilisons les fichiers JQuery pour les deux cultures que nous
grons [fr-FR] et [en-US]. Par ailleurs, on se souvient peut tre que la vue [Action13Get] utilisait le script Javascript [myscripts.js]
suivant :
1. // au chargement du document
2. $(document).ready(function () {
3.
var culture = 'fr-FR';
4.
Globalize.culture(culture);
5. });
Maintenant, la culture n'est plus seulement [fr-FR], elle varie. Aussi ces lignes sont-elles dsormais gnres par la vue
[Action14Get] elle-mme aux lignes 21-26. Ces six lignes seront incluses dans la page HTML envoye au client.
ligne 23 : la variable Javascript [culture] est initialise avec la culture courante du thread de la requte en cours de
traitement. On se souvient peut tre que celle-ci a t initialise par le constructeur de la classe [I18NController] (page
172) :
// on met la langue en session
httpContext.Session["lang"] = langue;
// on modifie les cultures du thread
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Si la culture courante est [en-US], le script Javascript incorpor dans la page HTML devient :
<script>
$(document).ready(function () {
http://tahe.developpez.com
181/362
On a dj dit que la fonction [$(document).ready] tait excute la fin du chargement de la page par le navigateur. Son excution
va avoir pour effet de fixer la culture du framework de validation ct client. Avec la culture [en-US] les messages d'erreur du
framework seront en anglais et proviendront du fichier de ressources [MyResources.en-US.resx]. Nous verrons comment.
Maintenant examinons les lignes 57-65 :
1. <!-- choix d'une langue -->
2. @using (Html.BeginForm("Lang", "Second"))
3. {
4.
<table>
5.
<tr>
6.
<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Franais</a></td>
7.
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
8.
</tr>
9.
</table>
10. }
On a l un second formulaire, le premier tant aux lignes 31-53. Ce formulaire affiche en bas de page les liens suivants :
ligne 2 : le formulaire est post l'action [Lang] du contrleur [Second]. Pour l'instant on ne voit aucune valeur qui
pourrait tre poste ;
lignes 6 et 7 : un clic sur les liens provoque l'excution de la fonction Javscript [postForm]. O se trouve cette fonction ?
Dans le script [myscripts2.js] rfrenc ligne 20 de la vue :
http://tahe.developpez.com
182/362
5.
var hiddenField = document.createElement("input");
6.
hiddenField.setAttribute("type", "hidden");
7.
hiddenField.setAttribute("name", "lang");
8.
hiddenField.setAttribute("value", lang);
9.
// ajout du champ cach dans le formulaire
10. form.appendChild(hiddenField);
11. // on lui ajoute l'attribut cach url
12. var hiddenField = document.createElement("input");
13. hiddenField.setAttribute("type", "hidden");
14. hiddenField.setAttribute("name", "url");
15. hiddenField.setAttribute("value", url);
16. // ajout du champ cach dans le formulaire
17. form.appendChild(hiddenField);
18. // soumission
19. form.submit();
20. }
21.
22. // http://blog.instance-factory.com/?p=268
23. $.validator.methods.number = function (value, element) {
24. return this.optional(element) ||
25.
!isNaN(Globalize.parseFloat(value));
26. }
27.
28. $.validator.methods.date = function (value, element) {
29. return this.optional(element) ||
30.
!isNaN(Globalize.parseDate(value));
31. }
32.
33. jQuery.extend(jQuery.validator.methods, {
34. range: function (value, element, param) {
35.
//Use the Globalization plugin to parse the value
36.
var val = Globalize.parseFloat(value);
37.
return this.optional(element) || (
38.
val >= param[0] && val <= param[1]);
39. }
40. });
Les lignes 22-40 sont celles dj prsentes dans le script [myscripts.js] utilis dans l'exemple prcdent (page 169). Nous ne revenons
pas dessus. La fonction [postForm] excute lors du clic sur les liens des langues est lignes 1-20 :
ligne 1 : la fonction admet deux paramtres, [lang] qui est la culture choisie par l'utilisateur et [url] qui est l'URL vers
laquelle doit tre redirig le navigateur client une fois le changement de culture opr. Ces deux paramtres sont prciss
l'appel :
<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Franais</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
http://tahe.developpez.com
183/362
1.
2.
3.
4.
Le formulaire est donc post l'URL [/Second/Lang]. Il nous faut alors dfinir une action [Lang] dans le contrleur
[SecondController]. Ce sera la suivante :
1. public class SecondController : I18NController
2.
{
3.
// Action14-GET
4.
[HttpGet]
5.
public ViewResult Action14Get()
6.
{
7.
return View("Action14Get", new ViewModel14());
8.
}
9.
10.
// Action14-POST
11.
[HttpPost]
12.
public ViewResult Action14Post(ViewModel14 modle)
13.
{
14.
return View("Action14Get", modle);
15.
}
16.
17.
// langue
18.
[HttpPost]
19.
public RedirectResult Lang(string url)
20.
{
21.
// on redirige le client vers url
22.
return new RedirectResult(url);
23.
}
24.
25.
}
Mais qu'est devenu le paramtre nomm [lang] ? Il faut maintenant se rappeler que le contrleur [SecondController] drive de la
classe [I18NController] (ligne 1 ci-dessous). C'est ce contrleur (cf page 172) qui gre le paramtre [lang] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
http://tahe.developpez.com
184/362
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35. }
}
if (langue == null)
{
// culture fr-FR
langue = "fr-FR";
}
// on met la langue en session
httpContext.Session["lang"] = langue;
// on modifie les cultures du thread
Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Dans notre exemple tudi, le paramtre [lang] est post. Il sera donc trouv la ligne 13, mis en session la ligne 31 et utilis pour
mettre jour la culture du thread courant lignes 33-34.
Que va-t-il se passer ensuite ? Revenons sur les liens :
<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Franais</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
35.
http://tahe.developpez.com
185/362
26.
27.
28.
29.
30.
31.
32.
33.
34.
35. }
{
// culture fr-FR
langue = "fr-FR";
}
// on met la langue en session
httpContext.Session["lang"] = langue;
// on modifie les cultures du thread
Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Cette fois-ci, le paramtre [lang] sera trouv dans la session par la ligne 18. Supposons que sa valeur soit [en-US]. Cette culture
devient donc la culture du thread d'excution de la requte (lignes 33-34). Revenons l'action [Action14Get] :
1.
2.
3.
4.
5.
6. }
// Action14-GET
[HttpGet]
public ViewResult Action14Get()
{
return View("Action14Get", new ViewModel14());
Parce que la culture du thread courant est [en-US], c'est le fichier [MyResources.en-US.resx] qui va tre exploit. Les messages
d'erreur seront donc en anglais.
Le modle [ViewModel14] instanci, la vue [Action14Get.cshtml] est affiche :
1. @model Exemple_03.Models.ViewModel14
2. @using Exemple_03.Resources
3. @using System.Threading
4. @{
5.
Layout = null;
6. }
7.
8. <!DOCTYPE html>
9.
10. <html>
11. <head>
12. <meta name="viewport" content="width=device-width" />
13. <title>Action14Get</title>
14. ...
15. <script>
16.
$(document).ready(function () {
17.
var culture = '@Thread.CurrentThread.CurrentCulture';
18.
Globalize.culture(culture);
19.
});
20. </script>
21.
22. </head>
23. <body>
24. <h3>Formulaire ASP.NET MVC - Internationalisation</h3>
25. @using (Html.BeginForm("Action14Post", "Second"))
26. {
27.
<table>
http://tahe.developpez.com
186/362
28.
29.
30.
31.
32.
33.
34.
35.
36.
37. ...
38.
39. <tr>
<thead>
<tr>
<th>@MyResources.type</th>
<th>@MyResources.value</th>
<th>@MyResources.error</th>
</tr>
</thead>
<tbody>
<tr>
</tr>
Parce que la culture du thread courant est [en-US], le script embarqu dans la page aux lignes 15-20 est :
<script>
$(document).ready(function () {
var culture = 'en-US';
Globalize.culture(culture);
});
</script>
Cela assure que le framework de validation va travailler avec les formats amricains (date, monnaie, nombres, ...). Toujours pour la
mme raison, les messages des lignes 30-32 seront tirs du fichier de ressources [MyResources.en-US.resx] et seront donc en anglais.
6.5
Exemples d'excution
1
2
http://tahe.developpez.com
187/362
Si on regarde le code source de la page, on voit que ces messages d'erreur ont t embarqus dans la page, donc gnrs par la vue
ASP.NET [Action14Get] et son modle [ViewModel14] :
1.
2.
3.
<tr>
<td><label for="Reel1">Real number</label></td>
<td><input class="text-box single-line" data-val="true" data-valnumber="The field Real number must be a number." data-val-required="Required data"
id="Reel1" name="Reel1" type="text" value="0" /></td>
4.
<td><span class="field-validation-valid" data-valmsg-for="Reel1" datavalmsg-replace="true"></span></td>
5.
</tr>
6.
<tr>
7.
<td><label for="Reel2">Real number in range [10.2-11.3]</label></td>
8.
<td><input class="text-box single-line" data-val="true" data-valnumber="The field Real number in range [10.2-11.3] must be a number." data-valrange="Invalid data" data-val-range-max="11.3" data-val-range-min="10.2" data-valrequired="Required data" id="Reel2" name="Reel2" type="text" value="0" /></td>
9.
<td><span class="field-validation-valid" data-valmsg-for="Reel2" datavalmsg-replace="true"></span></td>
10. </tr>
6.6
L'internationalisation est un problme complexe. Ainsi regardons la proprit [Date1] et son calendrier :
On constate que le calendrier est un calendrier franais alors que la culture de la page est [en-US]. En HTML5 existe un attribut
[lang] permettant de fixer la langue de la page ou d'un composant de la page. On peut alors crire dans la vue [Action14Get.cshtml]
le code suivant :
1. @model Exemple_03.Models.ViewModel14
2. @using Exemple_03.Resources
3. @using System.Threading
4. @{
5.
Layout = null;
6.
var lang = Session["lang"] as string;
7. }
8.
9. <!DOCTYPE html>
10.
11. <html lang="@lang">
http://tahe.developpez.com
188/362
12. <head>
13. ...
Les tests montrent que le calendrier reste en franais mme lorsque la page est par ailleurs affiche en anglais. Il y a galement un
problme avec l'autre date du formulaire :
1
En [1], la date continue tre demande au format franais jj/mm/aaaa (20/11/2013) alors que le format amricain est
mm/dd/yyyy (10/21/2013). Nous allons essayer de rsoudre ces deux problmes avec une nouvelle vue et un nouveau modle de
vue.
JQuery UI est un projet driv du projet JQuery et offre des composants pour les formulaires dont un calendrier. Ce calendrier
peut tre internationalis. C'est ce que nous allons montrer.
Pour commencer, ajoutons [JQuery UI] notre projet.
http://tahe.developpez.com
189/362
2
1
Le calendrier JQuery UI est par dfaut en anglais. Pour tre internationalis, il faut ajouter des scripts qu'on peut trouver l'URL
[https://github.com/jquery/jquery-ui/tree/master/ui/i18n] :
Pour avoir le calendrier JQuery UI en franais, on copiera le contenu du fichier [jquery.ui.datepicker-fr.js] ci-dessus dans le dossier
[Scripts] du projet.
Le code de la nouvelle vue [Action15.cshtml] est obtenu par recopie de la vue prcdente [Action14.cshtml] puis modifi. Nous ne
prsentons que les modifications :
http://tahe.developpez.com
190/362
1. @model Exemple_03.Models.ViewModel15
2. @using Exemple_03.Resources
3. @using System.Threading
4. @{
5.
Layout = null;
6. }
7.
8. <!DOCTYPE html>
9.
10. <html lang="@Model.Culture">
11. <head>
12. <meta name="viewport" content="width=device-width" />
13. <title>Action15</title>
14. ...
15. <link rel="stylesheet" href="~/Content/themes/base/jquery-ui.css" />
16. <script type="text/javascript" src="~/Scripts/jquery-ui-1.10.3.js"></script>
17. <script type="text/javascript" src="~/Scripts/jquery.ui.datepicker-fr.js"></script>
18. <script>
19.
$(document).ready(function () {
20.
var culture = '@Thread.CurrentThread.CurrentCulture';
21.
Globalize.culture(culture);
22.
$("#Date1").datepicker($.datepicker.regional['@Model.Regionale']);
23.
});
24. </script>
25. </head>
26. <body>
27. <h3>@MyResources.titre</h3>
28. @using (Html.BeginForm("Action15", "Second"))
29. {
30.
<table>
31. ...
32.
<tr>
33.
<td>@Html.LabelFor(m => m.Date1)</td>
34.
<td>@Html.TextBox("Date1", Model.StrDate1)</td>
35.
<td>@Html.ValidationMessageFor(m => m.Date1)</td>
36.
</tr>
37.
</tbody>
38.
</table>
39.
<p>
40.
<input type="submit" value="Valider" />
41.
</p>
42. }
43. <!-- choix d'une langue -->
44. @using (Html.BeginForm("Lang", "Second"))
45. {
46.
<table>
47.
<tr>
48.
<td><a href="javascript:postForm('fr-FR','/Second/Action15')">Franais</a></td>
49.
<td><a href="javascript:postForm('en-US','/Second/Action15')">English</a></td>
50.
</tr>
51.
</table>
52. }
53. </body>
54. </html>
http://tahe.developpez.com
191/362
Son code est celui du modle [ViewModel14] lgrement modifi. Nous ne prsentons que les modifications :
1. using Exemple_03.Resources;
2. ...
3. using System.Web;
4.
5. namespace Exemple_03.Models
6. {
7.
[Bind(Exclude = "Culture,Regionale,StrDate1,FormatDate")]
8.
public class ViewModel15 : IValidatableObject
9.
{
10.
11. ...
12.
[Display(ResourceType = typeof(MyResources), Name = "date1")]
13.
[RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessageResourceType =
typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
14.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
15.
public string Regexp1 { get; set; }
16.
17.
[Display(ResourceType = typeof(MyResources), Name = "date2")]
18.
[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName =
"infoRequise")]
19.
[DataType(DataType.Date)]
20.
public DateTime Date1 { get; set; }
21.
22.
// constructeur
23.
public ViewModel15()
24.
{
25.
// Culture du moment
26.
Culture = HttpContext.Current.Session["lang"] as string;
27.
cultureInfo=new CultureInfo(Culture);
28.
// Rgionale du calendrier JQuery
29.
Regionale = MyResources.ResourceManager.GetObject("regionale",
cultureInfo).ToString();
http://tahe.developpez.com
192/362
30.
31.
// format de date
FormatDate = MyResources.ResourceManager.GetObject("formatDate",
cultureInfo).ToString();
32.
}
33.
34.
35.
36.
// validation
37.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
38.
{
39.
// liste des erreurs
40.
List<ValidationResult> rsultats = new List<ValidationResult>();
41.
// le mme msg d'erreur pour tous
42.
string errorMessage = MyResources.ResourceManager.GetObject("infoIncorrecte",
cultureInfo).ToString();
43. ...
44.
// Regexp1
45.
try
46.
{
47.
DateTime.ParseExact(Regexp1, FormatDate, cultureInfo);
48.
}
49.
catch
50.
{
51.
rsultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
52.
}
53.
54.
// on rend la liste des erreurs
55.
return rsultats;
56.
57.
}
58.
59.
// champs en-dehors du modle de l'action
60.
public string Culture { get; set; }
61.
public string Regionale { get; set; }
62.
public string StrDate1 { get; set; }
63.
public string FormatDate { get; set; }
64.
65.
// donnes locales
66.
private CultureInfo cultureInfo;
67. }
68. }
Par rapport au modle prcdent [ViewModel14], nous avons quatre proprits supplmentaires :
ligne 60 : la culture de la vue, 'fr-FR' ou 'en-US'. Cette culture est initialise dans le constructeur ligne 26 ;
ligne 61 : la culture rgionale du calendrier JQuery, 'fr' pour un calendrier franais, '' pour un calendrier anglais. Ce champ
est initialis par les ligne 29 du constructeur ;
ligne 63 : le format de la date de la ligne 15 : 'dd/MM/yyyy' pour une date franaise, 'MM/dd/yyyy' pour une date
anglaise. Ce champ est initialis ligne 31 du constructeur ;
ligne 62 : la chane de caractres afficher dans le champ de saisie de [Date1]. Ce champ sera initialis par l'action ;
ligne 47 : la date [Regexp1] est maintenant vrifie selon le format de la culture courante.
Les valeurs des proprits [Regionale] et [FormatDate] sont trouves dans les fichiers de ressources [MyResources]. Les fichiers de
ressources franais [MyResources] [MyResources.fr-FR] [1] et le fichier de ressources anglais [2] voluent comme suit :
Nous sommes presque prts. Nous ajoutons une action [Action15] au contrleur [SecondController] :
1.
http://tahe.developpez.com
// Action15
193/362
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19. }
ligne 2 : la mthode [Action15] traite aussi bien les [GET] que les [POST]. Dans ce dernier cas, les valeurs postes sont
rcupres dans le paramtre [formData] ;
ligne 5 : on rcupre la mthode HTTP de la requte ;
ligne 7 : on cre le modle de la vue qui va tre affiche (le formulaire) ;
lignes 8-11 : dans le cas d'une commande [GET], la zone de saisie de [Date1] est initialise avec une chane vide ;
lignes 12-16 : dans le cas d'une commande [POST] :
ligne 14 : le modle est initialis avec les valeurs postes,
ligne 15 : la zone de saisie de [Date1] est initialise avec une chane de caractres qui est la valeur de [Date1] formate
selon la culture courante [dd/MM/yyyy] pour une date franaise, [MM/dd/yyyy] pour une date anglaise ;
ligne 18 : la vue [Action15.cshtml] est affiche avec son modle.
6.7
Conclusion
Comme on l'aura compris, le thme de l'internationalisation d'une application est un thme complexe...
http://tahe.developpez.com
194/362
Application web
couche [web]
1
2a
Front Controller
Navigateur
4b
Vue1
Vue2
Vuen
Contrleurs/
Actions
2c
Modles
demande du navigateur ;
Navigateur
Application web
couche [web]
HTML
Front Controller
JS
4
Vue1
Vue2
Vuen
Contrleurs/
Actions
Modles
en [1], un vnement se produit dans la page affiche dans le navigateur (clic sur un bouton, changement d'un texte, ...).
Cet vnement est intercept par du Javascript (JS) embarqu dans la page ;
en [2], le code Javascript fait une requte HTTP comme l'aurait fait le navigateur mais il le fait l'insu de celui-ci. La
requte est asynchrone : l'utilisateur peut continuer interagir avec la page sans tre bloqu par l'attente de la rponse la
requte HTTP. La requte suit le processus classique de traitement. Rien (ou peu) ne la distingue d'une requte classique ;
en [3], une rponse est envoye au client JS. Plutt qu'une vue HTML complte, c'est plutt une vue HTML partielle, un
flux XML ou JSON (JavaScript Object Notation) qui est envoy ;
en [4], le Javascript rcupre cette rponse et l'utilise pour mettre jour une rgion de la page HTML affiche.
Pour l'utilisateur, il y a changement de vue car ce qu'il voit a chang. Il n'y a cependant pas rechargement total d'une page mais
simplement modification partielle de la page affiche. Cela contribue donner de la fluidit et de l'interactivit la page : parce qu'il
n'y a pas de rechargement total de la page, on peut se permettre de grer des vnements qu'auparavant on ne grait pas. Par
exemple, proposer l'utilisateur une liste d'options au fur et mesure qu'il saisit des caractres dans une bote de saisie. A chaque
http://tahe.developpez.com
195/362
nouveau caractre tap, une requte AJAX est faite vers le serveur qui renvoie alors d'autres propositions. Sans Ajax, ce genre d'aide
la saisie tait auparavant impossible. On ne pouvait pas recharger une nouvelle page chaque caractre tap.
7.2
Nous avons souvent associ la bibliothque Javascript JQuery nos page. On y trouve ainsi la ligne :
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
La technologie Ajax d'ASP.NET MVC utilise JQuery. Nous allons nous-mmes crire quelques scripts JQuery. Aussi prsentonsnous maintenant les rudiments de JQuery connatre pour comprendre les scripts de ce chapitre.
Nous crons un nouveau projet [Exemple-04] l'intrieur de notre solution [Exemples] :
Pour utiliser Ajax avec ASP.NET MVC, une ligne doit tre prsente dans le fichier de configuration [Web.config] [1] :
1.
<appSettings>
2. ...
3.
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
4. </appSettings>
La ligne 3 autorise l'utilisation d'Ajax dans les vues ASP.NET. Elle est prsente par dfaut.
Nous crons un fichier HTML [JQuery-01.html] dans le dossier [Content] du nouveau projet [2] :
http://tahe.developpez.com
196/362
3. <head>
4.
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5.
<title>JQuery-01</title>
6.
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
7. </head>
8. <body>
9.
<h3>Rudiments de JQuery</h3>
10. <div id="element1">
11.
Elment 1
12. </div>
13. </body>
14. </html>
Avec Google Chrome faire [Ctrl-Maj-I] pour faire apparatre les outils de dveloppement [6]. L'onglet [Console] [7] permet
d'excuter du code Javascript. Nous donnons dans ce qui suit des commandes Javascript taper et nous en donnons une
explication.
JS
rsultat
$("#element1")
$("#element1").text("blabla")
http://tahe.developpez.com
197/362
$("#element1").hide()
$("#element1")
$("#element1").attr('style','color:
red')
Tableau
http://tahe.developpez.com
198/362
Dictionnaire
On notera que l'URL du navigateur n'a pas chang pendant toutes ces manipulations. Il n'y a pas eu d'changes avec le serveur web.
Tout se passe l'intrieur du navigateur. Maintenant, visualisons le code source de la page :
1. <!DOCTYPE html>
2. <html xmlns="http://www.w3.org/1999/xhtml">
3. <head>
4.
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5.
<title>JQuery-01</title>
6.
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
7. </head>
8. <body>
9.
<h3>Rudiments de JQuery</h3>
10. <div id="element1">
11.
Elment 1
12. </div>
13. </body>
14. </html>
C'est le texte initial. Il ne reflte en rien les manipulations que l'on a faites sur l'lment des lignes 10-12. Il est important de s'en
souvenir lorsqu'on fait du dbogage Javascript. Il est alors souvent inutile de visualiser le code source de la page affiche.
Nous en savons assez pour comprendre les scripts JS qui vont suivre.
7.3
7.3.1
http://tahe.developpez.com
199/362
6
2
4
3
7.3.2
[HttpGet]
public ViewResult Action01Get()
{
ViewModel01 modle = new ViewModel01();
modle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modle);
http://tahe.developpez.com
200/362
1.
2.
3.
4.
5.
6.
7.
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliparB, AdivisparB, Erreur, HeureChargement,
HeureCalcul")]
8.
public class ViewModel01
9.
{
10.
// formulaire
11.
[Required(ErrorMessage="Donne requise")]
12.
[Display(Name="Valeur de A")]
13.
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
14.
public double A { get; set; }
15.
[Required(ErrorMessage = "Donne requise")]
16.
[Display(Name = "Valeur de B")]
17.
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
18.
public double B { get; set; }
19.
20.
// rsultats
21.
public string AplusB { get; set; }
22.
public string AmoinsB { get; set; }
23.
public string AmultipliparB { get; set; }
24.
public string AdivisparB { get; set; }
25.
public string Erreur { get; set; }
26.
public string HeureChargement { get; set; }
27.
public string HeureCalcul { get; set; }
28. }
29. }
1. @model Exemple_04.Models.ViewModel01
2. @{
3.
Layout = null;
4.
AjaxOptions ajaxOpts = new AjaxOptions
5.
{
6.
UpdateTargetId = "rsultats",
7.
HttpMethod = "post",
http://tahe.developpez.com
201/362
8.
Url = Url.Action("Action01Post"),
9.
LoadingElementId = "loading",
10.
LoadingElementDuration = 1000
11. };
12. }
13.
14. <!DOCTYPE html>
15.
16. <html lang="fr-FR">
17. <head>
18. <meta name="viewport" content="width=device-width" />
19. <title>Ajax-01</title>
20. <link rel="stylesheet" href="~/Content/Site.css" />
21. <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
22. <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
23. <script type="text/javascript"
src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
24. <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
25. <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.frFR.js"></script>
26. <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.enUS.js"></script>
27. <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
28. <script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
29. </head>
30. <body>
31.
32. <h2>Ajax - 01</h2>
33. <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
34. <h4>Oprations arithmtiques sur deux nombres rels A et B positifs ou nuls</h4>
35. @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
36. {
37.
<table>
38.
<thead>
39.
<tr>
40.
<th>@Html.LabelFor(m => m.A)</th>
41.
<th>@Html.LabelFor(m => m.B)</th>
42.
</tr>
43.
</thead>
44.
<tbody>
45.
<tr>
46.
<td>@Html.TextBoxFor(m => m.A)</td>
47.
<td>@Html.TextBoxFor(m => m.B)</td>
48.
</tr>
49.
<tr>
50.
<td>@Html.ValidationMessageFor(m => m.A)</td>
51.
<td>@Html.ValidationMessageFor(m => m.B)</td>
52.
</tr>
53.
</tbody>
54.
</table>
55.
<p>
56.
<input type="submit" value="Calculer" />
57.
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
58.
<a href="javascript:postForm()">Calculer</a>
59.
</p>
60. }
61. <hr />
62. <div id="rsultats" />
63. </body>
64. </html>
http://tahe.developpez.com
202/362
http://tahe.developpez.com
203/362
ligne 15 : au lieu d'utiliser [@Html.BeginForm], on utilise [@Ajax.BeginForm]. Cette mthode admet de nombreuses
surcharges. Celle utilise a la signature suivante :
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions,
IDictionary<string,object> htmlAttributes)
ligne 1 : la balise <form> gnre. On notera les attributs [data-ajax-attr] qui refltent les valeurs des champs de l'objet de
type [AjaxOptions] qui a t associ la requte Ajax. Ces attributs sont grs par la bibliothque Ajax. Sans eux, la balise
<form> devient :
8.
</form>
On a alors affaire un formulaire HTML classique. C'est ce code qui sera excut si l'utilisateur dsactive le Javascript sur
son navigateur.
7.3.3
L'action [Action01Post]
[HttpPost]
public PartialViewResult Action01Post(FormCollection postedData, SessionModel
session)
3.
{
http://tahe.developpez.com
204/362
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32. }
7.3.4
// simulation attente
Thread.Sleep(2000);
// instanciation modle de l'action
ViewModel01 modle = new ViewModel01();
// l'heure de calcul
modle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// mise jour du modle
TryUpdateModel(modle, postedData);
if (!ModelState.IsValid)
{
// on retourne une erreur
modle.Erreur = getErrorMessagesFor(ModelState);
return PartialView("Action01Error", modle);
}
// une fois sur deux, on simule une erreur
int val = session.Randomizer.Next(2);
if (val == 0)
{
modle.Erreur = "[erreur alatoire]";
return PartialView("Action01Error", modle);
}
// calculs
modle.AplusB = string.Format("{0}", modle.A + modle.B);
modle.AmoinsB = string.Format("{0}", modle.A - modle.B);
modle.AmultipliparB = string.Format("{0}", modle.A * modle.B);
modle.AdivisparB = string.Format("{0}", modle.A / modle.B);
// vue
return PartialView("Action01Success", modle);
[FormCollection postedData] : l'ensemble des valeurs postes par la requte Ajax POST,
[SessionModel session] : les lments de la session. On utilise ici une technique dcrite au paragraphe 3,
page 92 ;
ligne 2 : l'action va rendre un fragment HTML et non une page HTML complte ;
ligne 5 : artificiellement, on s'arrte deux secondes pour simuler une action Ajax longue ;
ligne 7 : un modle de type [ViewModel01] est instanci ;
ligne 9 : l'heure de calcul est initialise ;
ligne 11 : on essaie de mettre jour le modle de type [ViewModel01] avec les valeurs postes. On rappelle qu'il y en a
deux : les valeurs des nombres A et B ;
ligne 12 : on vrifie le succs ou non de cette mise jour ;
ligne 15 : en cas d'erreur on renseigne le champ [Erreur] du modle ;
ligne 16 : on rend une vue partielle [Action01Error.cshtml] exploitant le modle [ViewModel01] ;
lignes 19-24 : une fois sur deux, on simule une erreur ;
ligne19 : on gnre un nombre entier alatoire dans l'intervalle [0,1]. Le gnrateur de nombres est pris dans la session ;
ligne 20 : si la valeur gnre est 0, on simule une erreur ;
ligne 22 : le message d'erreur est mis dans le modle ;
ligne 23 : on rend une vue partielle [Action01Error.cshtml] exploitant le modle [ViewModel01] ;
lignes 26-29 : les calculs arithmtiques sur les nombres A et B sont faits et les rsultats placs dans le modle sous forme
de chanes de caractres ;
ligne 31 : on rend une vue partielle [Action01Success.cshtml] exploitant le modle [ViewModel01] ;
La vue [Action01Error]
@model Exemple_04.Models.ViewModel01
<h4>Rsultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'est produite : @Model.Erreur</p>
http://tahe.developpez.com
205/362
On rappelle que ce flux HTML partiel va tre envoy en rponse la requte HTTP Ajax de type POST et tre plac dans la page
dans la rgion d'id [rsultats]. Tous ces renseignements viennent de la configuration Ajax utilise dans la page principale
[Action01Get.cshtml] :
1. @model Exemple_04.Models.ViewModel01
2. @{
3.
Layout = null;
4.
AjaxOptions ajaxOpts = new AjaxOptions
5.
{
6.
UpdateTargetId = "rsultats",
7.
HttpMethod = "post",
8.
Url = Url.Action("Action01Post"),
9.
LoadingElementId = "loading",
10.
LoadingElementDuration = 1000
11. };
12. }
7.3.5
La vue [Action01Success]
@model Exemple_04.Models.ViewModel01
<h4>Rsultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliparB</p>
<p>A/B=@Model.AdivisparB</p>
L encore, ce flux HTML partiel va tre envoy en rponse la requte HTTP Ajax de type POST et tre plac dans la page dans la
rgion d'id [rsultats] :
http://tahe.developpez.com
206/362
7.3.6
Gestion de la session
Nous avons vu que [Action01Post] utilisait la session. Le modle de la session est le type [SessionModel] suivant :
1.
2.
3.
4.
5.
6.
7.
8.
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
public Random Randomizer { get; set; }
}
}
// Session
protected void Session_Start()
{
SessionModel sessionModel=new SessionModel();
sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
Session["data"] = sessionModel;
http://tahe.developpez.com
207/362
7.3.7
1. @model Exemple_04.Models.ViewModel01
2. @{
3.
Layout = null;
4.
AjaxOptions ajaxOpts = new AjaxOptions
5.
{
6. ...
7.
LoadingElementId = "loading",
8.
LoadingElementDuration = 1000
9.
};
10. }
11.
12. ...
13. <body>
14.
15. ...
16. @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
17. {
18. ...
19.
<p>
20.
<input type="submit" value="Calculer" />
21.
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
22.
<a href="javascript:postForm()">Calculer</a>
23.
</p>
24. }
25. ...
Lorsque la requte Ajax dmarre, la rgion d'id [loading] ligne 7 est affiche au bout d'une seconde [ligne 8]. Cette rgion est l'image
de la ligne 21 initialement cache. Cela donne l'interface suivante :
7.3.8
http://tahe.developpez.com
208/362
19.
</p>
20. }
21. <hr />
22. <div id="rsultats" />
ligne 18 : un clic sur le lien [Calculer] provoque l'excution de la fonction JS [postForm]. Celle-ci est dfinie dans le fichier
[myScripts-01.js] de la ligne 5. Ce script est le suivant :
1. function postForm() {
2.
// on fait un appel Ajax la main avec JQuery
3.
var loading = $("#loading");
4.
var formulaire = $("#formulaire");
5.
var rsultats = $('#rsultats');
6.
$.ajax({
7.
url: '/Premier/Action01Post',
8.
type: 'POST',
9.
data: formulaire.serialize(),
10.
dataType: 'html',
11.
begin: loading.show(),
12.
success: function (data) {
13.
loading.hide()
14.
rsultats.html(data);
15.
}
16. })
17. }
18.
19. // http://blog.instance-factory.com/?p=268
20. $.validator.methods.number = function (value, element) {
21. return this.optional(element) ||
22.
!isNaN(Globalize.parseFloat(value));
23. }
24.
25. $.validator.methods.date = function (value, element) {
26. return this.optional(element) ||
27.
!isNaN(Globalize.parseDate(value));
28. }
29.
30. jQuery.extend(jQuery.validator.methods, {
31. range: function (value, element, param) {
32.
//Use the Globalization plugin to parse the value
33.
var val = Globalize.parseFloat(value);
34.
return this.optional(element) || (
35.
val >= param[0] && val <= param[1]);
http://tahe.developpez.com
209/362
36. }
37. });
Les fonctions des lignes 19-37 ont t dj rencontres au paragraphe 6.1, page 168. Elles grent l'internationalisation des pages.
Nous ne revenons pas dessus. Lignes 1-17, nous faisons la main l'appel Ajax qui dans le cas du bouton [Calculer] tait fait par la
bibliothque Ajax associe au projet. Pour cela, nous utilisons la bibliothque JQuery associe au projet.
ligne 3 : une rfrence sur le composant d'id [loading]. [$("#loading")] ramne la collection des lments d'id [loading]. Il
n'y en a qu'un ;
ligne 4 : une rfrence sur le composant d'id [formulaire] ;
ligne 5 : une rfrence sur le composant d'id [rsultats] ;
ligne 6 : l'appel Ajax avec ses options ;
ligne 7 : l'URL cible de l'appel Ajax ;
ligne 8 : la mthode HTTP utilise ;
ligne 9 : les donnes postes. [formulaire.serialize] cre la chane [A=val1&B=val2] du POST du formulaire d' id
[formulaire] ;
ligne 10 : le type de donnes attendu en retour. On sait que le serveur va renvoyer un flux HTML ;
ligne 11 : la mthode excuter lorsque la requte dmarre. Ici, on indique qu'il faut afficher le composant d'id [loading].
C'est l'image anime d'attente ;
ligne 12 : la mthode excuter en cas de succs de la requte Ajax. Le paramtre [data] est la rponse complte du
serveur. On sait que c'est un flux HTML ;
ligne 13 : on cache le signal d'attente ;
ligne 14 : on met jour le composant d'id [rsultats] avec le HTML du paramtre [data].
Le lecteur est invit tester le lien [Calculer]. Il fonctionne comme le bouton [Calculer] une anomalie prs. Une fois qu'on a utilis
ce lien, on peut alors poster des valeurs de A et B invalides :
1
3
en [1] et [2], on a entr des valeurs invalides. Elles sont signales par les validateurs ct client ;
en [3], on a cliqu sur le lien [Calculer] ;
en [4], il y a eu un [POST] puisqu'on obtient la rponse [4].
Lorsque les valeurs sont invalides et qu'on clique sur le bouton [Calculer], le [POST] vers le serveur n'a pas lieu. Dans le mme cas,
avec le lien [Calculer] le [POST] vers le serveur a lieu. Il y a donc un comportement du bouton [Calculer] qu'on n'a pas su
reproduire avec le lien [Calculer]. Plutt que d'essayer de rsoudre ce problme maintenant, nous le laissons pour un exemple
ultrieur qui illustrera galement un autre problme de validation ct client.
http://tahe.developpez.com
210/362
7.4
Dans l'exemple prcdent, le serveur web rpondait la requte HTTP Ajax par un flux HTML. Dans ce flux, il y avait des donnes
accompagnes par du formatage HTML. On se propose de reprendre l'exemple prcdent avec cette fois-ci des rponses JSON
(JavaScript Object Notation) ne contenant que les donnes. L'intrt est qu'on transmet ainsi moins d'octets.
7.4.1
L'action [Action02Get]
L'action [Action02Get] sera le point d'entre de la nouvelle application. Son code est le suivant :
1. @model Exemple_04.Models.ViewModel02
2. @{
3.
Layout = null;
4.
AjaxOptions ajaxOpts = new AjaxOptions
5.
{
6.
HttpMethod = "post",
7.
Url = Url.Action("Action02Post"),
8.
LoadingElementId = "loading",
9.
LoadingElementDuration = 1000,
10.
OnBegin = "OnBegin",
11.
OnFailure = "OnFailure",
12.
OnSuccess = "OnSuccess",
13.
OnComplete = "OnComplete"
14. };
15. }
16.
17. <!DOCTYPE html>
18.
19. <html lang="fr-FR">
20. <head>
21. <meta name="viewport" content="width=device-width" />
22. <title>Ajax-02</title>
23. ....
24. <script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
25. </head>
26. <body>
27. <h2>Ajax - 02</h2>
28. <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
29. <h4>Oprations arithmtiques sur deux nombres rels A et B positifs ou nuls</h4>
30. @using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
31. {
32. ...
33.
<p>
34.
<input type="submit" value="Calculer" />
35.
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
36.
<a href="javascript:postForm()">Calculer</a>
37.
</p>
38. }
39. <hr />
40. <div id="entete">
41.
<h4>Rsultats</h4>
42.
<p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
43. </div>
44. <div id="rsultats">
45.
<p>A+B=<span id="AplusB"/></p>
46.
<p>A-B=<span id="AmoinsB"/></p>
47.
<p>A*B=<span id="AmultipliparB"/></p>
48.
<p>A/B=<span id="AdivisparB"/></p>
49. </div>
50. <div id="erreur">
51.
<p style="color: red;">Une erreur s'est produite : <span id="msg"/></p>
52. </div>
53. </body>
http://tahe.developpez.com
211/362
54. </html>
7.4.2
L'action [Action02Post]
ligne 31 : mme dmarche pour les rsultats arithmtiques. Cette fois, la chane JSON envoye au navigateur aura la forme
suivante :
{"Erreur":"","AplusB":"4","AmoinsB":"2","AmultipliparB":"3","AdivisparB":"0,333333333333333","HeureCalcul":"05:52:17"}
http://tahe.developpez.com
212/362
7.4.3
Rappelons la configuration de l'appel Ajax dans la page HTML envoye au navigateur client :
1.
AjaxOptions ajaxOpts = new AjaxOptions
2.
{
3.
HttpMethod = "post",
4.
Url = Url.Action("Action02Post"),
5.
LoadingElementId = "loading",
6.
LoadingElementDuration = 1000,
7.
OnBegin = "OnBegin",
8.
OnFailure = "OnFailure",
9.
OnSuccess = "OnSuccess",
10.
OnComplete = "OnComplete"
11. };
Les fonctions JS rfrences aux lignes 7-10 ( droite du signe =) sont dfinies dans le fichier [myScripts-02.js] suivant :
1. // donnes globales
2. var entete;
3. var loading;
4. var rsultats;
5. var erreur;
6. var heureCalcul;
7. var msg;
8. var AplusB;
9. var AmoinsB;
10. var AmultipliparB;
11. var AdivisparB;
12. var formulaire;
13. ...
14. function postForm() {
15. ...
16. }
17.
18. // au chargement du document
19. $(document).ready(function () {
20. formulaire = $("#formulaire");
21. entete = $("#entete");
22. loading = $("#loading");
23. erreur = $("#erreur");
24. rsultats = $('#rsultats');
25. heureCalcul = $("#heureCalcul");
26. msg = $("#msg");
27. AplusB = $("#AplusB");
28. AmoinsB = $("#AmoinsB");
29. AmultipliparB = $("#AmultipliparB");
30. AdivisparB = $("#AdivisparB");
31.
32. // on cache certains lments de la page
33. entete.hide();
34. rsultats.hide();
35. erreur.hide();
36. });
37.
38. // dmarrage
39. function OnBegin() {
40. ....
41. }
42.
43. // fin de la requte
44. function OnComplete() {
45. ...
46. }
http://tahe.developpez.com
213/362
47.
48. // russite
49. function OnSuccess(data) {
50. ....
51. }
52.
53. // erreur
54. function OnFailure(request, error) {
55. ...
56. }
57.
Pour comprendre ce code il faut se rappeler les deux textes JSON susceptibles d'tre envoys en rponse au navigateur :
{"Erreur":"[erreur alatoire]","HeureCalcul":"05:31:37"}
http://tahe.developpez.com
214/362
Si on appelle [data] cette chane, la valeur du champ [Erreur] est obtenue par la notation [data.Erreur] ou [data["Erreur"]], au
choix. Idem pour les autres champs de la chane JSON. Par ailleurs, pour affecter un texte non format un composant d' id X, on
crit [X.text(chaine)]. Revenons au code de la fonction [OnSuccess] :
ligne 2 : [data] est la chane JSON reue ;
ligne 4 : le composant [heureCalcul] reoit sa valeur ;
ligne 5 : le composant [entete] est affich ;
ligne 6 : test du champ [Erreur] de la chane JSON ;
ligne 7 : le composant [msg] reoit sa valeur ;
ligne 8 : le composant [erreur] est affich ;
ligne 9 : c'est termin pour le cas d'erreur ;
ligne 12 : le composant [AplusB] reoit sa valeur ;
ligne 13 : le composant [AmoinsB] reoit sa valeur ;
ligne 14 : le composant [AmultipliparB] reoit sa valeur ;
ligne 15 : le composant [AdivisparB] reoit sa valeur ;
ligne 16 : le composant [rsultats] est affich.
La fonction [OnFailure] sera excute en cas d'chec de la requte HTTP Ajax. Cet chec est mesur avec le code HTTP renvoy
par le serveur. Le code 500 [Internal Server Error] par exemple indique que le serveur n'a pu excuter la requte. La fonction
[OnFailure] est la suivante :
1. // erreur
2. function OnFailure(request, error) {
3.
alert("L'erreur suivante s'est produite :" + error);
4. }
On se contente d'afficher une bote de dialogue avec l'erreur qui s'est produite. Dans la pratique, il faudrait tre plus prcis. Nous
allons bientt proposer une autre solution.
Enfin la fonction [OnComplete] est excute lorsque la requte est termine que ce soit sur une russite ou un chec.
1. // fin de la requte
2. function OnComplete() {
3.
// signal d'attente teint
4.
loading.hide();
5. }
On rappelle ici que c'est la configuration de l'appel Ajax dans la vue [Action02Get.cshtml] qui fait que ces diffrentes fonctions sont
appeles :
1.
AjaxOptions ajaxOpts = new AjaxOptions
2.
{
3. ...
4.
OnBegin = "OnBegin",
5.
OnFailure = "OnFailure",
6.
OnSuccess = "OnSuccess",
7.
OnComplete = "OnComplete"
8.
7.4.4
};
Le lien [Calculer]
http://tahe.developpez.com
215/362
3.
$.ajax({
4.
url: '/Premier/Action02Post',
5.
type: 'POST',
6.
data: formulaire.serialize(),
7.
dataType: 'json',
8.
beforeSend: OnBegin,
9.
success: OnSuccess,
10.
error: OnFailure,
11.
complete: OnComplete
12. })
13. }
Revenons sur la fonction Javascript qui gre le cas o l'appel Ajax choue (ligne 10). L'appel Ajax choue dans diverses situations,
par exemple lorsque le serveur renvoie un code d'erreur tel que [403 Forbidden], [404 Not Found], [500 Internal Server Error] [301
Moved Permanently], ...
Dans l'exemple de la page 215, la fonction [OnFailure] est la suivante :
1. // erreur
2. function OnFailure(request, error) {
3.
alert("L'erreur suivante s'est produite :" + error);
4. }
Gnralement, l'affichage de l'objet [error] n'apporte aucune information. Si on utilise un appel Ajax fait avec JQuery, on peut
utiliser la mthode [OnFailure] suivante ;
1. // erreur
2. function OnFailure(jqXHR) {
3.
alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
4.
msg.html(jqXHR.responseText);
5.
erreur.show();
6. }
Pour tester cette fonction d'erreur, nous allons crer artificiellement une exception dans l'action [Action02Post] :
1.
2.
3.
4.
5.
6.
7.
http://tahe.developpez.com
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// une exception artificielle pour tester la fonction d'erreur de l'appel Ajax
throw new Exception();
// simulation attente
Thread.Sleep(2000);
216/362
8.
9. ...
// validation modle
http://tahe.developpez.com
217/362
La rponse du serveur nous permet de voir quelle ligne du code serveur s'est produite l'erreur. C'est souvent une information utile
connatre. Dornavant nous utiliserons cette technique pour grer les erreurs des appels Ajax.
7.5
Voici un exemple basique d'une telle application. La nouvelle application aura deux vues :
3
1
[HttpGet]
public ViewResult Action03Get()
{
return View();
http://tahe.developpez.com
218/362
1. @{
2.
Layout = null;
3. }
4.
5. <!DOCTYPE html>
6.
7. <html>
8. <head>
9.
<meta name="viewport" content="width=device-width" />
10. <title>Action03Get</title>
11. <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
12. <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
13. </head>
14. <body>
15. <h3>Ajax - 03 - Single Page Application</h3>
16. <div id="content">
17.
@Html.Partial("Page1")
18. </div>
19. </body>
20. </html>
lignes 16-18 : un lment d'id [content]. C'est dans cet lment que les diffrentes pages vont s'afficher ;
ligne 17 : par dfaut c'est la page [Page1.cshtml] qui va d'abord s'afficher.
Voyons ce qui se passe lorsque le lien est cliqu. L'URL [/Premier/Action04?Page=2] est demande avec un GET. L'action
[Action04] est alors excute :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10. }
http://tahe.developpez.com
[HttpGet]
public PartialViewResult Action04(string page = "1")
{
string vue = "Page1";
if (page == "2")
{
vue = "Page2";
}
return PartialView(vue);
219/362
Le serveur renvoie donc le flux HTML ci-dessus comme rponse l'appel Ajax GET [/Premier/Action04?Page=2]. On se rappelle
que cet appel Ajax utilise cette rponse pour mettre jour la rgion d'id [content] (ligne 3 ci-dessous) :
1. <h4>Page 1</h4>
2.
<p>
3.
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions()
{ UpdateTargetId = "content" })
4. </p>
En suivant le mme raisonnement, on voit que le clic sur le lien [Page 1] de [1] va provoquer l'affichage [2].
Revenons au schma gnral d'une application ASP.NET MVC :
Navigateur
Application web
couche [web]
HTML
JS
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couches
[mtier, DAO,
ORM]
Donnes
Grce au Javascript embarqu dans les pages HTML et excut dans le navigateur, on peut dporter du code sur le navigateur et
aboutir l'architecture suivante :
http://tahe.developpez.com
220/362
Application web
couche [web]
Front Controller
Vue1
Vue2
Vuen
Contrleurs/
Actions
couches
[DAO, ORM]
Donnes
Modles
2
Utilisateur
Couche
[prsentation]
Couche
[mtier]
Couche
[DAO]
Navigateur
en [1], la couche Web ASP.NET MVC est devenue une interface web d'accs aux donnes, gnralement loges dans une
base de donnes. Les vues dlivres ne contiennent que des donnes et aucun habillage HTML, par exemple des flux
XML ou JSON ;
en [2] : le code Javascript embarqu dans les pages HTML peut tre structur en couches :
la couche [DAO] s'occupe de l'accs aux donnes via le serveur web [1] ,
la couche [mtier] correspond la couche [mtier] qui auparavant tait sur le serveur [1] et qui a t
dporte sur le navigateur [2] ;
L'intrt de cette architecture est qu'elle met en jeu des comptences diffrentes :
le code du serveur web [1] ncessite des comptences .NET mais pas de comptences Javascript, HTML, CSS ;
le code embarqu dans le navigateur [2] ncessite des comptences Javascript, HTML, CSS mais est indiffrent la
technologie du serveur web [1].
Ainsi, cette architecture facilite-t-elle le travail en parallle d'quipes aux comptences diffrentes. Elle s'applique particulirement
bien aux Applications Page Unique.
7.6
Nous avons voqu prcdemment une anomalie sur l'exemple Ajax-01 (page 210). Nous en rappelons le contexte :
http://tahe.developpez.com
221/362
1
3
en [1] et [2], on a entr des valeurs invalides. Elles sont signales par les validateurs ct client ;
en [3], on a cliqu sur le lien [Calculer] ;
en [4], il y a eu un [POST] puisqu'on obtient la rponse [4].
Lorsque les valeurs sont invalides et qu'on clique sur le bouton [Calculer], le [POST] vers le serveur n'a pas lieu. Dans le mme cas,
avec le lien [Calculer] le [POST] vers le serveur a lieu. Il y a donc un comportement du bouton [Calculer] qu'on n'a pas su
reproduire avec le lien [Calculer].
Nous allons reprendre cet exemple dans un nouveau contexte : l'application aura plusieurs vues mais sera du type [Application
Page Unique] que nous venons de dcrire.
7.6.1
3
2
http://tahe.developpez.com
222/362
L'application est page unique : celle-ci est charge par le navigateur lors de la premire requte. Elle est ensuite mise jour par des
appels Ajax.
Les pages prcdentes sont gnres par les vues [cshtml] suivantes :
http://tahe.developpez.com
223/362
15.
<script type="text/javascript"
src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
16. <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
17. <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.frFR.js"></script>
18. <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.enUS.js"></script>
19. <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
20. <script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
21. </head>
22. <body>
23.
24. <h2>Ajax - 05, Page unique - Validation formulaire ct client</h2>
25. <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
26. <h4>Oprations arithmtiques sur deux nombres rels A et B positifs ou nuls</h4>
27. <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
28. <div id="content">
29.
@Html.Partial("Formulaire05", Model)
30. </div>
31. </body>
32. </html>
ligne 1 : le modle de la vue est le type [ViewModel05] que nous allons prsenter prochainement ;
lignes 13-19 : on retrouve les scripts Javascript ncessaires pour faire de l'Ajax et de la validation ct client ;
ligne 20 : nous allons ajouter nos propres fonctions Javascript dans [myScripts-05.js] ;
ligne 27 : l'image anime d'attente ;
lignes 28-30 : une balise d'id [content]. C'est dans cette balise que les vues partielles [Formulaire05, Success05, Failure05]
vont venir s'insrer ;
ligne 29 : insertion de la vue partielle [Formulaire05].
La vue partielle [Formulaire05] va gnrer la partie [2] ci-dessus. Son code est le suivant :
1. @model Exemple_04.Models.ViewModel05
2.
3. @using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id =
"formulaire" }))
4. {
5.
<table>
6.
<thead>
7.
<tr>
8.
<th>@Html.LabelFor(m => m.A)</th>
http://tahe.developpez.com
224/362
9.
<th>@Html.LabelFor(m => m.B)</th>
10.
</tr>
11.
</thead>
12.
<tbody>
13.
<tr>
14.
<td>@Html.TextBoxFor(m => m.A)</td>
15.
<td>@Html.TextBoxFor(m => m.B)</td>
16.
</tr>
17.
<tr>
18.
<td>@Html.ValidationMessageFor(m => m.A)</td>
19.
<td>@Html.ValidationMessageFor(m => m.B)</td>
20.
</tr>
21.
</tbody>
22. </table>
23. <p>
24.
<table>
25.
<tbody>
26.
<tr>
27.
<td><a href="javascript:calculer()">Calculer</a>
28.
</td>
29.
<td style="width: 20px" />
30.
<td><a href="javascript:effacer()">Effacer</a>
31.
</td>
32.
</tr>
33.
</tbody>
34.
</table>
35. </p>
36. }
On notera que le formulaire n'a pas de bouton de type [submit]. Nous serons amens faire la main le [Post] des valeurs A et B
saisies.
S'il n'y a pas d'erreurs, les rsultats sont affichs :
http://tahe.developpez.com
225/362
La partie [4] ci-dessus est gnre par la vue partielle [Success05.cshtml] suivante :
1. @model Exemple_04.Models.ViewModel05
2. <hr />
3. <p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
4. <p>A=@Model.A</p>
5. <p>B=@Model.B</p>
6. <h4>Rsultats</h4>
7. <p>A+B=@Model.AplusB</p>
8. <p>A-B=@Model.AmoinsB</p>
9. <p>A*B=@Model.AmultipliparB</p>
10. <p>A/B=@Model.AdivisparB</p>
11. <p>
12. <a href="javascript:retourSaisies()">Retour aux saisies</a>
13. </p>
http://tahe.developpez.com
226/362
7.6.2
1.
2.
3.
4.
5.
6.
7.
8.
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel.DataAnnotations;
System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliparB, AdivisparB, Erreurs, HeureChargement,
HeureCalcul")]
http://tahe.developpez.com
227/362
9.
public class ViewModel05
10. {
11.
// formulaire
12.
[Required(ErrorMessage="Donne A requise")]
13.
[Display(Name="Valeur de A")]
14.
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
15.
public string A { get; set; }
16.
[Required(ErrorMessage = "Donne B requise")]
17.
[Display(Name = "Valeur de B")]
18.
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
19.
public string B { get; set; }
20.
21.
// rsultats
22.
public string AplusB { get; set; }
23.
public string AmoinsB { get; set; }
24.
public string AmultipliparB { get; set; }
25.
public string AdivisparB { get; set; }
26.
public List<string> Erreurs { get; set; }
27.
public string HeureChargement { get; set; }
28.
public string HeureCalcul { get; set; }
29. }
30. }
7.6.3
lignes 15 et 19 : les champs A et B sont dsormais de type [string] afin d'afficher des champs de saisie vides plutt que des
champs avec la valeur 0, lors de l'affichage initial du formulaire de saisie ;
lignes 14 et 18 : cela n'empche pas de vrifier la valeur saisie avec un validateur [Range] ;
ligne 26 : une liste de messages d'erreurs affiche par la vue [Failure05].
Page 207, paragraphe 7.3.6, nous avons vu que les donnes de la session taient encapsules dans le modle [SessionModel] suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// le gnrateur de nombres alatoires
public Random Randomizer { get; set; }
}
}
http://tahe.developpez.com
228/362
10.
public string B { get; set; }
11. }
12. }
Il est en effet ncessaire de mmoriser les valeurs de A et B dans la session comme le montre la squence suivante :
Requte 1
Requte 2
En [4], on retrouve les saisies faites en [1]. Or il y a deux requtes HTTP distinctes. On sait que la mmoire entre deux requtes
HTTP est la session. Pour que la seconde puisse retrouver les valeurs postes par la premire, il faut donc que ces dernires soient
mises en session.
http://tahe.developpez.com
229/362
7.6.4
L'action [Action05Get] est l'action qui fait afficher la page unique initiale. Son code est le suivant :
1.
2.
3.
4.
5.
6.
7. }
[HttpGet]
public ViewResult Action05Get()
{
ViewModel05 modle = new ViewModel05();
modle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modle);
ligne 6 : la vue [Action05Get.cshtml] dj tudie est affiche avec un modle de type [ViewModel05] ;
7.6.5
http://tahe.developpez.com
230/362
19.
beforeSend: function () {
20.
loading.show();
21.
},
22.
success: function (data) {
23.
content.html(data);
24.
},
25.
complete: function () {
26.
loading.hide();
27.
},
28.
error: function (jqXHR) {
29.
// affichage rponse serveur
30.
content.html(jqXHR.responseText);
31.
}
32. })
33. }
34.
35. function retourSaisies() {
36. ...
37. }
38.
39. function effacer() {
40. ...
41. }
42.
43. // au chargement du document
44. $(document).ready(function () {
45. // on rcupre les rfrences des diffrents composants de la page
46. loading = $("#loading");
47. content = $("#content");
48. // on cache l'image anime
49. loading.hide();
50. });
on rappelle que le code Javascript est toujours excut ct client, dans le navigateur ;
ligne 44 : la fonction JS excute lorsque le chargement initial de la page unique est termin ;
ligne 46 : rfrence sur l'image anime d'id [loading] ;
ligne 47 : rfrence sur la rgion d'id [content]. C'est cette rgion qui reoit les vues partielles [Formulaire05, Success05,
Failure05] ;
lignes 2-3 : les variables des lignes 46-47 sont dclares globales afin que les autres fonctions y aient accs. Il y a un cot
rechercher des lments dans une page (lignes 46-47). Il n'y a pas lieu de rpter cette recherche si on peut l'viter ;
ligne 5 : la fonction [calculer] ;
ligne 7 : on rcupre une rfrence sur le formulaire. La vue partielle [Formulaire05] lui a donn l'id [formulaire] ;
ligne 9 : cette instruction excute les validateurs du formulaire ct client. C'est ce qui manquait dans l'anomalie constate
page 210. Cette mthode a t trouve sur internet aprs une recherche sur la bibliothque [jquery.unobstrusive-ajax]
utilise par la page unique :
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
Ce flux HTML est celui envoy par l'action [Action05FaireCalcul] cible par l'appel Ajax. Le code de cette action ct serveur est le
suivant :
1.
2.
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData,
SessionModel session)
http://tahe.developpez.com
231/362
3.
{
4.
// modle
5.
ViewModel05 modle = new ViewModel05();
6.
// l'heure de calcul
7.
modle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
8.
// mise jour du modle
9.
TryUpdateModel(modle, postedData);
10.
if (!ModelState.IsValid)
11.
{
12.
// on retourne une erreur
13.
modle.Erreurs = getListOfMessagesFor(ModelState);
14.
return PartialView("Failure05", modle);
15.
}
16. ...
17. }
1. @model Exemple_04.Models.ViewModel05
2. <hr />
3. <p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
4. <p>A=@Model.A</p>
5. <p>B=@Model.B</p>
6. <h2>Les erreurs suivantes se sont produites</h2>
7. <ul>
8.
@foreach (string msg in Model.Erreurs)
9.
{
10.
<li>@msg</li>
11. }
12. </ul>
13. <p>
14. <a href="javascript:retourSaisies()">Retour aux saisies</a>
15. </p>
lignes 7-12 : la liste des erreurs du modle est affiche l'aide d'une balise <ul>.
On se rappelle que la fonction JS [calculer] l'origine du [Post] l'action serveur [Action05FaireCalcul] va mettre ce flux HTML
dans la rgion d'id [content]. Cela donne quelque chose comme ceci :
http://tahe.developpez.com
232/362
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel
session)
3.
{
4.
// modle
5.
ViewModel05 modle = new ViewModel05();
6. ...
7.
// on met les valeurs de A et B en session
8.
session.A = modle.A;
9.
session.B = modle.B;
10.
// pas d'erreurs pour l'instant
11.
List<string> erreurs = new List<string>();
12.
// une fois sur deux, on simule une erreur
13.
int val = session.Randomizer.Next(2);
14.
if (val == 0)
15.
{
16.
erreurs.Add("[erreur alatoire]");
17.
}
18.
if (erreurs.Count != 0)
19.
{
20.
modle.Erreurs = erreurs;
21.
return PartialView("Failure05", modle);
22.
}
23.
// calculs
24.
double A = double.Parse(modle.A);
25.
double B = double.Parse(modle.B);
26.
modle.AplusB = string.Format("{0}", A + B);
27.
modle.AmoinsB = string.Format("{0}", A - B);
28.
modle.AmultipliparB = string.Format("{0}", A * B);
29.
modle.AdivisparB = string.Format("{0}", A / B);
30.
// vue
31.
return PartialView("Success05", modle);
32. }
1. @model Exemple_04.Models.ViewModel05
2. <hr />
http://tahe.developpez.com
233/362
On se rappelle que la fonction JS [calculer] l'origine du [Post] l'action serveur [Action05FaireCalcul] va mettre ce flux HTML
dans la rgion d'id [content]. Cela donne quelque chose comme ceci :
7.6.6
Le lien Javascript [Effacer] permet de remettre le formulaire dans son tat initial :
http://tahe.developpez.com
234/362
28.
29. // au chargement du document
30. $(document).ready(function () {
31. // on rcupre les rfrences des diffrents composants de la page
32. loading = $("#loading");
33. content = $("#content");
34. // on cache l'image anime
35. loading.hide();
36. });
7.6.6.1
lignes 15-17 : on rcupre des rfrences sur divers lments du DOM (Document Object Model) ;
lignes 19-20 : on met des valeurs valides dans les champs de saisie des nombres A et B ;
ligne 23 : on excute les validateurs ct client. Comme les valeurs de A et B sont valides, cela va avoir pour effet de faire
disparatre d'ventuels messages d'erreur qui pourraient tre affichs ;
lignes 25-26 : on met des chanes vides dans les champs de saisie des nombres A et B ;
Le lien Javascript [Retour aux Saisies] permet de revenir au formulaire aprs avoir obtenu des rsultats :
http://tahe.developpez.com
235/362
http://tahe.developpez.com
236/362
33. ...
34. }
35.
36. // au chargement du document
37. $(document).ready(function () {
38. // on rcupre les rfrences des diffrents composants de la page
39. loading = $("#loading");
40. content = $("#content");
41. // on cache l'image anime
42. loading.hide();
43. });
[HttpPost]
public PartialViewResult Action05RetourSaisies(SessionModel session)
{
// vue
return PartialView("Formulaire05", new ViewModel05() { A = session.A, B =
session.B });
6. }
ligne 2 : l'action reoit pour paramtre le modle de la session dans lequel nous avons mmoris prcdemment les valeurs
de A et B saisies ;
ligne 5 : on retourne la vue partielle [Formulaire05] avec un modle de type [ViewModel05] dans lequel on prend soin
d'initialiser les champs A et B avec les valeurs de A et B prises dans la session ;
22. }
http://tahe.developpez.com
237/362
http://tahe.developpez.com
238/362
8 Conclusion intermdiaire
Ici se termine la prsentation du framework ASP.NET MVC. Nous allons poursuivre avec une tude de cas illustrant l'utilisation de
ce framework dans une architecture en couches :
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[ORM]
Connecteur
[ADO.NET]
Sgbd
Spring.net
la couche [Web] est la couche en contact avec l'utilisateur de l'application Web. Celui-ci interagit avec l'application Web au
travers de pages Web visualises par un navigateur. C'est dans cette couche que se situe ASP.NET MVC et
uniquement dans cette couche.
la couche [mtier] implmente les rgles de gestion de l'application, tels que le calcul d'un salaire ou d'une facture. Cette
couche utilise des donnes provenant de l'utilisateur via la couche [Web] et du SGBD via la couche [DAO].
la couche [DAO] (Data Access Objects), la couche [ORM] (Object Relational Mapper) et le connecteur ADO.NET grent
l'accs aux donnes du SGBD. La couche [ORM] fait un pont entre les objets manipuls par la couche [DAO] et les
lignes et les colonnes des tables d'une base de donnes relationnelle. Nous utiliserons l'ORM Entity Framework
(http://msdn.microsoft.com/en-us/data/ef.aspx ).
l'intgration des couches peut tre ralise par un conteneur d'injection de dpendances (Dependency Injection container).
Nous utiliserons Spring.net (http://www.springframework.net/ ).
Bien que ce document soit dj fort volumineux, il est incomplet. Le lecteur pourra complter sa formation avec le livre " Pro
ASP.NET MVC 4 " crit par Adam Freeman aux ditions Apress. C'est un trs bon livre. Ses 800 pages satisferont les lecteurs les
plus exigeants.
http://tahe.developpez.com
239/362
9 Etude de cas
9.1
Introduction
Nous allons prsenter une tude de cas dj publie dans un article disponible l'URL [http://tahe.developpez.com/dotnet/pamaspnet/]. Dans cet article, l'tude de cas est ralise avec ASP.NET classique et l'ORM NHibernate. Nous allons ici, la raliser
avec ASP.NET MVC et l'ORM Entity Framework. Comme dans l'article existant, l'tude de cas est prsente comme un TD
d'universit. Elle est donc destine des tudiants. Pour toutes les questions, des renvois aux chapitres prcdents seront faits pour
indiquer les lectures utiles.
9.2
Le problme rsoudre
Nous souhaitons crire une application web permettant un utilisateur de faire des simulations de calcul de la paie des assistantes
maternelles de l'association " Maison de la petite enfance " d'une commune. Nous nous intresserons autant l'organisation du
code DotNet de l'application qu'au code lui-mme.
L'application sera de type APU [Application Page Unique] et utilisera exclusivement des appels Ajax pour communiquer avec le
serveur. Elle prsentera l'utilisateur les vues suivantes :
- la vue [VueSaisies] qui prsente le formulaire de simulation
http://tahe.developpez.com
240/362
- la vue [VueSimulations] qui donne la liste des simulations faites par le client
http://tahe.developpez.com
241/362
- la vue [VueSimulationsVides] qui indique que le client n'a pas ou plus de simulations :
la vue [VueErreurs] qui indique une ou plusieurs erreurs (ici le SGBD MySQL a t arrt) :
9.3
Architecture de l'application
http://tahe.developpez.com
242/362
Navigateur
Application web
couche [ASP.NET MVC]
HTML
4b
JS
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
couches
[mtier, DAO, EF5]
Base de
donnes
2c
Modles
La couche [EF5] dsigne l'ORM Entity Framework 5. Le SGBD utilis sera MySQL.
Nous construirons cette application d'abord avec une couche [mtier] simule :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
Cela nous permettra de nous concentrer uniquement sur la couche [web]. La couche [mtier] simule respectera l'interface de la
couche [mtier] relle. Lorsque la couche [web] sera oprationnelle, on construira alors les couches [mtier], [DAO] et [EF5].
9.4
La base de donnes
Les donnes statiques utiles pour construire la fiche de paie sont places dans une base de donnes MySQL nomme [dbpam_ef5]
(pam=Paie Assistante Maternelle). Cette base a un administrateur appel root sans mot de passe. Elle a trois tables :
http://tahe.developpez.com
243/362
Table COTISATIONS : rassemble les taux des cotisations sociales prleves sur le salaire
Structure :
ID
CSGRDS
CSGD
SECU
RETRAITE
VERSIONING
Les taux des cotisations sociales sont indpendants du salari. La table prcdente n'a qu'une ligne.
Table INDEMNITES : rassemble les diffrentes indemnits dpendant de l'indice de l'employ
http://tahe.developpez.com
244/362
ID
INDICE
BASE_HEURE
ENTRETIEN_JOUR
REPAS_JOUR
INDEMNITES_CP
VERSIONING
9.5
Nous prsentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Nous prenons pour exemple, le
salaire de Mme Marie Jouveinal qui a travaill 150 h sur 20 jours pendant le mois payer.
Les lments suivants sont pris en compte
:
[TOTALHEURES]=150
[TOTALJOURS]= 20
[SALAIREBASE]=([TOTALHEURES]*[BAS
EHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.15
)= 362,25
CSGRDS : 12,64
CSGD : 22,28
Scurit sociale : 34,02
Retraite : 28,55
[COTISATIONSSOCIALES]=[SALAIREBAS
E]*(CSGRDS+CSGD+SECU+RETRAITE)/10
0
[COTISATIONSSOCIALES]=97,48
[INDEMNITS]=[TOTALJOURS]*(ENTRET
IENJOUR+REPASJOUR)
[INDEMNITES]=104
[SALAIREBASE][COTISATIONSSOCIALES]+
[INDEMNITS]
[salaire NET]=368,77
http://tahe.developpez.com
245/362
9.6
9
3
10
5
1
9.7
11
[ApplicationModelBinder] : la classe qui permet d'inclure les donnes de porte [Application] dans le
modle des actions,
[SessionModelBinder] : la classe qui permet d'inclure les donnes de porte [Session] dans le modle des
actions,
A partir de maintenant, nous dcrivons les tapes suivre pour raliser l'tude de cas. Lorsque c'est utile, nous rappelons le n du
chapitre relire ventuellement pour raliser le travail demand. Certains lments du projet vous sont donns dans un dossier
[aspnetmvc-support.zip] qu'on trouvera sur le site de ce document. On y trouvera le dossier [tudedecas-support] avec le contenu
suivant :
http://tahe.developpez.com
246/362
Le projet reprend par ailleurs des lments prsents dans les chapitres prcdents. Il suffit alors de rcuprer ceux-ci par copier /
coller entre ce PDF et Visual Studio.
9.7.1
Nous allons tout d'abord crer une solution Visual Studio dans laquelle nous allons crer deux projets :
Navigateur
Application web
couche [ASP.NET MVC]
1
HTML
Vue1
Vue2
4b
JS
Vuen
2b
2a
Front Controller
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
Visual Studio Express 2012 pour le bureau qui servira construire la couche [mtier] ;
Visual Studio Express 2012 pour le Web qui servira construire la couche [web].
Avec Visual Studio Express pour le bureau, nous crons une solution [pam-td] :
5
4
3
http://tahe.developpez.com
247/362
9.7.2
Dans une architecture en couches, il est de bonne pratique que la communication entre couches se fasse via des interfaces :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
Quelle interface doit prsenter la couche [mtier] la couche [web] ? Quelles sont les interactions possibles entre ces deux
couches ? Rappelons-nous l'interface web qui sera prsente l'utilisateur :
http://tahe.developpez.com
248/362
1
3
2
5
6
11
10
12
13
16
20
15
14
19
18
17
22
21
23
24
1.
2.
3.
4.
l'affichage initial du formulaire, on doit trouver en [1] la liste des employs. Une liste simplifie suffit (Nom, Prnom,
SS). Le n SS est ncessaire pour avoir accs aux informations complmentaires sur l'employ slectionn (informations 6
11).
les informations 12 15 sont les diffrents taux de cotisations.
les informations 16 19 sont les indemnits de l'employ
les informations 20 24 sont les lments du salaire calculs partir des saisies 1 3 faites par l'utilisateur.
L'interface [IPamMetier] offerte la couche [web] par la couche [metier] doit rpondre aux exigences ci-dessus. Il existe de
nombreuses interfaces possibles. Nous proposons la suivante :
1. using Pam.Metier.Entites;
2. namespace Pam.Metier.Service
3. {
4.
public interface IPamMetier
5.
{
6.
// liste de toutes les identits des employs
7.
Employe[] GetAllIdentitesEmployes();
http://tahe.developpez.com
249/362
8.
9.
// ------- le calcul du salaire
10.
FeuilleSalaire GetSalaire(string ss, double heuresTravailles, int joursTravaills);
11. }
12. }
9.7.3
L'interface prcdente utilise deux classes [Employe] et [FeuilleSalaire] qu'il nous faut dfinir :
Dans l'architecture finale, la couche [mtier] va manipuler des entits images de la base de donnes :
http://tahe.developpez.com
250/362
Nous utiliserons les classes suivantes pour reprsenter les lignes des trois tables de la base de donnes. On se reportera au
paragraphe 9.4, page 243, pour connatre la signification des diffrents champs.
Classe [Employe]
Elle reprsente une ligne de la table [employes]. Son code est le suivant :
1. using System;
2.
3. namespace Pam.Metier.Entites
4. {
5.
6.
public class Employe
7.
{
8.
public string SS { get; set; }
9.
public string Nom { get; set; }
10.
public string Prenom { get; set; }
11.
public string Adresse { get; set; }
12.
public string Ville { get; set; }
13.
public string CodePostal { get; set; }
14.
public Indemnites Indemnites { get; set; }
15.
16.
// signature
17.
public override string ToString()
18.
{
19.
return string.Format("Employ[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse,
Ville, CodePostal);
20.
}
21. }
22. }
Classe [Indemnites]
Elle reprsente une ligne de la table [indemnites]. Son code est le suivant :
1. using System;
2.
3. namespace Pam.Metier.Entites
4. {
http://tahe.developpez.com
251/362
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
Classe [Cotisations]
Elle reprsente une ligne de la table [cotisations]. Son code est le suivant :
1. using System;
2.
3. namespace Pam.Metier.Entites
4. {
5.
6.
public class Cotisations
7.
{
8.
public double CsgRds { get; set; }
9.
public double Csgd { get; set; }
10.
public double Secu { get; set; }
11.
public double Retraite { get; set; }
12.
// signature
13.
public override string ToString()
14.
{
15.
return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
16.
}
17. }
18. }
On notera que les classes ne reprennent pas les colonnes [ID] et [VERSIONING] des tables. Ces colonnes, utiles lorsqu'on utilisera
l'ORM EF5, ne le sont pas dans le contexte de la couche [mtier] simule.
La classe [FeuilleSalaire] encapsule les informations 6 24 du formulaire prsent page 248 :
1. namespace Pam.Metier.Entites
2. {
3.
public class FeuilleSalaire
4.
{
5.
6.
// proprits automatiques
7.
public Employe Employe { get; set; }
8.
public Cotisations Cotisations { get; set; }
9.
public ElementsSalaire ElementsSalaire { get; set; }
10.
11.
// ToString
12.
public override string ToString()
13.
{
14.
return string.Format("[{0},{1},{2}]", Employe, Cotisations, ElementsSalaire);
15.
}
16. }
17. }
http://tahe.developpez.com
252/362
ligne 7 : les informations 6 11 sur l'employ dont on calcule le salaire et les informations 16 19 sur ses indemnits. Il ne
faut pas oublier ici qu'un objet [Employe] encapsule un objet [Indemnites] reprsentant ses indemnits ;
ligne 8 : les informations 12 15 ;
ligne 9 : les informations 20 24 ;
lignes 12-14 : la mthode [ToString].
9.7.4
lignes 6-10 : les lments du salaire tels qu'expliqus dans les rgles mtier dcrites page 245 ;
ligne 6 : le salaire de base de l'employ, fonction du nombre d'heures travailles ;
ligne 7 : les cotisations prleves sur ce salaire de base ;
lignes 8 et 9 : les indemnits ajouter au salaire de base, fonction de l'indice de l'employ et du nombre de jours travaills ;
ligne 10 : le salaire net payer ;
lignes 14-17 : la mthode [ToString] de la classe.
La classe [PamException]
Nous crons un type d'exceptions spcifique pour notre application. C'est le type [PamException] suivant :
1. using System;
2.
3. namespace Pam.Metier.Entites
4. {
5.
// classe d'exception
6.
public class PamException : Exception
7.
{
8.
9.
// le code de l'erreur
10.
public int Code { get; set; }
11.
12.
// constructeurs
13.
public PamException()
14.
{
15.
}
16.
17.
public PamException(int Code)
18.
: base()
19.
{
20.
this.Code = Code;
21.
}
22.
23.
public PamException(string message, int Code)
http://tahe.developpez.com
253/362
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34. }
35. }
: base(message)
{
this.Code = Code;
}
public PamException(string message, Exception ex, int Code)
: base(message, ex)
{
this.Code = Code;
}
celui des lignes 23-27 qu'on peut utiliser comme montr ci-dessous :
throw new PamException("Problme d'accs aux donnes",5);
ou celui des lignes 29-33 destin faire remonter une exception survenue en l'encapsulant dans une exception de type
[PamException] :
try{
....
}catch (IOException ex){
// on encapsule l'exception ex
throw new PamException("Problme d'accs aux donnes",ex,10);
}
Cette seconde mthode a l'avantage de ne pas perdre l'information que peut contenir la premire exception.
9.7.5
http://tahe.developpez.com
254/362
27. }
le dictionnaire
BaseHeure = 0, EntretienJour = 0,
http://tahe.developpez.com
255/362
// calcul salaire
public FeuilleSalaire GetSalaire(string ss, double heuresTravailles, int
joursTravaills)
3.
{
4.
// on rcupre l'employ de n SS
5.
Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
6.
// existe ?
7.
if (e == null)
8.
{
9.
throw new PamException(string.Format("L'employ de n SS [{0}] n'existe pas",
ss), 10);
10.
}
11.
// on rend une feuille de salaire fictive
12.
return new FeuilleSalaire()
13.
{
14.
Employe = e,
15.
Cotisations = new Cotisations() { CsgRds = 3.49, Csgd = 6.15, Secu = 9.38,
Retraite = 7.88 },
16.
ElementsSalaire = new ElementsSalaire() { CotisationsSociales = 100,
IndemnitesEntretien = 100, IndemnitesRepas = 100, SalaireBase = 100, SalaireNet = 100 }
17.
};
18. }
9.7.6
ligne 2 : la mthode reoit le n SS de l'employ dont on veut calculer le salaire, son nombre d'heures travailles et son
nombre de jours travaills ;
ligne 5 : on va chercher l'employ dans le dictionnaire. On se rappelle que l'un d'eux n'y est pas ;
lignes 7-10 : si l'employ n'est pas trouv, une exception [PamException] est lance ;
lignes 12-17 : on rend une feuille de salaire fictive.
La classe [Program] ci-dessus va tester les mthodes de l'interface [IPamMetier]. Un exemple basique pourrait tre le suivant :
1. using Pam.Metier.Entites;
2. using Pam.Metier.Service;
3. using System;
4.
http://tahe.developpez.com
256/362
5. namespace Pam.Metier.Tests
6. {
7.
class Program
8.
{
9.
public static void Main()
10.
{
11.
// instanciation couche [metier]
12.
IPamMetier pamMetier = new PamMetier();
13.
// liste des employs
14.
Employe[] employes = pamMetier.GetAllIdentitesEmployes();
15.
Console.WriteLine("Liste des employs--------------------");
16.
foreach (Employe e in employes)
17.
{
18.
Console.WriteLine(e);
19.
}
20.
// calculs de feuilles de salaire
21.
Console.WriteLine("Calculs de feuilles de salaire-----------------");
22.
Console.WriteLine(pamMetier.GetSalaire(employes[0].SS, 30, 5));
23.
Console.WriteLine(pamMetier.GetSalaire(employes[1].SS, 150, 20));
24.
try
25.
{
26.
Console.WriteLine(pamMetier.GetSalaire(employes[2].SS, 150, 20));
27.
}
28.
catch (PamException ex)
29.
{
30.
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
31.
}
32.
}
33. }
34. }
Le lecteur est invit faire le lien entre ces rsultats et le code excut.
Afin de pouvoir utiliser ce projet dans le projet web que nous allons construire, nous en faisons une bibliothque de classes :
http://tahe.developpez.com
257/362
1
2
en [5], on demande un assembly de type [Release]. L'autre type est [Debug]. L'assembly contient alors des informations
facilitant le dbogage ;
en [6], on gnre le projet [pam-metier-simule] ;
9.8
Dans la solution Visual Studio prcdente nous allons crer le projet pour la couche web MVC.
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
http://tahe.developpez.com
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
258/362
Avec Visual Studio Express pour le web, nous ouvrons la solution [pam-td] cre prcdemment avec Visual Studio Express pour
le bureau.
1
en [1], la solution [pam-td] a t charge dans Visual Studio Express pour le Web ;
en [2], la solution et le projet pour la couche [mtier] simule que nous venons de crer.
Dans cette nouvelle tape, nous allons crer le squelette de l'application web.
http://tahe.developpez.com
259/362
en [6], on fait du nouveau projet, le projet de dmarrage de la solution, celui qui sera excut lorsqu'on fera [Ctrl-F5] ;
en [7], le nom du nouveau projet est pass en gras, indiquant qu'il est le projet de dmarrage de la solution.
Maintenant, on remplace avec l'explorateur Windows le dossier [Content] du projet par le dossier [tudedecas-support / web /
Content]. Ceci fait, il faut inclure les nouveaux fichiers dans le projet [pam-web-01]. On procdera ainsi :
http://tahe.developpez.com
260/362
4
2
Dans le dossier [Scripts], ajoutez les scripts JQuery Globalization [1] ncessaires la validation ct client (cf page 169).
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
<table>
<tbody>
<tr>
<td>
<h2>Simulateur de calcul de paie</h2>
</td>
<td style="width: 20px">
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
</td>
<td>
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de
simulation<br />
34.
</a>
http://tahe.developpez.com
261/362
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
De cette configuration, il rsulte que l'URL [/] est quivalente l'URL [/Pam/Index]. Comme notre application est de type APU,
l'URL [/] sera l'unique URL de celle-ci.
Crez le contrleur [Pam] (cf page 51) :
http://tahe.developpez.com
262/362
Crez maintenant, la vue [Index.cshtml] affiche par l'action [Index] ci-dessus (cf page 98) :
http://tahe.developpez.com
263/362
La feuille de style [/Content/Site.css] dfinit une image de fond pour les pages de l'application :
1. body {
2.
background-image: url("/Content/Images/standard.jpg");
3. }
9.9
Nous voulons crire une application suivant le modle APU (Application Page Unique) dcrit au paragraphe 7.5, page 218 ainsi
qu'au paragraphe 7.6, page 221. La page unique est celle charge par le navigateur au dmarrage de l'application :
http://tahe.developpez.com
264/362
la partie [1] ci-dessus est la partie fixe de la page unique. Nous avons vu qu'elle tait fournie par la page matre
[_Layout.cshtml] ;
la partie [2] est la partie variable de la page unique. Elle s'inscrit dans la rgion d'id [content] de la page matre
[_Layout.cshtml] :
1. <!DOCTYPE html>
2. <html>
3. <head>
4.
<title>@ViewBag.Title</title>
5.
...
6.
<script type="text/javascript" src="~/Scripts/myScripts.js"></script>
7. </head>
8. <body>
9.
<table>
10. ...
11. </table>
12. <hr />
13. <div id="content">
14.
@RenderBody()
15. </div>
16. </body>
17. </html>
Les diffrents fragments de page de l'application vont venir s'afficher dans la rgion d'id [content] de la ligne 13. Ils seront affichs
via des appels Ajax. Les scripts Javascript excutant ces appels sont dans la fichier [myScripts.js] rfrenc ligne 6. Crez ce fichier
dont nous allons avoir besoin :
Nous suivons dsormais le modle APU dcrit au paragraphe 7.6, page 221. Relisez ce paragraphe si vous l'avez oubli. Nous allons
maintenant mettre en place les diffrents fragments de page affichs par l'application.
9.9.1
Nous rappelons qu'avec le navigateur Chrome, vous disposez d'une palette d'outils pour dboguer le HTML, CSS, Javascript de vos
pages. Ces outils ont t prsents partiellement au paragraphe 7.2, page 196. Dans le modle APU, les navigateurs gardent en cache
les scripts Javascript rfrencs par la premire page de l'application. Aussi, faut-il penser vider ce cache lorsque vous modifiez vos
scripts, sinon les modifications peuvent ne pas tre prises en compte. Voici comment faire avec Chrome :
- faire [Ctrl-Maj-I] pour afficher l'environnement de dveloppement
http://tahe.developpez.com
265/362
9.9.2
Le formulaire de saisie est l'un des fragments affichs par l'application. Pour l'instant ce formulaire est affich par la vue
[Index.cshtml] qui est une vue complte :
1. @{
2.
ViewBag.Title = "Pam";
3. }
4. <h2>Formulaire</h2>
[HttpGet]
public ViewResult Index()
{
return View();
Ligne 4 ci-dessus, c'est bien une vue [View] et non une vue partielle [PartialView] qui est affiche. Nous avons besoin d'une vue
partielle pour le formulaire qui sera un fragment de page. Nous faisons voluer la vue [Index.cshtml] de la faon suivante :
1. @{
2.
ViewBag.Title = "Pam";
3. }
4. @Html.Partial("Formulaire")
Ligne 4, le formulaire ne fait plus partie de la page [Index.cshtml]. Il est dsormais log dans une vue partielle [Formulaire.cshtml] :
Faites ces modifications et vrifiez que vous obtenez toujours la vue suivante au dmarrage de l'application :
http://tahe.developpez.com
266/362
9.9.3
Nous nous intressons au fragment affich lorsque l'utilisateur clique sur le lien [Faire la simulation] :
Nous faisons voluer de la faon suivante la vue partielle [Formulaire.cshtml] qui affiche le formulaire :
1. <h2>Formulaire</h2>
2. <div id="simulation" />
Ligne 3, nous crons une rgion d'id [simulation] pour loger le fragment de la simulation.
Nous crons la vue partielle [Simulation.cshtml] suivante :
http://tahe.developpez.com
267/362
Il nous faut maintenant crire le code Javascript qui gre le clic sur le lien [Faire la simulation]. Nous allons suivre la dmarche
expose au paragraphe 7.6.5, page 230. Tout d'abord, regardons le code HTML du lien dans [_Layout.cshtml] :
1. <a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la
simulation<br />
2. </a>
On voit qu'un clic sur le lien [Faire la simulation] va lancer l'excution de la fonction JS [faireSimulation]. Cette fonction va tre
crite dans le fichier [myScripts.js] ainsi que les autres fonctions J ncessaires l'application :
1. // variables globales
2. var loading;
3. var content;
4.
5. function faireSimulation() {
6.
// on fait un appel Ajax la main
7. ...
8. }
9.
10. function effacerSimulation() {
11. // on efface les saisies du formulaire
12. ...
13. }
14.
15. function enregistrerSimulation() {
16. // on fait un appel Ajax la main
17. ...
18. }
19.
20. function voirSimulations() {
21. // on fait un appel Ajax la main
22. ...
23. }
24.
25. function retourFormulaire() {
26. // on fait un appel Ajax la main
27. ...
28. }
29.
30. function terminerSession() {
31. ...
32. }
33.
34. // au chargement du document
35. $(document).ready(function () {
36. // on rcupre les rfrences des diffrents composants de la page
37. loading = $("#loading");
38. content = $("#content");
39. });
On rappelle que les lments d'id [loading] et [content] sont dfinis dans la page matre [_Layout.cshtml] (lignes 14 et 21 cidessous) :
1.
2.
3.
4.
5.
6.
7.
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<table>
http://tahe.developpez.com
268/362
8.
<tbody>
9.
<tr>
10.
<td>
11.
<h2>Simulateur de calcul de paie</h2>
12.
</td>
13.
<td style="width: 20px">
14.
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
15.
</td>
16. ...
17.
</td>
18.
</tbody>
19. </table>
20. <hr />
21. <div id="content">
22.
@RenderBody()
23. </div>
24. </body>
25. </html>
Travail : en suivant la dmarche expose au paragraphe 7.6.5, page 230, crivez la fonction JS [faireSimulation]. Celle-ci mettra un
appel Ajax de type POST vers l'action [/Pam/FaireSimulation]. Il n'y aura pas de donnes postes pour l'instant. L'action
[/Pam/FaireSimulation] renverra la vue partielle [Simulation.cshtml] la fonction JS [faireSimulation] qui placera alors ce flux
HTML dans la rgion d'id [simulation] du formulaire.
Testez le lien [Faire la simulation] de votre application.
9.9.4
Travail : en suivant la dmarche prcdente, crivez la fonction JS [enregistrerSimulation]. Celle-ci mettra un appel Ajax de type
POST vers l'action [/Pam/EnregistrerSimulation]. Il n'y aura pas de donnes postes pour l'instant. L'action
[/Pam/EnregistrerSimulation] renverra la vue partielle [Simulations.cshtml] la fonction JS [enregistrerSimulation] qui placera alors
ce flux HTML dans la rgion d'id [content] de la page matre.
La vue [Simulations.cshtml] est la suivante :
http://tahe.developpez.com
269/362
9.9.5
Le lien [Voir les simulations] est dfini de la faon suivante dans [_Layout.cshtml] :
1. <a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les
simulations<br />
2. </a>
Travail : en suivant la dmarche prcdente, crivez la fonction JS [voirSimulations]. Celle-ci mettra un appel Ajax de type POST
vers l'action [/Pam/VoirSimulations]. Il n'y aura pas de donnes postes pour l'instant. L'action [/Pam/VoirSimulations] renverra la
vue partielle [Simulations.cshtml] la fonction JS [voirSimulations] qui placera alors ce flux HTML dans la rgion d' id [content] de
la page matre.
La vue [Simulations.cshtml] est celle dj utilise dans la question prcdente.
Voici un exemple d'excution :
9.9.6
Le lien [Retour au formulaire de simulation] est dfini de la faon suivante dans [_Layout.cshtml] :
1. <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au
formulaire de simulation<br />
2. </a>
Travail : en suivant la dmarche prcdente, crivez la fonction JS [retourFormulaire]. Celle-ci mettra un appel Ajax de type POST
vers l'action [/Pam/Formulaire]. Il n'y aura pas de donnes postes pour l'instant. L'action [/Pam/Formulaire] renverra la vue
partielle [Formulaire.cshtml] la fonction JS [retourFormulaire] qui placera alors ce flux HTML dans la rgion d' id [content] de la
page matre.
La vue [Formulaire .cshtml] a dj t dfinie. Voici un exemple d'excution :
http://tahe.developpez.com
270/362
9.9.7
Travail : en suivant la dmarche prcdente, crivez la fonction JS [terminerSession]. Celle-ci mettra un appel Ajax de type POST
vers l'action [/Pam/TerminerSession]. Il n'y aura pas de donnes postes pour l'instant. L'action [/Pam/TerminerSession] renverra
la vue partielle [Formulaire.cshtml] la fonction JS [terminerSession] qui placera alors ce flux HTML dans la rgion d' id [content]
de la page matre.
Voici un exemple d'excution :
9.9.8
La fonction JS [effacerSimulation]
de remettre les champs de saisie du formulaire dans l'tat o ils taient au chargement initial de l'application.
Travail : crivez la fonction JS [effacerSimulation]. Il n'y a pas ici d'appel Ajax. Ce qui se passe est interne au navigateur et
n'implique pas le serveur.
http://tahe.developpez.com
271/362
9.9.9
Pour l'instant, les liens sont toujours affichs. Nous allons maintenant grer leur affichage avec une fonction Javascript. Rappelons
tout d'abord le code des six liens Javascript dans [_Layout.cshtml] :
1. <a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
2. </a>
3. <a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la
simulation<br />
4. </a>
5. <a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
6. </a>
7. <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de
simulation<br />
8. </a>
9. <a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la
simulation<br />
10. </a>
11. <a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
12. </a>
Tous les liens ont un attribut [id] qui va nous permettre de les grer en Javascript. Nous modifions la mthode JS excute au
chargement de la page de la faon suivante :
1. // variables globales
2. var loading;
3. var content;
4. var lnkFaireSimulation;
5. var lnkEffacerSimulation
6. var lnkEnregistrerSimulation;
7. var lnkTerminerSession;
8. var lnkVoirSimulations;
9. var lnkRetourFormulaire;
10. var options;
11.
12. ...
13. // au chargement du document
14. $(document).ready(function () {
15. // on rcupre les rfrences des diffrents composants de la page
16. loading = $("#loading");
17. content = $("#content");
18. // les liens du menu
19. lnkFaireSimulation = $("#lnkFaireSimulation");
http://tahe.developpez.com
272/362
20.
21.
22.
23.
24.
25.
26.
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// on les met dans un tableau
options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation,
lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
27. // on cache certains lments de la page
28. loading.hide();
29. // on fixe le menu
30. setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
31. });
32.
lignes 19-24 : on rcupre les rfrences sur les six liens. Ces rfrences sont dfinies en variables globales aux lignes 4-9 ;
ligne 26 : le tableau [options] est initialis avec les six rfrences. Ce tableau est dfini en variable globale ligne 10 ;
ligne 28 : on cache l'image anime de l'attente de fin des appels Ajax ;
ligne 30 : on affiche les liens [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]. Les autres seront cachs.
http://tahe.developpez.com
273/362
http://tahe.developpez.com
274/362
Maintenant que le modle APU et les liens de navigation sont en place, nous pouvons passer l'criture des actions et des vues ct
serveur. Au fil des tapes, vous allez dcouvrir que certains de vos liens Ajax qui fonctionnent maintenant ne fonctionnent plus, ceci
parce que vous allez modifier les vues partielles envoyes au client. Au fur et mesure que vous allez construire les diffrentes
actions et vues c serveur, vos liens Ajax ct client vont retrouver le fonctionnement que vous leur avez donn.
9.10
C'est l'action [Index] qui doit produire cette page. Faisons quelques remarques :
http://tahe.developpez.com
275/362
la validit des champs de saisie [Heures travailles] et [Jours travaills] doit tre vrifie ;
la liste des employs vient de la couche [mtier] que nous avons construite prcdemment.
[HttpGet]
public ViewResult Index()
{
return View();
9.10.1
Le modle du formulaire
Requte
1
Modle
3 d'action
Liaison
2
Action
4
Modle
de Vue
Vue
Rponse
L'action laquelle nous nous intressons est l'action [Index] qui pour l'instant est la suivante :
1.
2.
3.
4.
5. }
[HttpGet]
public ViewResult Index()
{
return View();
L'action [Index] ne passe aucun modle la vue [Index.cshtml]. Celle-ci ne pourra donc pas afficher la liste des employs. Celle-ci
peut tre demande la couche [mtier]. Pour cela, il faut que le projet [pam-web-01] ait une rfrence sur le projet [pam-metiersimule]. Nous crons cette rfrence maintenant :
2
3
http://tahe.developpez.com
276/362
9.10.2
en [1], clic droit sur [References] du projet [pam-web-01] puis [Ajouter une rfrence] ;
en [2], slectionner l'option [Solution] puis le projet [pam-metier-simule] en [3] ;
en [4], le projet [pam-metier-simule] a t ajout aux rfrences du projet [pam-web-01].
Le modle de l'application
Nous avons introduit les notions importantes de modle d'application et de modle de session au paragraphe 4.10, page 88.
Nous allons les utiliser maintenant. On rappelle qu'on met dans le modle :
d'application des donnes en lecture seule pour tous les utilisateurs. Ce modle constitue une mmoire partage par
toutes les requtes de tous les utilisateurs ;
de session des donnes en lecture et criture pour un utilisateur donn. Ce modle constitue une mmoire partage par
toutes les requtes de cet utilisateur.
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
La couche [web] dtient une rfrence sur la couche [mtier]. Celle-ci peut tre partage par tous les utilisateurs. On peut donc la
mettre dans le modle de l'application. Par ailleurs, nous allons faire l'hypothse que la liste des employs ne change pas. Elle peut
donc tre lue une unique fois puis partage entre tous les utilisateurs. Nous proposons alors le modle d'application suivant :
Pour afficher une liste droulante dans une vue, on crit quelque chose comme suit (cf page 133) :
1.
2.
3.
4.
5.
6.
7. </tr>
http://tahe.developpez.com
277/362
La mthode [DropDownListFor] attend pour second paramtre un type SelectListItem[] qui avait t fourni ci-dessus par un type
[SelectList]. Il nous faut construire un tel tableau avec la liste des employs. Puisque les employs ne changent pas, ce tableau peut
lui aussi tre plac dans le modle de l'application. Nous faisons voluer celui-ci comme suit :
1. using Pam.Metier.Entites;
2. using Pam.Metier.Service;
3. using System.Web.Mvc;
4.
5. namespace Pam.Web.Models
6. {
7.
public class ApplicationModel
8.
{
9.
// --- donnes de porte application --10.
public Employe[] Employes { get; set; }
11.
public IPamMetier PamMetier { get; set; }
12.
public SelectListItem[] EmployesItems { get; set; }
13. }
14. }
A quel moment ce modle doit-il tre construit ? Nous l'avons montr au paragraphe 4.10, page 88. C'est lors de l'excution de la
mthode [Application_Start] du fichier [Global.asax] :
using
using
using
using
using
using
using
http://tahe.developpez.com
Pam.Metier.Entites;
Pam.Metier.Service;
PamWeb.Infrastructure;
PamWeb.Models;
System.Web.Http;
System.Web.Mvc;
System.Web.Optimization;
278/362
8. using System.Web.Routing;
9.
10. namespace pam_web_01
11. {
12. public class MvcApplication : System.Web.HttpApplication
13. {
14.
protected void Application_Start()
15.
{
16.
// ----------Auto-gnr
17.
AreaRegistration.RegisterAllAreas();
18.
WebApiConfig.Register(GlobalConfiguration.Configuration);
19.
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
20.
RouteConfig.RegisterRoutes(RouteTable.Routes);
21.
BundleConfig.RegisterBundles(BundleTable.Bundles);
22.
// ------------------------------------------------------------------23.
// ---------- configuration spcifique
24.
// ------------------------------------------------------------------25.
// donnes de porte application
26.
ApplicationModel application = new ApplicationModel();
27.
Application["data"] = application;
28.
// instanciation couche [metier]
29.
application.PamMetier = ...
30.
// tableau des employs
31.
application.Employes = ...
32.
// lments du combo des employs
33.
application.EmployesItems = ...
34.
// model binder pour [ApplicationModel]
35.
...
36.
}
37. }
38. }
Travail : complter le code de la mthode [Application_Start]. Tout ce dont vous avez besoin se trouve au paragraphe 4.10, page
88. Prenez le temps de relire ce paragraphe long mais important.
La ligne 33 comporte en fait plusieurs lignes. Pour construire un objet de type [SelectListItem] vous pouvez utiliser la mthode
suivante :
new SelectListItem() { Text = unTexte, Value = uneValeur };
Ce [SelectListItem] servira gnrer la balise HTML <option> suivante :
<option value='uneValeur'>unTexte</option>
9.10.3
Maintenant que nous avons dfini un modle pour l'application, nous pouvons faire voluer le code de l'action [Index] de la faon
suivante :
http://tahe.developpez.com
279/362
1.
2.
3.
4.
5. }
9.10.4
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View();
ligne 4 : le modle de l'application est dsormais un paramtre de l'action [Index]. Nous avons expliqu au paragraphe
4.10, page 88, comment ce paramtre tait initialis par le framework.
Maintenant, l'action [Index] a accs aux employs qui sont enregistrs dans le modle de l'application. Il faut maintenant qu'elle les
passe la vue [Index.cshtml] qu'elle va afficher. On pourrait passer un type [ApplicationModel] comme modle de la vue
[Index.cshtml] mais nous verrons rapidement que cette vue a besoin d'autres informations qui ne sont pas dans [ApplicationModel].
Nous allons utiliser le modle de vue [IndexModel] suivant :
1. namespace Pam.Web.Models
2. {
3.
public class IndexModel
4.
{
5.
// donnes de porte application
6.
public ApplicationModel Application { get; set; }
7.
}
8. }
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
ligne 4, la vue par dfaut [Index.cshtml] est affiche avec pour modle un type [IndexModel] initialis avec les donnes du
modle d'application.
http://tahe.developpez.com
280/362
Requte
Liaison
Modle
d'action
Action
Modle
de Vue
Vue
Rponse
le n SS de l'employ slectionn ;
lignes 13, 16, 18 : les trois valeurs postes. On notera que [joursTravaills] a t dclar de type [double] alors qu'en ralit
on attend un entier. Le type [double] a t introduit pour faciliter la validation de ce champ ct client, la validation d'un
type [int] ayant pos problme ;
lignes 12, 14, 17 : des libells pour les mthodes [Html.LabelFor] de la vue associe au modle ;
ligne 15 : une annotation pour avoir un affichage du champ [HeuresTravailles] avec deux dcimales ;
ligne 5 : on indique que la proprit nomme [Application] ne fait pas partie des valeurs postes.
9.10.5
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
http://tahe.developpez.com
281/362
1. @{
2.
ViewBag.Title = "Pam";
3. }
4. @Html.Partial("Formulaire")
1. @model Pam.Web.Models.IndexModel
2.
3. @using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id =
"formulaire" }))
4. {
5.
<table>
6.
<thead>
7.
<tr>
8. ...
9.
</tr>
10.
</thead>
11.
<tbody>
12.
<tr>
13. ...
14.
</tr>
15.
<tr>
16. ...
17.
</tr>
18.
</tbody>
19. </table>
20. }
21. <div id="simulation" />
Travail : complter le code de la vue [Formulaire.cshtml]. On utilisera les mthodes [DropDownListFor, EditorFor, LabelFor,
ValidationMessageFor] dcrites au paragraphe 5.7, page 132.
9.10.6
Nous avons crit tous les lments de la chane de traitement de l'URL [/Pam/Index] :
Requte
Liaison
Modle
d'action
Action
Modle
de Vue
Vue
Rponse
http://tahe.developpez.com
282/362
Vous devez vrifier que votre liste droulante a bien t remplie avec la liste des employs que nous avions dfinie dans la couche
[mtier] simule.
9.11
9.11.1
Bien que nous n'ayons rien fait pour cela, des validations ct client sont dj luvre :
Cependant parce que dans [IndexModel], on a dclar le champ [JoursTravaills] de type [double] :
public double JoursTravaills { get; set; }
http://tahe.developpez.com
283/362
Par ailleurs, on peut entrer des valeurs fantaisistes dans les deux champs :
n'accepter que des valeurs relles dans l'intervalle [0,400] pour le champ [HeuresTravailles] ;
n'accepter que des valeurs entires dans l'intervalle [0,31] pour le champ [JoursTravailles] ;
http://tahe.developpez.com
284/362
On pourra s'aider de l'exemple du paragraphe 7.6.2, page 227. Pour vrifier que le nombre de jours travaills est un entier, on
pourra utiliser une expression rgulire (cf exemples du paragraphe 5.9.1, page 157).
Voici des exemples de ce qui est attendu :
9.11.2
Dans la version actuelle de l'application, le nombre d'heures travailles doit tre un nombre dcimal au format anglo-saxon (avec le
point dcimal). Le format franais avec la virgule n'est pas accept :
http://tahe.developpez.com
285/362
9.11.3
Actuellement, on peut poster des valeurs invalides comme le montre la squence suivante :
http://tahe.developpez.com
286/362
La prsence de la simulation en [1] et le changement de menu en [2] montrent que le clic sur le lien [Faire la simulation] a post le
formulaire alors mmes que les valeurs saisies taient invalides. Ce problme a t identifi et trait au paragraphe 7.6.5, page 230.
Travail : en suivant la dmarche expose au paragraphe sus-nomm, faites en sorte que le POST du lien [Faire la simulation] ne
puisse se faire si les valeurs saisies sont invalides. Pensez vider le cache du navigateur avant de tester vos modifications.
On rappelle que la vue partielle [Formulaire.cshtml] gnre un formulaire HTML d'id [formulaire] (ligne 1 ci-dessous) :
1. @using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id =
"formulaire" }))
2. {
3. ...
4. }
9.12
9.12.1
Lorsque nous faisons une simulation, nous voulons obtenir le rsultat suivant :
http://tahe.developpez.com
287/362
9.12.2
http://tahe.developpez.com
288/362
13.
</tr>
14.
<tr>
15.
<td>
16.
<span class="valeur">@Model.Employe.Nom</span>
17.
</td>
18. ...
19.
</tr>
20.
<tr>
21.
<td><span class="libell">Ville</span>
22.
</td>
23.
<td><span class="libell">Code Postal</span>
24.
</td>
25.
<td><span class="libell">Indice</span>
26.
</td>
27.
</tr>
28.
<tr>
29. ...
30.
</tr>
31. </tbody>
32. </table>
33. <br />
34. <p><span class="info">Informations Cotisations</span></p>
35. <table>
36. ...
37. </tbody>
38. </table>
39. <br />
40. <p><span class="info">Informations Indemnits</span></p>
41. <table>
42. ...
43. </table>
44. <br />
45. <p><span class="info">Informations Salaire</span></p>
46. <table>
47. ...
48. </table>
49. <br />
50. <table>
51. ...
52. </table>
ligne 1 : la vue [Simulation.cshtml] a pour modle le type [FeuilleSalaire] dfini au paragraphe 9.7.3, page 250 ;
la vue utilise les classes [libell, info, valeur] dfinies dans la feuille de style de l'application [Content / Site.css] :
1. .libell {
2.
background-color: azure;
3.
margin: 5px;
4.
padding: 5px;
5. }
6.
7. .info {
8.
background-color: antiquewhite;
9.
margin: 5px;
10. padding: 5px;
11. }
12.
13. .valeur {
14. background-color: beige;
15. padding: 5px;
16. margin: 5px;
17. }
http://tahe.developpez.com
289/362
Par ailleurs, toujours dans [Site.css], on fixe la hauteur des lignes des diffrentes tables HTML de la rgion d' id [simulation],
prcisment l o est affiche la feuille de salaire :
1. #simulation table tr {
2.
height: 30px;
3. }
L'instruction ci-dessus affiche [somme] en valeur montaire [C] (Currency) avec deux dcimales [C2].
Pour tester cette vue, on doit lui fournir une feuille de salaire. Celle-ci doit lui tre fournie par l'action [/Pam/FaireSimulation] qui
est la cible de l'appel Ajax du lien [Faire la simulation]. Actuellement, cette action est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12. }
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
// faire une simulation
[HttpPost]
public PartialViewResult FaireSimulation()
{
return PartialView("Simulation");
Ci-dessus, l'action [FaireSimulation] ne passe aucun modle la vue [Simulation.cshtml]. Il faut qu'elle lui passe une feuille de
salaire. On sait que c'est la couche [mtier] qui fait le calcul des feuilles de salaire. Cette couche [mtier] est accessible via le modle
de l'application [ApplicationModel] que nous avons dfini au paragraphe 9.10.2, page 277 :
1.
public class ApplicationModel
2.
{
3.
// --- donnes de porte application --4.
public Employe[] Employes { get; set; }
5.
public IPamMetier PamMetier { get; set; }
6.
public SelectListItem[] EmployesItems { get; set; }
7. }
La couche [mtier] est accessible via la proprit de la ligne 5 ci-dessus. Pour que l'action [FaireSimulation] ait accs la couche
[mtier], nous allons lui passer le modle de l'application comme nous l'avons fait pour l'action [Index]. Le code volue alors comme
suit :
1.
2.
3.
4.
5.
6. }
Maintenant, nous sommes capables, l'intrieur de l'action, de calculer une feuille de salaire fictive. Le code volue comme suit :
1. // faire une simulation
2.
[HttpPost]
3.
public PartialViewResult FaireSimulation(ApplicationModel application)
4.
{
5.
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058",
150, 20);
6.
return PartialView("Simulation", feuilleSalaire);
http://tahe.developpez.com
290/362
7.
ligne 5, on calcule un salaire fictif. Le 1er paramtre est un n SS existant. Il a t dfini dans la classe [mtier] simule au
paragraphe 9.7.5, page 254. Le second paramtre est le nombre d'heures travailles et le troisime le nombre de jours
travaills ;
ligne 6 : cette feuille de salaire est passe comme modle la vue [Simulation.cshtml].
http://tahe.developpez.com
291/362
9.12.3
l'utilisateur clique sur le lien [Faire la simulation]. Cela dclenche l'excution de la fonction JS [faireSimulation] que nous
avons dj crite ;
la fonction JS [faireSimulation] fait ensuite un appel Ajax l'action serveur [/Pam/FaireSimulation] sur laquelle nous
travaillons actuellement. Pour l'instant, la fonction JS [faireSimulation] ne transmet aucune information l'action serveur. Il
faudra qu'elle lui transmette les valeurs saisies par l'utilisateur ;
l'action serveur [/Pam/FaireSimulation] va rcuprer les valeurs saisies dans les valeurs postes par la fonction JS
[faireSimulation].
Commenons par le point 2 : la fonction JS [faireSimulation] doit poster les valeurs saisies par l'utilisateur l'action serveur
[/Pam/FaireSimulation].
Travail : compltez la fonction JS [faireSimulation] afin qu'elle poste les valeurs saisies par l'utilisateur. On pourra s'aider de
l'exemple du paragraphe 7.6.5, page 230 o ce problme a t trait.
Traitons maintenant le point 3 ci-dessus. L'action serveur [/Pam/FaireSimulation] doit rcuprer les valeurs postes par la fonction
JS [faireSimulation].
Travail : compltez la mthode serveur [FaireSimulation] afin qu'elle calcule le salaire avec les valeurs postes par la fonction JS
[faireSimulation]. On pourra s'aider de nouveau de l'exemple du paragraphe 7.6.5, page 230 o ce problme a t trait. Pour le
moment, on supposera que le modle issu des valeurs postes est toujours valide.
Hint : l'action serveur [FaireSimulation] volue comme suit :
1. // faire une simulation
2.
[HttpPost]
3.
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection
data)
4.
{
5.
// cration du modle de l'action
6.
...
7.
// on essaie de rcuprer les valeurs postes dans ce modle
8.
...
9.
// on calcule le salaire
10.
FeuilleSalaire feuilleSalaire = ...
11.
// on affiche la feuille de salaire
12.
return PartialView("Simulation", feuilleSalaire);
13.
}
http://tahe.developpez.com
292/362
On a bien obtenu la feuille de salaire fictive de [Justine Laverti]. Prcdemment, la feuille de salaire unique qui tait calcule tait
celle de [Marie Jouveinal]. Donc la valeur poste pour le choix de l'employ a t exploite. Pour le nombre d'heures et le nombre
de jours, on ne peut rien dire puisque notre couche [mtier] simule n'en tient pas compte.
9.12.4
http://tahe.developpez.com
293/362
en [1], on choisit un employ qui n'existe pas (voir la dfinition de la couche [mtier] simule au paragraphe 9.7.5, page
254) ;
en [2], on fait la simulation ;
en [3] ci-dessous, on rcupre une page d'erreur.
http://tahe.developpez.com
294/362
11.
success: function (data) {
12. ...
13.
},
14.
error: function (jqXHR) {
15.
// affichage erreur
16.
simulation.html(jqXHR.responseText);
17.
simulation.show();
18.
},
19.
complete: function () {
20.
// signal d'attente teint
21.
loading.hide();
22.
}
23. });
24. // menu
25. setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession,
lnkVoirSimulations]);
26. }
L'appel Ajax a chou et c'est la fonction des lignes 14-18 qui s'est excute. La page d'erreur [jqXHR.responseText] renvoye par le
serveur a t affiche. Celle-ci est assez prcise. La couche [mtier] simule a lanc une exception parce que le n SS qu'on lui a
fourni n'est pas celui d'un employ existant (voir code de la couche [mtier] simule au paragraphe 9.7.5, page 254). Il nous faut
grer ce cas proprement.
Nous allons crer une vue partielle [Erreurs.chtml] qui sera renvoye au client JS chaque fois qu'une erreur sera dtecte ct
serveur :
http://tahe.developpez.com
295/362
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29. }
{
// calcul salaire
feuilleSalaire = ...
}
catch (Exception ex)
{
exception = ex;
}
// erreur ?
if (exception == null)
{
// on affiche la feuille de salaire
return PartialView("Simulation", feuilleSalaire);
}
else
{
// on affiche la page d'erreurs
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
lignes 9-17 : le calcul du salaire est dsormais fait dans un try / catch ;
ligne 27 : s'il y a eu erreur, on affiche la vue partielle [Erreurs.cshtml] avec pour modle la liste de messages d'erreur
fournie par la mthode statique [Static.GetErreursForException(exception)].
1. using System;
2. using System.Collections.Generic;
3. using System.Web.Mvc;
4.
5. namespace PamWeb.Infrastructure
6. {
7.
public class Static
8.
{
9.
// liste des messages d'erreur d'une exception
10.
public static List<string> GetErreursForException(Exception ex)
11.
{
12.
List<string> erreurs = new List<string>();
13.
while (ex != null)
14.
{
15.
erreurs.Add(ex.Message);
16.
ex = ex.InnerException;
17.
}
18.
return erreurs;
19.
}
20.
21.
// liste des messages d'erreur lis un modle invalide
22.
public static List<string> GetErreursForModel(ModelStateDictionary tat)
23.
{
24.
List<string> erreurs = new List<string>();
25.
if (!tat.IsValid)
26.
{
27.
foreach (ModelState modelState in tat.Values)
28.
{
http://tahe.developpez.com
296/362
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
}
}
return erreurs;
}
lignes 10-19 : la fonction statique [GetErreursForException] rend la liste des erreurs d'une pile d'exceptions ;
lignes 22-36 : la fonction statique [GetErreursForModel] rend la liste des erreurs d'un modle d'action invalide. Le code
de cette fonction ainsi que celui de la mthode prive [getErrorMessageFor] (lignes 39-54) a dj t rencontr page 73.
1
3
http://tahe.developpez.com
297/362
1.
2.
3.
En ligne 8, on met jour le modle de la ligne 6 avec les valeurs postes par l'appel Ajax. Nous ne vrifions pas la validit du
modle. Il faut le faire car on ne peut savoir d'o proviennent les valeurs postes. Quelqu'un a pu bricoler un POST et nous envoyer
des donnes invalides.
Travail : en suivant le modle que nous avons dvelopp pour le cas de l'exception, modifiez l'action serveur [FaireSimulation] afin
d'envoyer une page d'erreurs lorsque les donnes postes sont invalides. Pour cela, on utilisera la mthode statique
[GetErreursForModel] de la classe [Static].
Comment tester cette modification ? Au paragraphe 9.11.3, page 286, vous avez fait en sorte que la fonction JS [faireSimulation] ne
fasse pas le POST des valeurs saisies si celles-ci taient invalides. Mettez en commentaires les lignes qui ralisent cela puis faites le
test suivant :
Pour la suite, pensez dcommenter les lignes que vous venez de mettre en commentaires dans la fonction JS [faireSimulation].
9.13
L'application [Simulateur de calcul de paie] permet l'utilisateur de faire diverses simulations de paie avec le lien [Faire la
simulation], de les conserver avec le lien [Entegistrer la simulation], de les afficher avec le lien [Voir les simulations] et de les
supprimer avec le lien [Retirer la simulation]. Nous savons qu'entre deux requtes successives de l'utilisateur, il n'y a pas de mmoire
sauf si on en cre une par le mcanisme de la session (cf paragraphe 4.10, page 88). Il est assez clair ici que nous devons conserver
dans la session la liste des simulations enregistres au fil du temps par l'utilisateur. Il y a d'autres donnes mmoriser : lorsque
l'utilisateur fait une simulation, celle-ci n'est enregistre dans la liste des simulations que si l'utilisateur le demande avec le lien
[Enregistrer la simulation]. Lorsqu'il le fait, on doit tre capable de retrouver la simulation calcule dans la requte prcdente. Pour
cela, celle-ci sera mise galement dans la session. Enfin, nous allons numroter les simulations partir de 1. Pour numroter
correctement une nouvelle simulation, il faut avoir conserv le n de la simulation prcdente, l encore dans la session.
Dans le paragraphe 4.10, page 88, nous avons introduit le concept de modle de session en paramtre d'entre d'une action afin que
celle-ci ait accs la session. Nous allons reprendre ce concept. Vous tes invits relire le paragraphe concern si cette notion est
obscure pour vous.
http://tahe.developpez.com
298/362
La classe [Simulation] des lignes 9 et 13, va enregistrer des informations sur une simulation. Qu'avons-nous besoin d'enregistrer ? Le
lien [Faire la simulation] calcule une feuille de salaire de type [FeuilleSalaire]. Il parat naturel de mettre celle-ci dans la simulation.
Par ailleurs, il nous faut mmoriser les informations qui ont men cette feuille de salaire :
l'employ slectionn. On trouvera celui-ci dans le champ [FeuilleSalaire.Employe]. Il est donc inutile de le mmoriser une
seconde fois ;
le nombre d'heures et de jours travaills. Ces informations ne sont pas dans le type [FeuilleSalaire]. Il nous faut donc les
mmoriser.
Enfin chaque simulation est rpre par un numro. On pourrait donc partir sur la classe [Simulation] suivante :
1. using Pam.Metier.Entites;
2.
3. namespace Pam.Web.Models
4. {
5.
public class Simulation
6.
{
7.
// n de la simulation
8.
public int Num { get; set; }
9.
// le nombre d'heures travailles
10.
public double HeuresTravailles { get; set; }
11.
// le nombre de jours travaills
12.
public int JoursTravaills { get; set; }
13.
// la feuille de salaire
14.
public FeuilleSalaire FeuilleSalaire { get; set; }
15. }
16. }
http://tahe.developpez.com
299/362
L'action serveur [FaireSimulation] doit outre calculer une feuille de salaire, crer une simulation et la mettre dans la session. Pour
cela, elle va recevoir en paramtre le modle de la session :
1. // faire une simulation
2.
[HttpPost]
3.
public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel
session, FormCollection data)
4.
{
5.
// cration du modle de l'action
6.
IndexModel modle = new IndexModel() { Application = application };
7.
// on essaie de rcuprer les valeurs postes dans le modle
8.
TryUpdateModel(modle, data);
9.
// modle valide ?
10.
if (!ModelState.IsValid)
11.
{
12.
// on affiche la page d'erreurs
13.
return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
14.
}
15.
// on calcule le salaire
16.
FeuilleSalaire feuilleSalaire = null;
17.
Exception exception = null;
18.
try
19.
{
20.
// calcul salaire
21.
feuilleSalaire = application.PamMetier.GetSalaire(modle.SS,
modle.HeuresTravailles, (int)modle.JoursTravaills);
22.
}
23.
catch (Exception ex)
24.
{
25.
exception = ex;
26.
}
27.
// erreur ?
28.
if (exception != null)
29.
{
30.
// on affiche la page d'erreurs
31.
return PartialView("Erreurs", Static.GetErreursForException(exception));
32.
}
33.
// on cre une simulation et on la met dans la session
34.
session.Simulation = ...
35.
// on affiche la feuille de salaire
36.
return PartialView("Simulation", feuilleSalaire);
37.
}
9.14
9.14.1
http://tahe.developpez.com
300/362
http://tahe.developpez.com
301/362
La vue partielle [Simulations.cshtml] affiche dsormais la liste des simulations faites par l'utilisateur. On rappelle que la feuille de
salaire calcule est fictive.
9.14.2
Le lien Ajax [Enregistrer la simulation] appelle l'action serveur [EnregistrerSimulation] dont le code tait jusqu' maintenant le
suivant :
1.
2.
3.
4.
5. }
[HttpPost]
public PartialViewResult EnregistrerSimulation()
{
return PartialView("Simulations");
ligne 1 : l'action [EnregistrerSimulation] a besoin d'avoir accs la session. C'est pourquoi il a pour paramtre le modle de
la session.
9.14.3
L'action prcdente [EnregistrerSimulation] fait afficher la vue partielle [Simulations.cshtml] avec pour modle la liste des
simulations faites par l'utilisateur. Son code est le suivant :
1. @model IEnumerable<Simulation>
2.
3. @using Pam.Web.Models
4.
5. @if (Model.Count() == 0)
6. {
7.
<h2>Votre liste de simulations est vide</h2>
8. }
9. @if (Model.Count() != 0)
10. {
11. <h2>Liste des simulations</h2>
12. ...
13. }
Travail 1 : complter le code de la vue partielle [Simulations.cshtml]. On utilisera une table HTML pour l'affichage des simulations.
On pourra s'aider des exemples du paragraphe 5.4, page 107.
Note : le lien [retirer] de chaque simulation de la table HTML sera un lien Javascript de la forme suivante :
<a href="javascript:retirerSimulation(N)">retirer</a>
o N est le n de la simulation.
http://tahe.developpez.com
302/362
Travail 2 : testez votre application en faisant des simulations. Pour faire celles-ci, faites la squence suivante de faon rpte : 1)
chargez la page de l'application par [F5], 2) faites une simulation, 3) enregistrez-la. Les simulations vont s'accumuler dans la session
ce qui devrait tre reflte dans la vue [Simulations.cshtml].
Travail 3 : amliorez la vue partielle [Simulations.cshtml] de telle sorte que les couleurs des lignes de la table HTML soient
alternes.
On donnera de faon alterne aux lignes <tr> de la table HTML, les classes CSS [pair] et [impair] dfinies dans la feuille de style
[/Content/Site.css] :
1.
2.
3.
4.
5.
6.
7.
9.15
9.15.1
.impair {
background-color: beige;
}
.pair {
background-color: lightsteelblue;
}
Lorsque nous avons obtenu la liste des simulations, nous pouvons revenir au formulaire de saisie, ce qu'on ne pouvait plus faire
depuis un moment :
http://tahe.developpez.com
303/362
9.15.2
Le lien Ajax [Retour au formulaire de simulation] appelle l'action serveur [Formulaire] dont le code tait jusqu' maintenant le
suivant :
1.
2.
3.
4.
5. }
[HttpPost]
public PartialViewResult Formulaire()
{
return PartialView("Formulaire");
La vue partielle [Formulaire] qu'elle affiche attend un modle [IndexModel] (ligne 1 ci-dessous) :
1. @model Pam.Web.Models.IndexModel
2.
3. @using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id =
"formulaire" }))
4. {
5. ...
6. }
7. <div id="simulation" />
C'est pour cette raison que le lien [Retour au formulaire de simulation] ne marchait plus.
Travail : crire la nouvelle version de l'action serveur [Formulaire] (2 lignes rcrire) puis faire les tests.
9.15.3
Avec la modification faite prcdemment, on peut dsormais revenir au formulaire mais une anomalie apparat alors :
http://tahe.developpez.com
304/362
9.16
9.16.1
Le problme
Lorsqu'on travaille avec le formulaire de simulation, on peut visualiser la liste des simulations qu'on a faites :
9.16.2
Le lien Ajax [Voir les simulations] appelle l'action serveur [VoirSimulations] dont le code tait jusqu' maintenant le suivant :
1.
2.
3.
4.
5.
http://tahe.developpez.com
305/362
6. }
La vue partielle [Simulations] qu'elle affiche attend un modle [IEnumerable<Simulation>] (ligne 1 ci-dessous) :
1. @model IEnumerable<Simulation>
2.
3. @using Pam.Web.Models
4.
5. @if (Model.Count() == 0)
6. {
7.
<h2>Votre liste de simulations est vide</h2>
8. }
9. @if (Model.Count() != 0)
10. {
11. <h2>Liste des simulations</h2>
12. ...
13. }
C'est pour cette raison que le lien [Voir les simulations] ne marchait plus.
Travail : crire la nouvelle version de l'action serveur [VoirSimulations] (2 lignes rcrire) puis faire les tests.
9.17
9.17.1
On peut tout moment terminer la session de l'utilisateur avec le lien [Ajax] [Terminer la session]. Ceci a pour effet d'abandonner la
session courante pour en commencer une nouvelle. Par ailleurs, on revient la vue du formulaire :
1
2
http://tahe.developpez.com
306/362
9.17.2
en [3], cause du changement de session, la liste des simulations est dsormais vide.
Le lien Ajax [Terminer la session] appelle l'action serveur [TerminerSession] dont le code tait jusqu' maintenant le suivant :
7.
8.
9.
10.
11.
12. }
// terminer la session
[HttpPost]
public PartialViewResult TerminerSession()
{
return PartialView("Formulaire");
La vue partielle [Formulaire] qu'elle affiche attend un modle [IndexModel] (ligne 1 ci-dessous) :
8. @model Pam.Web.Models.IndexModel
9.
10. @using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id =
"formulaire" }))
11. {
12. ...
13. }
14. <div id="simulation" />
C'est pour cette raison que le lien [Terminer la session] ne marchait plus.
Travail : crire la nouvelle version de l'action serveur [TerminerSession] (2 lignes rcrire) puis faire les tests.
Note : pour abandonner la session dans l'action, on crit :
Session.Abandon() ;
9.17.3
Avec la modification faite prcdemment, on peut dsormais revenir au formulaire mais une anomalie apparat alors, celle qui a t
dcrite prcdemment au paragraphe 9.15.3, page 304.
Travail : en suivant la dmarche que vous avez suivie au paragraphe 9.15.3, page 304, corrigez la fonction Javascript
[terminerSession] puis faites des tests pour vrifier que les validateurs ct client fonctionnent de nouveau.
9.18
9.18.1
Lorsqu'on a fait une simulation, on peut l'effacer avec le lien Javascript [Effacer la simulation] :
http://tahe.developpez.com
307/362
9.18.2
Travail : compltez ce code. On pourra s'inspirer de l'exemple du paragraphe 7.6.6, page 234.
9.19
9.19.1
Lorsqu'on a la page des simulations, on peut en supprimer certaines avec le lien Javascript [retirer] :
http://tahe.developpez.com
308/362
9.19.2
o N est le n de la simulation.
Travail : en suivant la dmarche des paragraphes 9.9.3 9.9.8, crivez la fonction JS [enregistrerSimulation]. Celle-ci mettra un
appel Ajax de type POST vers l'action [/Pam/RetirerSimulation]. Elle postera la donne N sous la forme num=N.
Note : la fonction JS [enregistrerSimulation] est analogue aux autres fonction JS que vous avez crites et qui font un appel Ajax au
serveur. La seule nouveaut ici est le POST d'une valeur qui n'est pas dans un formulaire. On sait que les valeurs postes sont
rassembles dans une chane de caractres sous la forme :
param1=val1¶m2=val2&....
9.19.3
ligne 6 : la proprit [data] d'un appel Ajax JQuery reprsente la chane poste au serveur.
9.20
Notre application web est termine. Elle est fonctionnelle avec une classe [mtier] simule. Rappelons l'architecture que nous avons
dveloppe :
http://tahe.developpez.com
309/362
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
couche
[mtier]
simule
Il reste quelques dtails rgler avant de passer l'implmentation relle de la couche [mtier] et cela se passe dans la mthode
d'initialisation de l'application : la mthode [Application_Start] dans [Global.asax] :
La mthode [Application_Start] dans [Global.asax] est excute une unique fois au dmarrage de l'application. C'est l que le fichier
de configuration [Web.config] peut tre exploit. Pour l'instant, notre mthode [Application_Start] ressemble ceci :
1. // application
2.
protected void Application_Start()
3.
{
4.
// ----------Auto-gnr
5.
AreaRegistration.RegisterAllAreas();
6.
WebApiConfig.Register(GlobalConfiguration.Configuration);
7.
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
8.
RouteConfig.RegisterRoutes(RouteTable.Routes);
9.
BundleConfig.RegisterBundles(BundleTable.Bundles);
10.
// ------------------------------------------------------------------11.
// ---------- configuration spcifique
12.
// ------------------------------------------------------------------13.
// donnes de porte application
14.
ApplicationModel application = new ApplicationModel();
15.
Application["data"] = application;
16.
// instanciation couche [metier]
17.
application.PamMetier = new PamMetier();
18. ...
19.
// model binders
20. ...
21. }
En ligne 17, la couche mtier est instancie par un oprateur new. Par ailleurs, le modle de l'application est dfini comme suit :
http://tahe.developpez.com
310/362
1.
public class ApplicationModel
2.
{
3.
// --- donnes de porte application --4.
public Employe[] Employes { get; set; }
5.
public IPamMetier PamMetier { get; set; }
6.
public SelectListItem[] EmployesItems { get; set; }
7. }
Ligne 5 ci-dessus, on voit que le type de la proprit [PamMetier] est celui de l'interface [IPamMetier]. Ceci signifie que cette
proprit peut tre initialise par tout objet implmentant cette interface. Or ligne 17 de [Application_Start], nous avons crit en dur
le nom d'une classe d'implmentation de [IPamMetier]. Si donc la couche [mtier] venait tre implmente avec une nouvelle
classe implmentant [IPamMetier], il faudrait changer cette ligne. Ce n'est pas bien important mais cela peut tre vit. La dfinition
de la classe d'implmentation de l'interface [IPamMetier] peut tre dporte dans un fichier de configuration. Pour changer
d'implmentation, on change alors le contenu de ce fichier de configuration. Le code .NET n'a pas tre chang.
Nous utiliserons ici le conteneur d'injections de dpendances [Spring.net]. J'ai l'habitude d'utiliser ce framework parce que c'est le
principal framework pour faire ce genre de choses dans le monde Java et que j'ai l'habitude de l'utiliser dans ce contexte. Il existe
d'autres frameworks .NET pour faire la mme chose, peut-tre mieux et plus simplement.
L'architecture du projet volue comme suit :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
A
Contrleurs/
Actions
Modles
2b
2a
Front Controller
2c
Spring.net
couche
[mtier]
simule
en [A], la mthode d'initialisation de la couche [ASP.NET MVC] va demander [Spring.net] une rfrence sur la couche
[mtier] simule ;
en [B], [Spring.net] va crer la couche [mtier] simule en exploitant son fichier de configuration pour savoir quelle classe il
doit instancier ;
en [C], [Spring.net] va rendre la rfrence de la couche [mtier] simule la couche [ASP.NET MVC].
On notera que par dfaut, les objets grs par [Spring.net] sont des singletons : ils n'existent qu'en un unique exemplaire. Ainsi si
plus tard dans notre exemple, du code redemande [Spring.net] une rfrence sur la couche [mtier] simule, [Spring.net] se
contente de rendre la rfrence sur l'objet initialement cr.
9.20.1
Nous allons utiliser [Spring.net]. Ce framework arrive sous la forme de DLL qu'il faut ajouter aux rfrences du projet. On pourra
procder ainsi :
http://tahe.developpez.com
311/362
3
1
En [1], cliquer droit sur la branche [References] du projet puis prendre l'option [Grer les packages NuGet]. Il faut une connexion
internet. Ensuite on procdera comme il a t fait page 169 pour la bibliothque JQuery [Globalize]. On cherchera le mot cl
[Spring.core] et on installera ce package. L'installation amne deux DLL : [Spring.core] [2] et [Common.Logging] [3]. Dans les
exemples qui suivent, c'est la version 1.3.2 de Spring qui a t utilise.
Note : si vous n'avez pas de connexion Internet, vous trouverez ces DLL dans un dossier [lib] du support de cette tude de cas.
9.20.2
Configuration de [web.config]
lignes 2-8 : reprez la balise <configSections> du fichier et insrez dedans les lignes 4-7 ;
ligne 4 : l'attribut [name="spring"] indique des informations concernant la section [spring] des lignes 10-17 ;
ligne 5 : dfinit la classe [ Spring.Context.Support.DefaultSectionHandler] situe dans la DLL [ Spring.Core]
comme celle capable de traiter la section [objects] des lignes 14-16 ;
ligne 6 : dfinit la classe [Spring.Context.Support.ContextHandler] situe dans la DLL [Spring.Core] comme
celle capable de traiter la section [context] des lignes 11-13 ;
lignes 11-13 : cette section apporte l'information [<resource uri="config://spring/objects" />] qui indique que
les objets Spring se trouvent dans le fichier de configuration dans la section [/spring/objects], --d aux lignes 14-16 ;
lignes 14-16 : la balise [objects] introduit les objets Spring ;
ligne 15 : dfinit un objet identifi par [ id="pammetier"] qui est une instance de la classe
[Pam.Metier.Service.PamMetier] situe dans la DLL [ pam-metier-simule]. L, il ne faut pas se tromper. Pour
l'attribut [id], vous pouvez mettre ce que vous voulez. Vous allez utiliser cet identifiant dans [Global.asax]. La classe
[Pam.Metier.Service.PamMetier] est celle de notre couche [mtier] simule. Il faut revenir sa dfinition pour
connatre son nom complet :
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
http://tahe.developpez.com
312/362
{
...
9.20.3
Modification de [Application_Start]
ligne 19 : on utilise la classe Spring [ContextRegistry] qui est une classe capable d'exploiter le fichier [web.config]. Pour
cela, on a besoin d'importer l'epace de noms de la ligne 1. La mthode statique [GetContext] permet d'avoir le contenu des
balises [context] qui indiquent o se trouvent les objets Spring. La mthode statique[GetObject] permet ensuite d'avoir
un objet particulier identifi par son attribut id. On notera que maintenant, le nom de la classe d'implmentation de
l'interface [IPamMetier] n'est plus inscrit en dur dans le code. Il est maintenant dans le fichier [web.config].
Aprs avoir fait toutes ces modifications, testez votre application. Elle doit marcher.
9.20.4
http://tahe.developpez.com
313/362
Nous allons grer une ventuelle exception dans un try / catch. Le code volue comme suit :
1. // application
2.
protected void Application_Start()
3.
{
4.
// ----------Auto-gnr
5. ...
6.
// ------------------------------------------------------------------7.
// ---------- configuration spcifique
8.
// ------------------------------------------------------------------9.
// donnes de porte application
10.
ApplicationModel application = new ApplicationModel();
11.
Application["data"] = application;
12.
application.InitException = null;
13.
try
14.
{
15.
// instanciation couche [metier]
16.
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as
IPamMetier;
17.
}
18.
catch (Exception ex)
19.
{
20.
application.InitException = ex;
21.
}
22.
//si pas d'erreur
23.
if (application.InitException == null)
24.
{
25. ....
26.
}
27.
// model binders
28. ...
29.
}
ligne 12, nous introduisons une nouvelle proprit nomme [InitException] dans le modle de l'application :
1.
public class ApplicationModel
2.
{
3.
// --- donnes de porte application --4.
public Employe[] Employes { get; set; }
5.
public IPamMetier PamMetier { get; set; }
6.
public SelectListItem[] EmployesItems { get; set; }
7.
public Exception InitException { get; set; }
8. }
On sait qu'au dmarrage de l'application, l'action serveur [Index] est excute. pour l'instant c'est la suivante :
1.
2.
3.
4.
5. }
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
http://tahe.developpez.com
314/362
Ligne 2, l'action [Index] reoit le modle de l'application. Elle peut donc savoir si l'initialisation s'est bien passe ou non et afficher
une page d'erreurs si l'initialisation a chou d'une faon ou d'une autre. Nous faisons voluer le code de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12. }
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
// erreur d'initialisation ?
if (application.InitException != null)
{
// page d'erreurs sans menu
return View("InitFailed",Static.GetErreursForException(application.InitException));
}
// pas d'erreur
return View(new IndexModel() { Application = application });
Ligne 8, en cas d'erreur d'initialisation, nous affichons la vue [InitFailed.cshtml] avec pour modle la liste des messages d'erreur de
l'exception qui s'est produite lors de l'initialisation. La mthode [Static.GetErreursForException] a t prsente et explique au
paragraphe 9.12.4, page 293. La vue [InitFailed.cshtml] sera la suivante :
http://tahe.developpez.com
315/362
31. </html>
ligne 1 : le modle de la vue est une liste de messages d'erreur. Ceux-ci sont affichs dans une liste HTML aux lignes 2429 ;
ligne 3 : cette vue n'utilise pas la page matre [_Layout.cshtml]. En effet, on ne veut pas du menu amen par ce document.
On construit donc une page HTML complte (lignes 5-23).
Pour tester, il suffit de modifier dans [Application_Start], l'instanciation de la couche [mtier] comme suit :
1.
2.
3.
4.
5.
6.
7.
8.
9. }
try
{
// instanciation couche [metier]
application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
Ligne 4, on cherche un objet qui n'existe pas dans les objets Spring.
Lorsqu'on valide ces modifications et qu'on lance l'application, on obtient la page suivante :
On obtient une page d'erreurs sans menu. L'utilisateur ne peut rien faire d'autre que constater l'erreur. C'est ce qui tait souhait.
9.21
O en sommes nous ?
Nous avons dsormais une application web oprationnelle qui travaille avec une couche mtier simule. Son architecture est la
suivante :
http://tahe.developpez.com
316/362
Navigateur
Application web
couche [ASP.NET MVC]
1
HTML
Vue1
Vue2
4b
JS
Vuen
A
2b
2a
Front Controller
3
Contrleurs/
Actions
couche
[mtier]
simule
2c
Modles
Spring.net
La couche [ASP.NET MVC] travaille avec la couche mtier simule au-travers de l'interface [IPamMetier]. Si nous remplaons cette
couche mtier simule par une couche mtier relle qui respecte cette interface, nous n'aurons pas modifier le code de la couche
web. Grce [Spring.net], nous aurons juste changer dans [web.config] la classe d'implmentation de l'interface [IPamMetier].
Nous partons sur cette voie.
La nouvelle architecture sera la suivante :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
4b
JS
Vue1
Vue2
Vuen
A
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
2c
Spring.net
couches
[mtier,
DAO, EF5]
Base de
donnes
la couche [EF5] connecte au SGBD. Elle sera implmente avec Entity Framework 5 (EF5) ;
la couche [DAO] qui gre l'accs aux donnes via la couche [EF5]. Cela lui permet d'ignorer l'existence du SGBD. Cette
couche se contente de manipuler les entits de l'application [Employe, Cotisations, Indemnites] ;
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[ORM]
Connecteur
[ADO.NET]
Sgbd
Spring.net
la couche [Web] est la couche en contact avec l'utilisateur de l'application Web. Celui-ci interagit avec l'application Web au
travers de pages Web visualises par un navigateur. C'est dans cette couche que se situe ASP.NET MVC et
uniquement dans cette couche ;
la couche [mtier] implmente les rgles de gestion de l'application, tels que le calcul d'un salaire ou d'une facture. Cette
couche utilise des donnes provenant de l'utilisateur via la couche [Web] et du SGBD via la couche [DAO] ;
http://tahe.developpez.com
317/362
la couche [DAO] (Data Access Objects), la couche [ORM] (Object Relational Mapper) et le connecteur ADO.NET grent
l'accs aux donnes du SGBD. La couche [ORM] fait un pont entre les objets manipuls par la couche [DAO] et les
lignes et les colonnes des donnes d'une base de donnes relationnelle. Deux ORM sont couramment utiliss dans le
monde .NET, NHibernate (http://sourceforge.net/projects/nhibernate/ ) et Entity Framework
(http://msdn.microsoft.com/en-us/data/ef.aspx ) ;
l'intgration des couches peut tre ralise par un conteneur d'injection de dpendances (Dependency Injection Container)
tel que Spring (http://www.springframework.net/ ) ;
Les couches [mtier], [DAO], [EF5] vont tre implmentes l'aide de projets C#. A partir de maintenant, nous travaillons avec
Visual Studio Express 2012 pour le bureau.
9.22
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
La cration de la couche [EF5] est moins affaire de codage que de configuration. Pour apprhender l'criture de cette couche, on
lira
le
document
[Introduction
Entity
Framework
5
Code
First]
disponible
l'URL
[http://tahe.developpez.com/dotnet/ef5cf-02/]. C'est un document assez volumineux. Les fondamentaux sont dans les quatre
premiers chapitres. Les paragraphes lire plus particulirement seront prciss. Lorsque nous ferons rfrence ce document nous
utiliserons la notation [refEF5].
Par ailleurs, nous aurons parfois besoin de concepts C#. On rfrencera alors le cours [Introduction au langage C#] disponible
l'URL [http://tahe.developpez.com/dotnet/csharp/] avec la notation [refC#].
9.22.1
La base de donnes
La base de donnes de l'application a t prsente au paragraphe 9.4, page 243. C'est une base de donnes MySQL nomme
[dbpam_ef5] (pam=Paie Assistante Maternelle). Cette base a un administrateur appel root sans mot de passe.
Rappelons le schma de la base de donnes. Elle a trois tables :
http://tahe.developpez.com
318/362
Il y a une relation de cl trangre entre la colonne EMPLOYES(INDEMNITE_ID) et la colonne INDEMNITES(ID). Une partie
de la structure de cette base est dicte par son utilisation avec EF5.
Le script SQL de cration de la base est le suivant :
1. -- phpMyAdmin SQL Dump
2. -- version 3.5.1
3. -- http://www.phpmyadmin.net
4. -5. -- Client: localhost
6. -- Gnr le: Lun 04 Novembre 2013 09:34
7. -- Version du serveur: 5.5.24-log
8. -- Version de PHP: 5.4.3
9.
10. SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
11. SET time_zone = "+00:00";
12.
13.
14. /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
15. /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
16. /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
17. /*!40101 SET NAMES utf8 */;
18.
19. -20. -- Base de donnes: `dbpam_ef5`
21. -22.
23. -- -------------------------------------------------------24.
25. -26. -- Structure de la table `cotisations`
27. -28.
29. CREATE TABLE IF NOT EXISTS `cotisations` (
30. `ID` bigint(20) NOT NULL AUTO_INCREMENT,
31. `SECU` double NOT NULL,
32. `RETRAITE` double NOT NULL,
33. `CSGD` double NOT NULL,
http://tahe.developpez.com
319/362
http://tahe.developpez.com
320/362
http://tahe.developpez.com
321/362
157. END
158. //
159. DELIMITER ;
160.
161. -162. -- Contraintes pour les tables exportes
163. -164.
165. -166. -- Contraintes pour la table `employes`
167. -168. ALTER TABLE `employes`
169.
ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES
`indemnites` (`ID`);
170.
171. /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
172. /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
173. /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
lignes 30, 73, 122 : les cls primaires des tables sont en mode [AUTO_INCREMENT]. C'est MySQL qui les gre et non
EF5 ;
ligne 83 : le n SS a une contrainte d'unicit ;
ligne 130 : l'indice de l'employ a une contrainte d'unicit ;
lignes 168-169 : la cl trangre de la table [employes] vers la table [indemnites] ;
ligne 49 : un dclencheur ou [Trigger] est un script SQL embarqu par le SGBD et qui est excut certains moments ;
lignes 51-54 : le dclencheur [INCR_VERSIONING_COTISATIONS] se dclenche avant toute modification d'une ligne
de la table [cotisations]. Il incrmente alors d'une unit la colonne [VERSIONING] ;
lignes 59-62 : le dclencheur [START_VERSIONING_COTISATIONS] se dclenche avant toute insertion d'une
nouvelle ligne dans la table [cotisations]. Il initialise alors 1 la colonne [VERSIONING] ;
au final, la colonne [VERSIONING] vaut 1 lorsqu'une ligne est cre dans la table [cotisations] puis est incrmente de 1
chaque modification faite sur cette ligne. Ce mcanisme permet EF5 de grer la concurrence d'accs une ligne de la
table [cotisations] de la faon suivante :
un processus P1 lit une ligne L de la table [cotisations] au temps T1. La ligne a la colonne
[VERSIONING] V1 ;
un processus P2 lit la mme ligne L de la table [cotisations] au temps T2. La ligne a la colonne
[VERSIONING] V1 parce que le processus P1 n'a pas encore valid sa modification ;
le processus P2 fait ensuite de mme. EF5 lance alors une exception car le processus P2 a une ligne
avec une colonne [VERSIONING] ayant une valeur V1 diffrente de celle trouve en base qui est V1+1. On
ne peut modifier une ligne que si on a la mme valeur de [VERSIONING] que dans la base.
On appelle cela la gestion optimiste des accs concurrents. Avec EF5, un champ jouant ce rle doit avoir l'annotation
[ConcurrencyCheck].
un mcanisme analogue est cr pour la table [employes] (lignes 98-113) et la table [indemnites] (lignes 144-159).
Travail : crez la base de donnes MySQL [dbpam_ef5] l'aide du script SQL prcdent. La base [dbpam_ef5] doit tre cre
auparavant car le script ne la cre pas. On jouera ensuite le script SQL sur cette base.
9.22.2
Avec Visual Studio Express 2012 pour le bureau, nous chargeons la solution [pam-td] utilise lors de la construction de la couche
[web] :
http://tahe.developpez.com
322/362
en [1], VS 2012 Express pour le bureau n'arrive pas charger le projet web [pam-web-01]. C'est normal et ce n'est pas
gnant ;
en [2], on ajoute un nouveau projet la solution [pam-td] ;
5
http://tahe.developpez.com
323/362
9.22.3
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Notre projet a besoin d'un certain nombre de DLL :
Nous avons plac d'autres DLL dans le dossier [lib]. Nous les utiliserons ultrieurement. En [2], nous ajoutons ces nouvelles DLL
au projet.
http://tahe.developpez.com
324/362
3
5
Il nous faut une autre DLL. Celle-ci sera trouve parmi celles du framework .NET de la machine.
9.22.4
Les entits Entity framework sont des classes dans lesquelles on encapsule les lignes des diffrentes tables de la base de donnes.
Rappelons celles-ci :
http://tahe.developpez.com
325/362
Dans la couche [web], nous avions utilis les entits [Employe, Cotisations, Indemnits] (voir paragraphe 9.7.3, page 250). Elles
n'taient pas des images fidles des tables. Ainsi les colonnes [ID, VERSIONING] avaient t ignores. Ici, ce ne va pas tre le cas
car elles sont utilises par l'ORM EF5. Nous allons donc leur ajouter les proprits manquantes. Nous crons ces entits dans un
dossier [Models] du projet :
http://tahe.developpez.com
326/362
17.
Classe [Indemnites]
1. using System;
2.
3. namespace Pam.EF5.Entites
4. {
5.
public class Indemnites
6.
{
7.
public int Id { get; set; }
8.
public int Indice { get; set; }
9.
public double BaseHeure { get; set; }
10.
public double EntretienJour { get; set; }
11.
public double RepasJour { get; set; }
12.
public double IndemnitesCp { get; set; }
13.
public int Versioning { get; set; }
14.
15.
// signature
16.
public override string ToString()
17.
{
18.
return string.Format("Indemnits[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning,
Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
19.
}
20. }
21. }
Classe [Employe]
1. using System;
2.
3. namespace Pam.EF5.Entites
4. {
5.
6.
public class Employe
7.
{
8.
public int Id { get; set; }
9.
public string SS { get; set; }
10.
public string Nom { get; set; }
11.
public string Prenom { get; set; }
12.
public string Adresse { get; set; }
13.
public string Ville { get; set; }
14.
public string CodePostal { get; set; }
15.
public Indemnites Indemnites { get; set; }
16.
public int Versioning { get; set; }
17.
18.
// signature
19.
public override string ToString()
20.
{
21.
return string.Format("Employ[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning,
SS, Nom, Prenom, Adresse, Ville, CodePostal);
http://tahe.developpez.com
327/362
22.
}
23. }
24. }
Pour tre utilisables par l'ORM EF5, les proprits de ces classes doivent tre dcores par des annotations.
Travail : en vous aidant du paragraphe 3.4 [Cration de la base partir des entits] de [ refEF5], ajoutez aux entits [Employe,
Cotisations, Indemnites] les annotations ncessaires EF5.
Conseils :
il s'agit seulement de crer des annotations. Ne suivez pas la partie [cration de base] du paragraphe rfrenc ;
pour l'annotation [Table], vous suivrez l'exemple MySQL du paragraphe 4.2 de [refEF5] ;
pour l'annotation [ConcurrencyCheck] sur la proprit [Versioning], vous suivrez l'exemple Oracle du paragraphe 5.2 de
[refEF5] ;
pour la cl trangre que possde la table [employes] sur la table [indemnits], vous suivrez l'exemple 3.4.2 de [refEF5].
Vous ajouterez ainsi une nouvelle proprit l'entit [Employe] :
public int IndemniteId { get; set; }
dont la valeur sera celle de la colonne [INDEMNITES_ID] de la table [employes]. Vous mettrez aux proprits [IndemniteId] et
[Indemnites] de l'entit [Employe] les annotations de cl trangre. Pour cela, suivez l'exemple 3.4.2 de [refEF5] ;
9.22.5
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
La couche [EF5] va accder la base de donnes via le connecteur [ADO.NET] du SGBD MySQL. Elle a besoin d'un certain
nombre de renseignements pour accder cette base. Ceux-ci sont placs divers endroits du projet.
Nous devons tout d'abord crer le contexte de la base de donnes. Ce contexte est une classe drive de la classe systme
[System.Data.Entity.DbContext]. Elle sert dfinir les images objets des tables de la base de donnes. Nous placerons cette classe
dans le dossier [Models] du projet avec les entitts EF5 :
http://tahe.developpez.com
328/362
5. {
6.
public class DbPamContext : DbContext
7.
{
8.
public DbSet<Employe> Employes { get; set; }
9.
public DbSet<Cotisations> Cotisations { get; set; }
10.
public DbSet<Indemnites> Indemnites { get; set; }
11. }
12. }
Nous appellerons par la suite la classe [DbPamContext] contexte de persistance de la base de donnes [dbpam_ef5]. C'est une
terminologie habituelle dans les ORM (Object Relational Mapper). Ce contexte de persistance est une image objet de la base de
donnes. On parle galement de synchronisation du contexte de persistance avec la base de donnes : les modifications, ajouts,
suppressions faits sur le contexte de persistance sont rpercuts sur la base de donnes. Cette synchronisation se fait des moments
prcis : la fermeture du contexte de persistance, la fin d'une transaction ou avant une requte SQL SELECT sur la base.
Les informations sur le SGBD et la base de donnes sont places dans [app.config].
La configuration ncessaire dans [app.config] est explique aux paragraphes suivants de [refEF5] :
3.4 pour le SGBD SQL Server. C'est l que les grands principes de la configuration d'EF5 sont poss ;
les lignes 6-19 ont t ajoutes. Elles doivent s'insrer dans la balise <configuration> des lignes 2 et 20 ;
http://tahe.developpez.com
329/362
lignes 7-11 : dfinissent des chanes de connexion des bases de donnes, un concept ADO.NET (voir paragraphe 7.3.5
dans [refC#]) ;
lignes 8-10 : dfinissent la chane de connexion la base de donnes MySQL [dbpam_ef5] :
ligne 8 : le nom de la chane de connexion. Ici, on ne peut pas mettre n'importe quoi. Par dfaut, il faut mettre le nom
de la classe implmentant le contexte de la base de donnes :
1. public class DbPamContext : DbContext
2. {
3.
public DbSet<Employe> Employes { get; set; }
4.
public DbSet<Cotisations> Cotisations { get; set; }
5.
public DbSet<Indemnites> Indemnites { get; set; }
6. }
C'est tout. C'est compliqu et obscur lorsqu'on le fait la premire fois, puis au fil du temps cela devient simple car c'est toujours la
mme chose que l'on rpte.
9.22.6
Nous sommes prts tester notre couche [EF5]. On le fait l'aide du programme [Program.cs] dj prsent :
http://tahe.developpez.com
330/362
Nous allons afficher le contenu de la base. Si on y arrive ce sera un dbut d'indication que notre configuration est correcte. Un
exemple de code est disponible au paragraphe 3.5.3 de [refEF5]. Le code de [Program.cs] sera le suivant :
1. using Pam.EF5.Entites;
2. using Pam.Models;
3. using System;
4.
5. namespace Pam
6. {
7.
class Program
8.
{
9.
static void Main(string[] args)
10.
{
11.
try
12.
{
13.
using (var context = new DbPamContext())
14.
{
15.
// on affiche le contenu des tables
16.
Console.WriteLine("Liste des employs ----------------------------------------");
17.
foreach (Employe employe in context.Employes)
18.
{
19.
Console.WriteLine(employe);
20.
}
21.
Console.WriteLine("Liste des indemnits --------------------------------------");
22.
foreach (Indemnites indemnite in context.Indemnites)
23.
{
24.
Console.WriteLine(indemnite);
25.
}
26.
Console.WriteLine("Liste des cotisations -------------------------------------");
27.
foreach (Cotisations cotisations in context.Cotisations)
28.
{
29.
Console.WriteLine(cotisations);
30.
}
31.
}
32.
}
33.
catch (Exception e)
34.
{
35.
Console.WriteLine(e);
36.
return;
37.
}
38.
}
39. }
40. }
ligne 13 : toute opration sur la BD se fait au travers du contexte de cette base. Nous avons implment ce contexte avec
la classe [DbPamContext]. Nous l'avons appel galement contexte de persistance de la base ;
lignes 13, 31 : les oprations sur le contexte de persistance se font dans une clause [using]. Le contexte de persistance est
ouvert au dbut de la clause [using] et automatiquement ferm la sortie de cette clause. Cela implique que toute
modification faite sur le contexte de persistance dans la clause [using] sera rpercute sur la base de donnes la sortie de
la clause. Une srie d'ordres SQL est alors envoye la BD l'intrieur d'une transaction. Ce qui veut dire que si un ordre
SQL choue, tous les ordres SQL mis prcdemment sont annuls. Une exception est alors lance par EF5 ;
http://tahe.developpez.com
331/362
ligne 17 : l'expression [context.Employes] dsigne l'image objet de la table [employes]. On rappelle que [Employes] est une
proprit du contexte de persistance [DbPamContext] :
1.
public class DbPamContext : DbContext
2.
{
3.
public DbSet<Employe> Employes { get; set; }
4.
public DbSet<Cotisations> Cotisations { get; set; }
5.
public DbSet<Indemnites> Indemnites { get; set; }
6. }
ligne 17 : le fait que le [foreach] parcourt la collection [context.Employes] va ramener tous les employs de la base de
donnes dans le contexte de persistance. Un ordre SQL SELECT va donc tre mis par EF5 ;
lignes 17-20 : on parcourt la collection des employs et ligne 19, on utilise la mthode [ToString] de la classe [Employe]
pour afficher les employs sur la console ;
lignes 21-25 : idem pour la collection des indemnits ;
lignes 27-30 : idem pour la collection des cotisations.
Lorsqu'on ramne un employ dans le contexte de persistance, ramne-t-on son indemnit avec ? La rponse est non par dfaut.
C'est la notion de [Lazy Loading]. Les entits rfrences au sein d'une autre entit ne sont pas amenes dans le contexte de
persistance avec cette autre entit. Elles ne le sont que lorsqu'elles sont demandes par le code au sein d'un contexte de persistance
ouvert. Si le contexte de persistance est ferm, une exception est alors lance.
Ainsi, si la mthode [ToString] avait rfrenc la proprit [Indemnites] comme ci-aprs :
1.
2.
3.
4.
// signature
public override string ToString()
{
return string.Format("Employ[{0},{1},{2},{3},{4},{5},{6},{7},{8}]", Id,
Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
5. }
http://tahe.developpez.com
332/362
1.
2.
3.
4. }
aurait ramen dans le contexte de persistance non seulement les employs mais galement leurs indemnits, parce que ligne 3, la
mthode [Employe.ToString] est appele et qu'elle rfrence l'entit [Indemnites].
L'excution de [Program.cs] donne les rsultats suivants :
1.
2.
3.
4.
5.
6.
7.
8.
Que faire si a ne marche pas ? Vous tes mal... Il y a de nombreuses sources d'erreur possibles :
9.22.7
Nous faisons de notre projet une bibliothque de classes afin qu' la gnration, un assembly .dll soit gnr plutt qu'un .exe. Cela
se fait dans les proprits du projet comme il a t vu au paragraphe 9.7.6, page 256 pour la couche mtier simule.
Travail : transformez le type du projet [pam-ef5] en bibliothque de classes puis rgnrez le projet.
9.23
9.23.1
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Comme nous l'avions fait pour la couche [mtier] simule, la couche [DAO] sera accessible via une interface. Quelle sera-t-elle ?
Regardons l'interface [IPamMetier] de la couche [mtier] simule que nous avons construite :
1.
public interface IPamMetier {
2.
// liste de toutes les identits des employs
3.
Employe[] GetAllIdentitesEmployes();
4.
5.
// ------- le calcul du salaire
6.
FeuilleSalaire GetSalaire(string ss, double heuresTravailles, int joursTravaills);
7. }
http://tahe.developpez.com
333/362
Les informations des lignes 5 et 6 viendront de la base de donnes. Rappelons qu'un employ a une proprit [Indemnites]. Cette
information devra tre ramene galement.
On pourrait donc partir avec l'interface suivante pour la couche [DAO] :
1.
public interface IPamDao {
2.
// liste de toutes les identits des employs
3.
Employe[] GetAllIdentitesEmployes();
4.
// un employ particulier avec ses indemnits
5.
Employe GetEmploye(string ss);
6.
// liste de toutes les cotisations
7.
Cotisations GetCotisations();
8. }
9.23.2
Travail : ajouter la solution [pam-td] un nouveau projet de type [console] appel [pam-dao]. Faites-en le projet de dmarrage de la
solution.
9.23.3
http://tahe.developpez.com
334/362
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Le projet [pam-dao] a besoin d'un certain nombre de DLL :
9.23.4
Ci-dessus, la classe [PamException] est celle qui a t dfinie au paragraphe 9.7.4, page 253. On change simplement son espace de
noms (ligne 1 ci-dessous) :
1. namespace Pam.Dao.Entites
2. {
3.
// classe d'exception
4.
public class PamException : Exception
5.
{
6. ....
7.
}
http://tahe.developpez.com
335/362
8. }
L'interface [IPamDao] est celle que nous venons de dfinir au paragraphe 9.23.1, page 333 :
1. using Pam.EF5.Entites;
2.
3. namespace Pam.Dao.Service
4. {
5.
public interface IPamDao
6.
{
7.
// liste de toutes les identits des employs
8.
Employe[] GetAllIdentitesEmployes();
9.
// un employ particulier avec ses indemnits
10.
Employe GetEmploye(string ss);
11.
// liste de toutes les cotisations
12.
Cotisations GetCotisations();
13. }
14. }
La classe [PamDaoEF5] implmente cette interface l'aide de l'ORM EF5. Son code est le suivant :
1. using Pam.Dao.Entites;
2. using Pam.EF5.Entites;
3. using Pam.Models;
4. using System;
5. using System.Linq;
6.
7. namespace Pam.Dao.Service
8. {
9.
10. public class PamDaoEF5 : IPamDao
11. {
12.
// champs privs
13.
private Cotisations cotisations;
14.
private Employe[] employes;
15.
16.
// Constructeur
17.
public PamDaoEF5()
18.
{
19.
// cotisation
20.
try
21.
{
22. ....
23.
}
24.
catch (Exception e)
25.
{
26.
throw new PamException("Erreur systme lors de la construction de la couche [DAO]",
e, 1);
27.
}
28.
}
29.
30.
// GetCotisations
31.
public Cotisations GetCotisations()
32.
{
33.
return cotisations;
34.
}
35.
36.
// GetAllIdentitesEmploye
37.
public Employe[] GetAllIdentitesEmployes()
38.
{
39.
return employes;
40.
}
41.
http://tahe.developpez.com
336/362
42.
// GetEmploye
43.
public Employe GetEmploye(string SS)
44.
{
45.
try
46.
{
47. ....
48.
catch (Exception e)
49.
{
50.
throw new PamException(string.Format("Erreur systme lors de la recherche de
l'employ [{0}]", SS), e, 2);
51.
}
52.
}
53. }
54. }
A savoir :
9.23.5
Comme il a t fait au paragraphe 9.22.5, page 328, il nous faut configurer EF5 dans le fichier [app.config] du projet :
Travail 1 : configurez EF5 dans [app.config]. Il suffit de reprendre ce qui a t fait dans le fichier [app.config] de la couche [EF5].
Notre programme de test va utiliser [Spring.net] pour obtenir une rfrence sur la couche [DAO].
Travail 2 : en vous aidant de ce qui a t fait au paragraphe 9.20.2, page 312, modifiez le fichier de configuration [app.config] du
projet [pam-dao] afin qu'il dfinisse un objet Spring appel [pamdao] associ la classe [PamDaoEF5] que nous venons de
construire. Les fichiers [app.config] et [web.config] ont la mme structure. Il faut faire attention ce que la balise
<configSections> soit la premire balise rencontre derrire la balise racine <configuration>.
9.23.6
Nous sommes prts tester notre couche [DAO]. On le fait l'aide du programme [Program.cs] dj prsent :
http://tahe.developpez.com
337/362
Nous allons tester les diffrentes fonctionnalits de l'interface de la couche [DAO]. Le code de [Program.cs] sera le suivant :
1. using Pam.Dao.Service;
2. using Pam.EF5.Entites;
3. using Spring.Context.Support;
4. using System;
5.
6. namespace Pam.Dao.Tests
7. {
8.
public class Program
9.
{
10.
public static void Main()
11.
{
12.
try
13.
{
14.
// instanciation couche [dao]
15.
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
16.
// liste des identits des employs
17.
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
18.
{
19.
Console.WriteLine(Employe.ToString());
20.
}
21.
// un employ avec ses indemnits
22.
Console.WriteLine("------------------------------------");
23.
Console.WriteLine("Employ n 254104940426058");
24.
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
25.
Console.WriteLine("------------------------------------");
26.
// un employ qui n'existe pas
27.
Employe employe = pamDao.GetEmploye("xx");
28.
Console.WriteLine("Employ n xx");
29.
Console.WriteLine((employe == null ? "null" : employe.ToString()));
30.
Console.WriteLine("------------------------------------");
31.
// liste des cotisations
32.
Cotisations cotisations = pamDao.GetCotisations();
33.
Console.WriteLine(cotisations.ToString());
34.
}
35.
catch (Exception ex)
36.
{
37.
// affichage exception
38.
Console.WriteLine(ex.ToString());
39.
}
40.
//pause
41.
Console.ReadLine();
42.
}
43. }
44. }
http://tahe.developpez.com
338/362
9.23.7
Travail : transformez le type du projet [pam-dao] en bibliothque de classes puis rgnrez le projet (refaire ce qui a t fait au
paragraphe 9.22.7, page 333).
9.24
9.24.1
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
L'interface de la couche [mtier] sera l'interface [IPamMetier] de la couche [mtier] simule que nous avons construite au
paragraphe 9.7.2, page 248.
8.
public interface IPamMetier {
9.
// liste de toutes les identits des employs
10.
Employe[] GetAllIdentitesEmployes();
11.
12.
// ------- le calcul du salaire
13.
FeuilleSalaire GetSalaire(string ss, double heuresTravailles, int joursTravaills);
14. }
9.24.2
Travail : ajouter la solution [pam-td] un nouveau projet de type [console] appel [pam-metier]. Faites-en le projet de dmarrage de
la solution.
9.24.3
http://tahe.developpez.com
339/362
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Le projet [pam-metier] a besoin d'un certain nombre de DLL :
9.24.4
Ci-dessus, on retrouve quatre lments dj utiliss dans la couche [mtier] simule (voir paragraphe 9.7, page 246). Il peut y avoir
des changements pour les espaces de noms imports par ces diffrentes classes. Grez-les. La classe [PamMetier] implmente
l'interface [IPamMetier] de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
using
using
using
using
Pam.Dao.Service;
Pam.EF5.Entites;
Pam.Metier.Entites;
System;
namespace Pam.Metier.Service
{
http://tahe.developpez.com
340/362
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
// calcul du salaire
public FeuilleSalaire GetSalaire(string ss, double heuresTravailles, int
joursTravaills)
35.
{
36.
// SS : n SS de l'employ
37.
// HeuresTravailles : le nombre d'heures travaills
38.
// Jours Travaills : nbre de jours travaills
39. ...
40. }
41. }
ligne 13 : on a une rfrence sur la couche [DAO]. Elle sera initialise par Spring lors de l'instanciation de la classe
[PamMetier]. Donc lorsque les diffrentes mthodes s'excutent, la ligne 13 a dj t initialise.
Travail : complter le code de la classe [PamMetier]. Si dans [GetSalaire] on dcouvre que l'employ de n ss n'existe pas, on lancera
une [PamException]. Le mode de calcul du salaire est expliqu au paragraphe 9.5, page 245. On fera attention d'arrondir tous les
calculs intermdiares deux chiffres aprs la virgule.
9.24.5
Comme il a t fait au paragraphe 9.22.5, page 328, il nous faut configurer EF5 dans le fichier [app.config] du projet :
Travail 1 : configurez EF5 dans [app.config]. Il suffit de reprendre ce qui a t fait dans le fichier [app.config] de la couche [EF5].
Notre programme de test va utiliser [Spring.net] pour obtenir une rfrence sur la couche [mtier].
Travail 2 : en vous aidant de ce que vous avez fait prcdemment au paragraphe 9.23.5, page 337, modifiez le fichier de
configuration [app.config] du projet [pam-metier] afin qu'il dfinisse un objet Spring appel [pammetier] associ la classe
http://tahe.developpez.com
341/362
[PamMetier] que nous venons de construire. Le plus simple est de recopier le fichier [app.config] du projet [pam-dao] et d'ajouter ce
qui manque.
Il y a une difficult ici. Non seulement, il faut instancier la couche [mtier] avec la classe [PamMetier] mais il faut galement
initialiser sa proprit [PamDao] :
// rfrence sur la couche [DAO] initialise par Spring
public IPamDao PamDao { get; set; }
9.24.6
Nous sommes prts tester notre couche [metier]. On le fait l'aide du programme [Program.cs] dj prsent :
Nous allons tester les diffrentes fonctionnalits de l'interface de la couche [mtier]. Le code de [Program.cs] sera le suivant :
1. using System;
2. using Pam.Dao.Entites;
3. using Pam.Metier.Service;
4. using Spring.Context.Support;
5. using Pam.EF5.Entites;
6.
7. namespace Pam.Metier.Tests
8. {
9.
public class Program
10. {
11.
public static void Main()
12.
{
13.
try
14.
{
15.
// instanciation couche [metier]
16.
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as
IPamMetier;
17.
// liste des identits des employs
http://tahe.developpez.com
342/362
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
Console.WriteLine("Employs -----------------------------");
foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe);
}
// calculs de feuilles de salaire
Console.WriteLine("salaires -----------------------------");
Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
try
{
Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
}
catch (PamException ex)
{
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Exception : {0}, Exception interne : {1}",
ex.Message, ex.InnerException == null ? "" : ex.InnerException.Message));
40.
}
41.
// pause
42.
Console.ReadLine();
43.
}
44. }
45. }
9.24.7
Travail : transformez le type du projet [pam-metier] en bibliothque de classes puis rgnrez le projet (refaire ce qui a t fait au
paragraphe 9.22.7, page 333).
9.25
http://tahe.developpez.com
343/362
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Nous allons rutiliser la couche [web] que nous avions dveloppe avec l'aide d'une couche [mtier] simule.
9.25.1
Nous revenons Visual Studio Express 2012 pour le web afin de brancher notre couche web aux couches [mtier, DAO, EF5] que
nous venons de dvelopper. Il y a surtout de la configuration faire et quelques changements d'espaces de noms.
Avec Visual Studio Express 2012 pour le web, chargez la solution [pam-td] :
en [1], la solution [pam-td] dans VS Studio pour le web. Le projet web [pam-web-01] redevient visible. On l'avait perdu
dans VS Studio pour le bureau.
la configuration du projet web [pam-web-01] va devoir tre modifie. Plutt que de modifier un projet qui fonctionne, on
va faire les modifications sur une copie de ce projet. Tout d'abord en [2], on supprime le projet de la solution (a ne
supprime rien dans le systme de fichiers).
http://tahe.developpez.com
344/362
en [6], chargez l'ancien projet [pam-web-01]. Vous avez dsormais tous vos projets. Faites attention de travailler avec [pamweb-02].
9.25.2
Utilisateur
Couche
[Web]
Couche
[mtier]
Couche
[DAO]
Couche
[EF5]
Connecteur
[ADO.NET]
Sgbd
Spring.net
Le projet [pam-web-02] a besoin d'un certain nombre de DLL :
http://tahe.developpez.com
345/362
9.25.3
Gnrez le projet [pam-web-02]. Des erreurs vont apparatre telle que la suivante :
La classe [ApplicationModel] utilise le type [Employe]. Avec la couche [mtier] simule, ce type tait dfini dans l'espace de noms
[Pam.Metier.Entites]. Il est dsormais dans l'espace de noms [Pam.EF5.Entites]. Corrigez ces erreurs comme montr ci-dessus.
9.25.4
Comme il a t fait au paragraphe 9.24.5, page 341, il nous faut configurer EF5 dans le fichier [web.config] du projet :
Travail 1 : remplacez tout le contenu actuel de [web.config] par celui du fichier [app.config] du projet [pam-metier].
Le fichier [Global.asax] de notre application web utilise [Spring.net] pour rcuprer une rfrence sur la couche [mtier] :
http://tahe.developpez.com
346/362
1.
2.
3.
4.
try
{
// instanciation couche [metier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier")
as IPamMetier;
5.
}
6.
catch (Exception ex)
7.
{
8.
application.InitException = ex;
9. }
Ligne 4, on demande une rfrence sur l'objet Spring nomm [pammetier]. C'est bien ce nom qui a t donn la couche [mtier]
(vrifiez-le dans votre fichier [web.config]).
9.25.5
Nous sommes prts tester notre couche [web]. On excute le projet [pam-web-02] par [Ctrl-F5]. On obtient alors la page d'accueil
suivante :
En [1], on obtient les employs de la base de donnes [dbpam_ef5]. On remarquera qu'on n'a plus l'employ [X X] que nous avions
avec la couche [mtier] simule. Faisons une simulation :
http://tahe.developpez.com
347/362
En [2], on obtient bien le salaire rel et non plus un salaire fictif. Maintenant arrtons le SGBD MySQL5 et faisons une autre
simulation :
http://tahe.developpez.com
348/362
En [3], on a obtenu une page d'erreurs lisible mme si certains messages sont en anglais. Maintenant arrtons de nouveau MySQL et
rexcutons l'application dans VS par [Ctrl-F5] :
On obtient la vue [initFailed.cshtml] construite au paragraphe 9.20.4, page 313. Elle affiche les messages d'erreurs de la pile
d'exceptions. Le lecteur est invit faire d'autres tests.
http://tahe.developpez.com
349/362
10 Conclusion
Rappelons ce qui a t fait dans ce document :
les chapitres 1 8 nous ont prsent les fondamentaux du framework ASP.NET MVC ;
le chapitre 9 a t consacr une tude de cas, d'abord avec une architecture simplifie :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
4b
JS
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
couche
[mtier]
simule
2c
Modles
Cette architecture simplifie nous a permis de nous concentrer uniquement sur la couche [web] et a par ailleurs facilit les
tests. Puis nous avons utilis l'architecture plus complexe suivante :
Navigateur
Application web
couche [ASP.NET MVC]
HTML
JS
4b
Vue1
Vue2
Vuen
2b
2a
Front Controller
3
Contrleurs/
Actions
Modles
couches
[mtier, DAO, EF5]
Base de
donnes
2c
Nous avons pu voir que les couches [metier], [DAO] et [EF5] amenaient une vraie complexit l'ensemble de
l'application, ce qui a justifi, a posteriori, l'usage d'une architecture simplifie pour dvelopper la couche [web].
Le lecteur ayant fait cette tude de cas devrait avoir acquis une bonne matrise d'ASP.NET MVC et du concept APU, Application
Page Unique.
Il manque certainement une chose dans ce document, ce sont les tests unitaires. Ceux-ci auraient d tre faits diffrents
endroits :
http://tahe.developpez.com
350/362
rservs.
ad.univ-angers.fr
fe80::698b:455a:925:6b13%4
172.19.81.34
255.255.0.0
172.19.0.254
Mdia dconnect
L'adresse IP est ici indique ligne 14. Si vous avez une connexion wifi, l'adresse wifi du poste apparatra lignes 20 et suivantes.
vrifiez les proprits du projet [clic droit sur projet / proprits / onglet web] :
http://tahe.developpez.com
351/362
Le serveur a renvoy une rponse [400 Bad Request]. Le serveur IIS Express utilis par Visual Studio n'accepte que le nom
[localhost].
Pour rendre accessible l'application dveloppe, une URL du type [http://adresseIP/contexte/...], il faut utiliser un autre serveur
que IIS Express, par exemple un serveur IIS (pas Express). Pour vrifier la prsence de celui-ci, il faut aller dans le panneau de
configuration [Panneau de configuration\Systme et scurit\Outils dadministration] :
On dmarre le site web par dfaut. Ceci fait, demandez l'URL [http://localhost] avec un navigateur. Vrifiez auparavant qu'un autre
serveur web n'occupe pas dj le port 80. Si oui, arrtez le.
http://tahe.developpez.com
352/362
Le serveur IIS nous a rpondu. Maintenant remplacez [localhost] par l'adresse IP de votre poste :
http://tahe.developpez.com
353/362
Ceci fait, il faut changer la configuration du projet web qu'on veut dployer [clic droit sur projet / proprits / onglet web] :
Il faut choisir le serveur IIS local comme serveur de dploiement. Visual studio fixe l'URL de l'application. On peut la changer.
Excutez le projet par [Ctrl-F5] :
Si on ne dispose pas du serveur IIS, on pourra utiliser un serveur ASP.NET gratuit tel [Ultidev Web Server Pro] disponible l'URL
[http://ultidev.com/Download/ ].
http://tahe.developpez.com
354/362
Une fois install, il y a deux mthodes pour lancer une application web avec ce serveur :
la manire rapide
Prenez un explorateur windows et slectionnez le dossier de l'application ASP.NET dployer :
Le serveur web est alors lanc et l'application web affiche dans un navigateur :
la manire longue
Lancez le serveur web puis suivez les tapes suivantes :
http://tahe.developpez.com
355/362
http://tahe.developpez.com
356/362
Votre application web est dsormais disponible. Prenez un navigateur et demandez son URL l'adresse [localhost] :
Le serveur Ultidev s'est install sous la forme d'un service Windows qui se lance automatiquement. Cela peut tre gnant si vous
utilisez d'autres serveurs web qui utilisent galement le port 80. Vous pouvez inhiber le dmarrage automatique de la faon
suivante :
http://tahe.developpez.com
357/362
3
1
Pour lancer manuellement le serveur, utilisez par exemple l'application [Ultidev Web Explorer] :
http://tahe.developpez.com
358/362
http://tahe.developpez.com
359/362
http://tahe.developpez.com
360/362
http://tahe.developpez.com
361/362
9.16.1 LE PROBLME....................................................................................................................................................................305
9.16.2 CRITURE DE L'ACTION SERVEUR [VOIRSIMULATIONS].........................................................................................................305
9.17 TAPE 11 : TERMINER LA SESSION.......................................................................................................................................306
9.17.1 LE PROBLME....................................................................................................................................................................306
9.17.2 CRITURE DE L'ACTION SERVEUR [TERMINERSESSION].........................................................................................................307
9.17.3 MODIFICATION DE LA FONCTION JAVASCRIPT [TERMINERSESSION]........................................................................................307
9.18 TAPE 12 : EFFACER LA SIMULATION...................................................................................................................................307
9.18.1 LE PROBLME....................................................................................................................................................................307
9.18.2 CRITURE DE L'ACTION CLIENT [EFFACERSIMULATION].........................................................................................................308
9.19 TAPE 13 : RETIRER UNE SIMULATION.................................................................................................................................308
9.19.1 LE PROBLME....................................................................................................................................................................308
9.19.2 CRITURE DE L'ACTION CLIENT [RETIRERSIMULATION].........................................................................................................309
9.19.3 CRITURE DE L'ACTION SERVEUR [RETIRERSIMULATION]......................................................................................................309
9.20 TAPE 14 : AMLIORATION DE LA MTHODE D'INITIALISATION DE L'APPLICATION.............................................................309
9.20.1 AJOUT DES RFRENCES [SPRING] AU PROJET WEB...............................................................................................................311
9.20.2 CONFIGURATION DE [WEB.CONFIG]......................................................................................................................................312
9.20.3 MODIFICATION DE [APPLICATION_START]............................................................................................................................313
9.20.4 GRER UNE ERREUR D'INITIALISATION DE L'APPLICATION......................................................................................................313
9.21 O EN SOMMES NOUS ?....................................................................................................................................................316
9.22 TAPE 15 : MISE EN PLACE DE LA COUCHE ENTITY FRAMEWORK 5...................................................................................318
9.22.1 LA BASE DE DONNES.........................................................................................................................................................318
9.22.2 LE PROJET VISUAL STUDIO.................................................................................................................................................322
9.22.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................324
9.22.4 LES ENTITS ENTITY FRAMEWORK......................................................................................................................................325
9.22.5 CONFIGURATION DE L'ORM EF5........................................................................................................................................328
9.22.6 TEST DE LA COUCHE [EF5].................................................................................................................................................330
9.22.7 DLL DE LA COUCHE [EF5].................................................................................................................................................333
9.23 TAPE 16 : MISE EN PLACE DE LA COUCHE [DAO].............................................................................................................333
9.23.1 L'INTERFACE DE LA COUCHE [DAO]...................................................................................................................................333
9.23.2 LE PROJET VISUAL STUDIO.................................................................................................................................................334
9.23.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................334
9.23.4 IMPLMENTATION DE LA COUCHE [DAO]............................................................................................................................335
9.23.5 CONFIGURATION DE LA COUCHE [DAO]..............................................................................................................................337
9.23.6 TEST DE LA COUCHE [DAO]...............................................................................................................................................337
9.23.7 DLL DE LA COUCHE [DAO]...............................................................................................................................................339
9.24 TAPE 17 : MISE EN PLACE DE LA COUCHE [MTIER]..........................................................................................................339
9.24.1 L'INTERFACE DE LA COUCHE [MTIER].................................................................................................................................339
9.24.2 LE PROJET VISUAL STUDIO.................................................................................................................................................339
9.24.3 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................339
9.24.4 IMPLMENTATION DE LA COUCHE [MTIER]..........................................................................................................................340
9.24.5 CONFIGURATION DE LA COUCHE [MTIER]...........................................................................................................................341
9.24.6 TEST DE LA COUCHE [METIER]............................................................................................................................................342
9.24.7 DLL DE LA COUCHE [MTIER]............................................................................................................................................343
9.25 TAPE 18 : MISE EN PLACE DE LA COUCHE [WEB]...............................................................................................................343
9.25.1 LE PROJET VISUAL STUDIO.................................................................................................................................................344
9.25.2 AJOUT DES RFRENCES NCESSAIRES AU PROJET................................................................................................................345
9.25.3 IMPLMENTATION DE LA COUCHE [WEB]..............................................................................................................................346
9.25.4 CONFIGURATION DE LA COUCHE [WEB]................................................................................................................................346
9.25.5 TEST DE LA COUCHE [WEB].................................................................................................................................................347
10 CONCLUSION.................................................................................................................................................................... 350
11 RENDRE ACCESSIBLE SUR INTERNET UNE APPLICATION ASP.NET............................................................... 351
http://tahe.developpez.com
362/362