MVC4 RAZOR
Wilfart Emmanuel
Programmation WEB
3ième informatique
Introduction
La philosophie MVC
Introduction
Le moteur de vue RAZOR
Dans la partie ASP.NET (WebForms), nous avons abordé l'existence du style
imbriqué sous la forme suivante
<!DOCTYPE html>
<%@ Page Language="C#" %>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Ma premiere application en asp.net</title>
</head>
<body>
<h1>ceci est ma page ecrite en asp.net</h1>
<% Response.Write("ceci est du code imbriqué"); %>
<br>
La date d'aujoud'hui est: <% =System.DateTime.Now %>
</body>
</html>
Introduction
Le moteur de vue RAZOR
RAZOR apporte une autre syntaxe plus claire et plus simple à mettre en
oeuvre dans une page HTML
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>title>Ma premiere application en MVC Razor</title>
</head>
<body>
<div>
<h1>ceci est ma page ecrite en asp.net MVC Razor</h1>
@Html.Raw("ceci est du code imbriqué")<br/>
La date d'aujoud'hui est: @System.DateTime.Now
</div>
</body>
</html>
Syntaxe de Razor
Les instructions uniques
L'exemple précent La date d'aujoud'hui est: @System.DateTime.Now
correspond à une instruction unique. Elle est précédée du symbole @.
Chaque instruction doit être précédée de ce symbole.
Les blocs de code
Un bloc de code débute par les symboles @{ et se termine par le symbole }
Le code écrit dans ce bloc respectera la syntaxe du C# ou celle du VB.NET.
Dans le cas du C#, les intructions se termineront par un ;
Razor supporte la plupart des structures conditionnelles utilisables dans le C#
@{ var WelcomMsg = "bienvenue";
var weekDay = DateTime.Now.DayOfWeek;
var DisplayMsg = WelcomMsg + " Nous sommes le: " + weekDay; }
<h2>Message: @DisplayMsg</h2>
Syntaxe de Razor
Les instructions de boucle
Instruction foreach
@{
string[] Courses = {"Csharp", "Linux", "Advanced Network"};
}
<ul>
@foreach (var course in @Courses)
{
<li>@course</li>
}
</ul>
Syntaxe deRazor
Les instructions de boucle
Instruction for
@{
string[] Courses = {"Csharp", "Linux", "Advanced Network"};
}
<ul>
@for (int i = 0; i < @Courses.Length;i++ )
{
<li>@Courses[i]</li>
}
</ul>
Syntaxe de Razor
Les instructions de boucle
Instruction while
@{
string[] Courses = {"Csharp", "Linux", "Advanced Network"};
}
<ul>
@{
int i=0;
while(i < @Courses.Length)
{
<li>@Courses[i]</li>
i++;
}
}
</ul>
Syntaxe de Razor
Les instructions de contrôle
Instruction if (else)
@{
float[] Points = { 10, 8, 15, 13, 9 };
}
<ul>
@foreach (var point in @Points)
{
<li>@point/20
@if (@point<10)
{
<span>en echec</span>
}
</li>
}
</ul>
Syntaxe de Razor
Les instructions de contrôle
Instruction switch
@{ string[] Evals = { "A", "B", "C", "E", "A" }; }
<ul>
@foreach (var eval in @Evals) { <li>
@switch(@eval)
{
case "A":
<span>Excellent</span>
break;
case "D":
<span>Passable</span>
break;
case "E":
<span>Echec</span>
break;
} </li> } </ul>
Syntaxe de Razor
Le contenu statique
Dans un bloc de code, il est parfois nécessaire de placer du texte qui sera
reproduit tel quel sur le flux de sortie en HTML.
@{
Debut de ma page
var WelcomMsg = "bienvenue";
var weekDay = DateTime.Now.DayOfWeek;
var DisplayMsg = WelcomMsg + " Nous sommes le: " + weekDay;
}
Cette syntaxe produira un erreur. Pour éviter cette erreur, il suffit de
placer ce texte dans un conteneur
@{
<text>Debut de ma page</text>
}
Syntaxe de Razor
Le contenu statique
Une autre syntaxe consiste à utiliser les symboles @: en début de texte
@{
@:Debut de ma page
}
Les commentaires
Tout bloc de code peut contenir des commentaires qui seront placés
entre les symbôles suivants @* et *@
@{
@* commentaire *@
}
Accès à la vue
Mise en place d'un contrôleur
L'accès à la vue passe obligatoirement par un contrôleur qui recevra la
requête en provenance du navigateur
Accès à la vue
Mise en place d'un contrôleur
Le contrôleur ajouté devra posséder un nom, dans notre cas, Home,
postfixé du mot controller. Nous envisagerons en l'absence de modèle, le
choix d'un contrôleur MVC vide.
Accès à la vue
Mise en place d'un contrôleur
Une fois le contrôleur ajouté, nous pouvons éditer le fichier correspondant et
nous obtenons alors le contenu d'écran suivant
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
Dans le controleur, nous retrouvons une action associée Index() qui
correspondra à la vue portant le même nom. Il nous suffit de créer
maintenant la vue Index dans le dossier Home (nom du contrôleur)
Accès à la vue
Mise en place de la vue
Dans le dossier vue, il faudra ajouter le dossier Home dans lequel nous
placerons la vue Index
Le fichier Index.cshtml sera alors créé correspondant à la page HTML
Accès à la vue
Mise en place du routage
L'utilisateur n'accède pas directement à une page mais l'URL renseignée
pointe vers une ressource qui devra correspondre à un contrôleur et une
action. C'est le rôle du routage de faire correspondre la requête générée
par le client à un contrôleur/action et des paramètres optionnels dont nous
parlerons par la suite
Dans app_start, nous retrouvons le fichier RouteConfig.cs
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional }
);
http://localhost/Home/Index ou http://localhost/
Mise en place d'un modèle
Introduction
Dans la partie précédente propre à la vue, nous avons vu comment insérer du
code Razor C# dans une page HTML. La grand majorité des applications WEB
doivent cependant présenter des données extraites d'une source externe. Elles
doivent être modifiables et d'éventuelles nouvelles données doivent pouvoir
être ajoutées.
MVC offre la possibilité de transmettre des données du contrôleur vers une vue
pour le rendu tandis que les données seront transmises de la vue au contrôleur
lors de l'édition. Les données seront représentées sous forme d'un modèle
Nous allons à titre d'exemple mettre en place deux classes, l'une représentant
les caractéristiques d'un livre dans une librairie et l'autre la librairie.
Mise en place d'un modèle
Introduction
public class Livre {
public int ID { get; set; }
public string Titre { get; set; }
public string Editeur { get; set; }
public string Genre { get; set; } }
La librairie respectera le pattern CRUD (Create, Retreive, Update et Delete)
Mise en place d'un modèle
Introduction
public void InitLibrairie() {
this.Add(new Models.Livre {
ID = 1,
Titre = "Csharp Facile",
Genre = "Education",
Editeur = "Wilfart"});
public Livre Select(int id) {
Livre livre = null;
livre = this.Find(x => x.ID == id);
return livre; }
public List<Livre> Select()
{
return this;
}
Lien Contôleur-Modèle-Vue
Contrôleur Home - Action Index
public Models.Librairie MyLib;
public HomeController()
{
MyLib = new Models.Librairie();
}
public ActionResult Index()
{
return View(MyLib.Select());
}
L'action Index permet de retourner un ActionResult qui sera une énumération
des livres présents dans la librairie.
Lien Contôleur-Modèle-Vue
Contrôleur Home - Action Detail
public Models.Librairie MyLib;
public HomeController()
{
MyLib = new Models.Librairie();
}
public ActionResult Details(int id)
{
return View(MyLib.Select(id));
}
L'action Index permet de retourner un ActionResult qui sera une énumération du
livre présent dans la librairie et ayant l'id passé en argument.
Lien Contôleur-Modèle-Vue
Liaison Vue Index - Model (récupéré du controleur)
<table>
<tr>
<td>Titre</td>
<td>Détails</td>
</tr>
@foreach (var livre in Model)
{
<tr>
<td>@livre.Titre</td>
<td><a href="/Home/Details/@livre.ID.ToString()">detail</a></td>
</tr>
}
</table>
Le controleur retourne une collection de livres parcourue par l'instruction foreach.
Pour chaque item, on accède au titre et à l'ID pour le lien
Lien Contôleur-Modèle-Vue
Liaison Vue Index - Model (récupéré du controleur)
<tr>
<td>Csharp Facile</td>
<td><a href="/Home/Details/1">detail</a></td>
</tr>
<tr>
<td>Linux Facile</td>
<td><a href="/Home/Details/2">detail</a></td>
</tr>
<tr>
<td>Asp.Net Facile</td>
<td><a href="/Home/Details/3">detail</a></td>
</tr>
La configuration des ancrages respecte bien les informations de routage sous la
forme: url: "{controller}/{action}/{id}"
Lien Contôleur-Modèle-Vue
Liaison Vue Details - Model
<div>
<h3>Titre du livre</h3>
@Model.Titre
<h3>Genre du livre</h3>
@Model.Genre
<h3>Editeur</h3>
@Model.Editeur
</div>
Le controleur retourne un livre. Nous pouvons directement accéder au titre, au
genre et à l'éditeur en passant par l'object Model
Lien Contôleur-Modèle-Vue
Html-Helper et les liens
Comme nous pouvons le constater, la mise en place d'un lien est plus compliqué.
C'est une des raisons de la présence d'un générateur de code html qui sur base de
la syntaxe suivante, simplifiera le code.
<td><a href="/Home/Details"/@livre.ID>Details</a></td>
<td>@Html.ActionLink("Details","Details",new {id=livre.ID})</td>
Le générateur de code HTML offre encore plus de possibilités notamment dans
la création des formuliaires abordés ci après.
Les formulaires
Introduction
Les formulaires offrent la possibilité d'éditer des données en vue de les modifier
ainsi que celle d'ajouter une nouvelle donnée.
Nous retrouverons donc une vue spécifique à ce formulaire, les actions associées
dans le contrôleur mais aussi les méthodes associées dans le modèle pour
l'insertion ou l'ajout d'un nouvel enregistrement dans la source de données.
Nous allons donc créer la vue Edit avec l'action correspondante Edit comprenant
l'argument de l'enregistrement qui doit être éditer.
Les formulaires
Edition des données - Lien dans la page d'index
@foreach (var livre in Model)
{
<tr>
<td>@livre.Titre</td>
<td><a href="/Home/Details/@livre.ID">Details</a></td>
<td>@Html.ActionLink("Edit","Edit",new {id=livre.ID})</td>
</tr>
}
Nous retrouvons l'HTML Helper Action link reprenant l'action Edit avec
comme paramètre l'identifiant du livre. L'action Edit permettra de démarrer
la vue Edit avec comme model l'objet Livre associé à la sélection faite dans la
page d'index
Les formulaires
Edition des données - Appel de l'action
public ActionResult Edit(int? id)
{
if (id != null)
{
Models.Livre livre = MyLib.Select(id.Value);
return View(livre);
}
return View("Index",MyLib.Select());
}
Si aucun argument n'est renseigné, l'id aura la valeur null. Pour acepter cette
valeur, il faudra le renseigner au niveau de l'argument de la fonction int? id
Si l'id n'est pas null, nous retrournons vers la vue Edit le livre correspondant
Les formulaires
Edition des données - Liaison de la vue au modèle
<form method="post" action="/Home/Update">
<input id="Uid" name="ID" type="hidden" value="@Model.ID" />
<span class="label">Titre:</span><input id="Titre" name="Titre"
type="text" value="@Model.Titre" /><br />
<span class="label">Editeur:</span><input id="Editeur"
name="Editeur" type="text" value="@Model.Editeur" /><br />
<span class="label">Genre:</span><input id="Genre" name="Genre"
type="text" value="@Model.Genre" /><br />
<input type="submit" value="valider" />
</form>
<form method="post" action="/Home/Update"> correspond à la technique
classique HTML de gestion des formulaires. Le formulaire sera envoyé par la
méthode post vers l'url /Home/Update
value="@Model.Titre" Chaque zone d'édition aura un attribut name
correspondant au nom du champ de l'enregistrement tandis que la valeur
sera récupérée du modèle fourni par le contrôleur.
Les formulaires
Mise à jour des données - Modèle
public void UpdateLivre(Livre tmp)
{
Livre Mod = Select(tmp.ID);
Mod.Titre = tmp.Titre;
Mod.Genre = tmp.Genre;
Mod.Editeur = tmp.Editeur;
}
Cette méthode reçoit un livre en argument permettant d'effectuer une mise à
jour de la source de données du livre ayant l'identifiant correspondant
Les formulaires
Mise à jour des données - Action Update
public ActionResult Update(int? id)
{
if (id != null)
{
Models.Livre test = new Models.Livre();
UpdateModel<Models.Livre>(test);
MyLib.UpdateLivre(test);
return View("edit",test);
}
return View("Index", MyLib.Select());
}
L'utilisation de UpdateModel permet de mettre à jour un objet de type Livre
dont les valeurs sont récupérées du formulaire. L'action Update n'a pas de
vue correspondante. Nous devrons donc renseigner cette vue de la façon
suivante: return View("edit",test);
Les formulaires
Validation des données
Un formulaire n'est pas aussi simple. Il va nécessiter que certains champs soient
obligatoires, que certains champs soient contrôlés avec un double encodage tel
que la confirmation d'une adresse mail par exemple, que des champs soient
validés par des expressions régulières et finalement validés éventuellement côté
serveur. MVC Razor met en place des mécanismes grace au HTML helper.
Reprenons notre code et adaptons le pour utiliser ce mécanisme de génération
automatique d'HTML.
Les formulaires
Validation des données Attributs du côté Model
public class Livre
{
[HiddenInput]
public int ID { get; set; }
[Display (Name = "Titre")]
[Required (ErrorMessage="Le {0} est requis")]
[StringLength(100)]
public string Titre { get; set; }
[Display(Name = "Editeur")]
[StringLength(100)]
public string Editeur { get; set; }
[Display(Name = "Genre")]
[StringLength(100)]
public string Genre { get; set; }
}
Les formulaires
Liste des attributs disponibles
HiddenInput ex: [HiddenIput]
Permet d'indiquer que le champ HTML généré doit être caché
Display ex: [Display (Name = "Titre")]
Spécifie le nom affiché comme label pour un champ.
DataType ex: [DataType (DataType.MultilineText)]
Spécifie le format d'affichage pour le champ.
DisplayFormat ex:[DisplayFormat (DataFormatSring = "{0:C}")]
Spécifie le format d'affichage pour le champ.
Required ex: [Required]
Indique qu'un champ est obligatoire.
ReqularExpression ex: [RegularExpression ("^0[1-68][0-9]{8}$")]
Expression régulière permettant de valider la structure du texte du champ
Les formulaires
Liste des attributs disponibles
Range ex: Range(0, double.MaxValue)
Valide la valeur d'un champ dans une plage de valeurs données.
Compare ex: [Compare ("Email")]
Valide le contenu du champ avec le contenu d'un autre.
StringLength ex: [StringLength(100, MinimumLength = 5)]
Spécifie le nombre de caractères maximum et minimum accepté dans le champ
Remote ex: [Remote ("Valider","index",AdditionalFields = "ID")]
Cet appel se fait via une requête GET en Ajax, l'action appelée devant retourner
un booléen sous la forme JSON
Les attributs de validation peuvent se voir affecter un message d'erreur
personnalisé sous la forme suivante: [Required (ErrorMessage="Le {0} est
requis")]
Les formulaires
Attribut remote avec action (AJAX - JSON)
Un exemple simple est par exemple l'enregistrement d'un utilisateur avec le choix
laissé à l'utilisateur de prendre un identifiant unique.
Il faut pouvoir avertir l'utilisateur que l'identifiant est déjà utilisé ou pas. Pour ce
faire, la vérification doit se faire du côté serveur via un accès à la base de données.
Les routes
les paramètres dans les routes
Dans un des exemples précédents, nous avions les configurations suivantes.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional });
public ActionResult Details(int id)
{
Models.Livre livre = MyLib.Select(id);
return View(livre);
}
<a href="/Home/Details/2">Details</a>
Les routes
les paramètres dans les routes
Si nous prenons l'url du mappage et celle du lien hypertexte, nous retrouvons
une similitude
href="/Home/Details/2"
url: "{controller}/{action}/{id}"
Le controleur est bien associé à Home
L'action est bien associée à Détails
Le paramètre correspondra à la valeur 2
Dans le controleur HomeController, nous retrouvons bien l'action ( la méthode)
Details qui aura la structure suivante
public ActionResult Details(int id) {
Models.Livre livre = MyLib.Select(id);
return View(livre); }
Le paramètre dans l'URL est récupéré par l'argument de la méthode
Les routes
Paramètre optionnel
Une route peut être associée à plusieurs vues dont certaines ne nécessitent pas
d'argument et d'autres oui. La vue Index ne réclame pas d'argument tandis que
la vue Details réclame un argument. Ces deux vues peuvent être validées par la
même route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id =
UrlParameter.Optional });
Les deux URL suivantes seront validées par la même route:
http://localhost/Home/Index
et
http://localhost/Home/Details/2
Les routes
Paramètre optionnel
Prenons le cas de ces deux URLS correspondant à la même action:
http://localhost/Home/Details/2
et
http://localhost/Home/Details
Dans un cas, l'argument devra récupérer un entier tandis que dans le second
cas, l'argument aura la valeur nulle.
Soit nous définissons deux actions avec le même nom mais l'un sans argument
et l'autre avec un argument, soit nous définissons une même action acceptant
un argument null
Les routes
Paramètre optionnel
public ActionResult Details(int? id) {
if (id != null) {
Models.Livre livre = MyLib.Select(id.value);
return View(livre); }
return View("Index"); }
public ActionResult Details(int id) { ... }
et
public ActionResult Details() { ... }
Les routes
Valeur par défaut
On peut imaginer que, dans le cas de la vue Details de l'exercice précédent, si
aucun choix n'est fait, ce soit la fiche d'id 1 qui soit affichée.
routes.MapRoute(
name: "Details",
url: "Details/{id}",
defaults: new { controller = "Home", action = "Details", id=1 });
L'URL mappée par cette route pourrait être:
http://localhost/Details
Les routes
Paramètres multiples - Multiples segments
Un exemple de paramètres multiples pourrait être:
routes.MapRoute(
name: "Details",
url: "Details/{langue}/{id}",
defaults: new { controller = "Home", action = "Details", id=1 });
Si un paramètre par défaut doit être défini, il est préférable qu'il soit placé en
dernière position. Nous entendons par segment, le paramètre identifié par les
séparateurs '/'
Les routes
Paramètres multiples - Segment unique
Un exemple de paramètres multiples pourrait être:
routes.MapRoute(
name: "Details",
url: "Details/{langue}/{idA}-{idB}",
defaults: new { controller = "Home", action = "Details"});
Il faudra choisir un caractère de séparation permettant d'identifier dans un même
segment les différents paramètres. Nous avons choisi dans notre exemple le
caractère '-'
Les routes
Paramètres, les contraintes
Imaginons une page de recherche permettant de fixer comme critères la date et
le mois de l'édition. Nous imaginerons une URL suivante:
http://localhost/Recherche/1998/12/
Nous imposerons une contrainte sur les deux segments devant être numérique.
routes.MapRoute(
name: "Recherche",
url: "Recherche/{annee}/{mois}/",
defaults: new { controller = "Home", action = "Recherche" },
constraints: new
{
annee = @"\d{4}",
mois = @"\d{2}"
} );
Les routes
Routes vers une page WebForms
Cette possibiluté est offerte pour passer à MVC4 Razor en conservant
d'anciennes parties du site qui sont en Asp.Net WebForms
routes.MapPageRoute(
routeName: "Route Legacy",
routeUrl: "Commande",
physicalFile: "Commande.aspx",
default: new RouteValueDictionnary ()
{
{ "id", "1" }
},
checkPhysicalUrlAccess: true
);
Les actions
Le retour d'exécution d'une action
Jusqu'à présent, nous avons utilisé la syntaxe suivante pour retourner une vue
dans la déclaration de la méthode d'action... return View(...);
Nous retrouvons également les retours d'objects suivants nécessitant donc une
instanciation avec l'opérateur new. Toutes les classes dérivent de la classe
Actionesult
EmptyResult
HttpStatusCodeResult - HttpNotFoundResult - HttpUnauthorizedResult
RedirectResult
RedirectToRouteResult
JavascriptResult
JsonResult
FileResult
ViewResultBase - ViewResult - PartialViewResult
Les actions
EmptyResult
Le code HTTP de retour correspond bien à 200 mais la page renvoyée est vide
public ActionResult Recherche(int annee, int mois)
{
return new EmptyResult();
}
HttpStatusCodeResult
Ce retour correspond en général à un code d'erreur HTTP (ressource on trouvée,
accès non authorisé ). Exemple d'accès à un livre n'existant pas
Models.Livre livre = MyLib.Select(id.Value);
if (livre == null) return new HttpStatusCodeResult(HttpStatusCode.NotFound);
else return View(livre);
Les actions
RedirectResult
Le code HTTP de retour correspondra à une redirection vers une autre page
envoyée vers le client (code 301 pour une redirection permanente ou 302 pour
une redirection temporaire)
return new RedirectResult("http://www.google.be");
RedirectToRouteResult
Le code HTTP de retour correspondra à une redirection vers une autre route.
L'URL associée sera envoyée vers le client (code 301 pour une redirection
permanente ou 302 pour une redirection temporaire)
return new RedirectToRouteResult("Details", new RouteValueDictionary()
{
{"id",1}
});
Les actions
FileResult
Cette classe est abstraite dont dérive les trois classes suivantes:
FileContentResult (tableau de bytes), FilePathResult (chemin physique) et
FileStreamResult ( flux).
Ces classes peuvent être utilisées au travers de la méthode File du contrôleur
public ActionResult GetImage(Guid id)
{
bibliothequeEntities db = new bibliothequeEntities();
var livre = db.livres.Find(id);
return new FileContentResult(livre.image, "image/jpeg");
}
L'image est récupérée sous forme d'un tableau de byte de la db et retournée
avec un type mime "image/jpeg". La dernière ligne de code peut être
remplacée par return File(livre.image, "image/jpeg");
Les actions
FileResult
Cette classe est abstraite dont dérive les trois classes suivantes:
FileContentResult (tableau de bytes), FilePathResult (chemin physique) et
FileStreamResult ( flux).
Ces classes peuvent être utilisées au travers de la méthode File du contrôleur
public ActionResult GetImage(Guid id)
{
bibliothequeEntities db = new bibliothequeEntities();
var livre = db.livres.Find(id);
return new FileContentResult(livre.image, "image/jpeg");
}
L'image est récupérée sous forme d'un tableau de byte de la db et retournée
avec un type mime "image/jpeg". La dernière ligne de code peut être
remplacée par return File(livre.image, "image/jpeg");
Les actions
Stockage d'une image dans une db
Pour dupliquer le test précédent, il est nécessaire d'uploader une image dans la
db sous forme d'un tableau de byte. Nous retrouverons le formulaire
@using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
<fieldset>
<legend>Upload a file</legend>
<div class="editor-field">
@Html.TextBox("file", "", new { type = "file" })
</div>
<div class="editor-field">
<input type="submit" value="Upload" />
</div>
</fieldset>
}
Les actions
JsonResult
Des données au format Json peuvent être envoyées vers un action via Ajax.
L'action pourra retrourner un résultat dans le même format vers une fonction
javascript.
Reprenons notre gestion de bibliotheque avec la gestion d'un caddy . Il est clair
que l'envoi d'un livre dans le caddy stocké coté serveur ne se fera pas en postant
à chaque fois l'entierté du formulaire. Nous utiliserons un envoi asynchrone
<td><input type="button" value="ajouter"
onclick="@(Html.Raw(String.Format("AddToCart({0})",
livre.ID)))"/></td>
Dans le code HMTL généré, nous obtiendrons
<td>Csharp Facile</td>
<td><a href="/Home/Details/1">Details</a></td>
<td><a href="/Home/Edit/1">Edit</a></td>
<td><input type="button" value="ajouter"
onclick="AddToCart(1)"/></td>
Les actions
JsonResult
La fonction javascript AddToCart permettra l'envoi de l'article commandé vers
une action d'un contrôleur de façon asynchrone
function AddToCart(id) {
var dataString = { itemid: id };
$.ajax({
type: "POST",
url: "home/AddToCart",
data: dataString,
cache: false,
dataType: "json",
success: UpdateCart });
return false; }
function UpdateCart(data) {
alert(data.count); }
url: "home/AddToCart" correspond à l'action AddToCart du contrôleur home
dataType: "json" envoi des données au format json
Les actions
JsonResult
L 'action récupère les données en ayant soin de choisir un paramètre possédant
le même nom que la variable dans le format Json
public ActionResult AddToCart(string itemid)
{
return Json(new { count = 1 });
}
La fonction javascript appelée en cas de succès récupére alors la donnée
transmise
function UpdateCart(data)
{
alert(data.count);
}
Les actions
ViewResultBase - ViewResult - PartialViewResult
L 'instanciation des deux classes ViewResult et PartialViewResult est simplifiée
grâce aux méthodes View et PartialView présentes dans la classe controller .
Avant d'aborder les actions retournant des vues partielles, nous aborderons les
vues partielles elles-mêmes.
Les vues partielles sont l'équivalent des UserControl en Asp.Net WebForms
Reprenons notre vue Index.cshtml et adaptons la pour utiliser les vues partielles
@foreach (var livre in Model) {
<table style="border:1px solid black;width:200px;margin:5px;">
<tr>
<td rowspan="4"><img style="width:50px"
src="~/images/castagnettes.png" /></td>
</tr>
<tr> <td>Titre:@livre.Titre</td> </tr>
<tr> <td>Genre:@livre.Genre </td> </tr>
<tr> <td>Editeur:@livre.Editeur</td> </tr>
</table> }
Les actions
ViewResultBase - ViewResult - PartialViewResult
Sur base du code précédent, nous créons notre vue partielle que nous
appellerons _livre.cshtml
@model MvcApplication3.Models.Livre
<table style="border:1px solid black;width:200px;margin:5px;">
<tr>
<td rowspan="4"><img style="width:50px"
src="~/images/castagnettes.png" /></td>
</tr>
<tr><td>Titre:@Model.Titre</td> </tr>
<tr><td>Genre:@Model.Genre </td></tr>
<tr> <td>Editeur:@Model.Editeur</td> </tr>
</table>
Les actions
ViewResultBase - ViewResult - PartialViewResult
Dans la vue index.cshtml, nous pouvons maintenant intégrer la vue partielle de
la façon suivante
@foreach (MvcApplication3.Models.Livre livre in Model)
{
@Html.Partial("_livrepartial", livre);
}
Dans ce cas de figure, la vue partielle est intégrée dans la vue principale. Nous
pouvons placer cette vue dans une variable et la transmettre de façon
asynchrone au travers d'un mécanisme Ajax
La méthode @Html.Partial transmet le contenu de la vue sur le flot de réponse
La méthode @Html.RenderPartial permet de placer le contenu dans une
variable
Les actions
ViewResultBase - ViewResult - PartialViewResult
Imaginons un bouton permettant l'envoi d'une requête Ajax vers une action
retournant une vue partielle. Le contenu html correspondant sera alors placé
dans une bloc DIV en utilisant le Jquery
<script type="text/javascript">
$(function() {
$('#ViewPartialBt').click( function() {
var datastring = { "itemid": 1 };
$.ajax({
url: '/Home/GetLib',
data: datastring,
dataType: 'html',
success: function(data) {
$('#partial').html(data); },
}); }); });
</script>
Les actions
ViewResultBase - ViewResult - PartialViewResult
L'action GetLib définie dans le contrôleur Home retournera la vue partielle.
public ActionResult GetLib(int ? itemid)
{
Models.Livre livre = MyLib.Select(itemid.Value);
return PartialView("_livrepartial", livre);
}
La fonction Javascript sera appelée à la réception de la donnée html
success: function(data) {
$('#partial').html(data); },
La vue
Dossier Shared - Layout
Dans l'explorateur de solution, nous retrouvons un dossier Shared qui contiendra
les éléments partagés entre les différentes vues tels que une vue partielle ou un
layout.
Chaque vue peut comprendre un lien vers un fichier de mise en page. Ce fichier
étant généralement global, il pourra être placé dans le fichier ViewStart.cshtml,
fichier analysé par l'ensemble des vues de l'application. Voici le contenu type de
ce fichier
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
Il est également possible d'ajouter d'autres variables dans ce fichier, qui seront
alors effectives dans les autres vues. Tout Layout défini dans une vue
surchargera celui renseigné dans le fichier _ViewStart.cshtml. Dans la syntaxe
suivante, aucun Layout ne sera utilisé. @{ Layout=null;}
La vue
Dossier Shared - Layout
<html> <head>
<meta charset="utf-8" />
<title>@Page.Title Mon application MVC</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-2.1.1.min.js")"></script>
<script src="@Url.Content("~/Scripts/modernizr-2.7.2.js")"></script>
</head> <body>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
</ul>
<section id="main">
@RenderBody()
<p>Copyright HELHA 2014. All Rights Reserved.</p>
</section> </body></html>
La vue
Dossier Shared - Layout
@RenderBody() correspond au rendu de la vue elle même. C'est à l'emplacement
de cette instruction que le contenu de la vue se trouve insérée. Dans notre
exemple, le Lauout comprend le menu et le pied de page avec le copyright. Voici
un exemple de contenue de vue
@{
Page.Title = "Home Page";
}
<div>
@foreach (MvcApplication3.Models.Livre livre in Model)
{
@Html.Partial("_livrepartial", livre);
}
</div>
Page.Title = "Home Page"; permet de définir une variable qui sera utilisée pour
paersonnaliser le Layout <title>@Page.Title Mon application MVC</title>
La vue
ViewBag - ViewData
ViewBag et ViewData correspondent au même dictionnaire, accessible depuis le
contrôleur ou la vue et donc utile pour partager quelques données utiles
ViewBag représente la partie dynamique de ViewData.
Exemple: ViewBag.Title="Mon applicaion MVC" ou ViewData["Title"]="Mon
application MVC". Dans le contrôleur, nous auron par exemple
public ActionResult Index()
{
ViewBag.Title = "Mon application MVC";
return View(MyLib.Select());
}
Dans la partie Layout (également possible dans le partie vue)
<title>@Page.Title @ViewBag.Title</title>
La vue
@Styles.Render et @Scripts.render
Ces instructions permettent d'inclure automatiquement un bundle dans une page.
Les bundles permettent de contourner la limite des navigateurs dans le nombre de
téléchargements parallèles (8 à 10). Des scripts et feuilles de style peuvent être
concaténés et fournis en une seule requête. Lors de la création d'une application
web de modèle "Application Internet", l assemblage nécessaire aux Bundles est
référencé: System.Web.optimization.dll
Dans le dossier App_start, nous retrouvons le fichier BundleConfig.cs dont voici un
exemple de contenu (limité)
public static void RegisterBundles(BundleCollection bundles) {
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*")); /*....*/ }
La vue
@Styles.Render et @Scripts.render
Dans le fichier Global.asax, la méthode Application_start comprendra notamment
le code suivant
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
La vue
@Styles.Render et @Scripts.render
Il suffira maintenant d'utiliser ces bundles au travers des deux syntaxes. Reprenons
le Layout généré automatiquement lors de la création d'un projet
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<title>@ViewBag.Title - Mon application ASP.NET MVC</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
La vue
@RenderSection
RenderSection donne la possibilité de placer un contenu spécifique à une vue. On
peut imaginer inclure du code Javascript additionnelle uniquement utile dabs la
vue associée. Un paramètre Required permettra de préciser si la vue doit
obligatoirement définir cette section
@section MesScripts { <script type="text/javascript">
$(function () {
$('#ViewPartialBt').click(function () {
var datastring = { "itemid": 1 };
$.ajax({
url: '/Home/GetLib',
data: datastring,
dataType: 'html',
success: function (data) {
$('#partial').html(data);
},
}); });}); </script> }
La vue
@RenderSection
<section id="main">
@RenderBody()
@RenderSection("MesScripts",false)
<p>Copyright HELHA 2014. All Rights Reserved.</p>
</section>
@RenderSection("MesScripts",false) false indique que la section "MesScripts"
n'est pas obligatoire dans la vue. De ce fait, ce Layout autorise les vues qui ne
comprenent pas cette section.
Le contrôleur
Les filtres d'action
Un filtre d'action est un attribut que vous pouvez appliquer à une action d'un
contrôleur - ou un contrôleur entier - qui modifie la façon dont l'action est
exécutée. ASP.NET MVC comprend de façon native plusieurs filtres d'action tels
que ResultFilter, ExceptionFilter, AuthorizationFilter, AuthenticationFilter. A ces
filtres seront associés des attributs tels que par exemple
OutputCache. Gestion de la cache de sortie pour Resultfilter
HandleError. Cette attribut permet de spécifier que dans le cas d'une erreur non
capturée dans une action doit provoquer l'affichage de la page Error pour
Exceptionfilter
Authorize. Ce filtre permet de restreindre les accès aux utilisateurs authentifiés
(tous, certains, ou ayant un rôle determiné) pour Authorizationfilter
AllowAnonymous. Ce filtre permet d'autoriser les accès aux utilisateurs non
authentifiés pour AuthenticationFilter
Nous retrouvons également dans les filtres d'action, les
ActionMethodSelectorAttribute tels que HttpGet et HttpPost définisant le "verb"
autorisé pour l'accès à l'action concernée.
Le contrôleur
Les filtres d'action - OutputCache
public class HomeController : Controller {
[OutputCache(Duration=10, VaryByParam="none")]
public ActionResult Index()
{
return View();
}}
Pour des questions de centralisation et donc de maintenance, nous pouvosn
également créer un profil de cache dans le fichier web.config
<caching> <outputCacheSettings> <outputCacheProfiles> <add
name="Cache1Hour" duration="3600" varyByParam="none"/>
</outputCacheProfiles> </outputCacheSettings> </caching>
[OutputCache(CacheProfile="Cache1Hour")] public string Index() { return
DateTime.Now.ToString("T"); }
Le contrôleur
Les filtres d'action - OutputCache
Il y a d'autres options disponibles pouvant faire varier la sortie en cache. Nous
citerons notamment:
VaryByContentEncoding
VaryByCustom
VaryByHeader
Location
Nous pouvons également utiliser un SqlDependency pour invalider la cache
lorsque une donnée change au niveau de la base de données
Le contrôleur
Les filtres d'action - Authorize - AllowAnonymous
L'accès à un site peut être public mais peut aussi comprendre certaines actions qui
ne pourront être accessible que si un utilisateur est authentifié
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login() { // ... }
public ActionResult Index() { return View();}
}
Si une action est protégée par un attribut [Authorize], une redirection devra être
prévue vers une page de login pour permettre une authentification
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880"
defaultUrl="~/Home/Index"></forms></authentication>
Le contrôleur
Les filtres d'action - Authorize - AllowAnonymous
L'action Login a été placée dans un autre controleur qui autorise par défaut les
accès anonymes. Une des actions login (reprise ci après) correspond au
chargement de la vue comprenant les demandes d'identifiant.
public ActionResult Login(string ReturnUrl)
{
ViewBag.ReturnUrl = ReturnUrl;
return View();
}
L'action Login de la dia suivante est celle correspondant à la récupération des
informations d'authentification et leur validation. Pour des question de sécurité,
cette action est limitée à une requête de type post
Le contrôleur
Les filtres d'action - Authorize - AllowAnonymous
[HttpPost]
public ActionResult Login(User tempUser, string ReturnUrl) {
WPFTutorialEntities1 WPFUsers = new WPFTutorialEntities1();
if (ModelState.IsValid) {
int result = WPFUsers.ValidateUser(tempUser.UserName,
tempUser.Passwd).FirstOrDefault() ?? 0;
if (result==1) {
Session["User"] = tempUser;
FormsAuthentication.SetAuthCookie(tempUser.UserName, false);
if (ReturnUrl != null) return Redirect(ReturnUrl);
else return Redirect(FormsAuthentication.DefaultUrl); }
else {
ModelState.AddModelError("", "Log In Failed"); } }
else {
ModelState.AddModelError("", "Log In Failed"); }
return View(); }
Le contrôleur
Les filtres d'action - HandleError
Nous allons générer une erreur dans une des actions présente dans le contrôleur
"Home". Voici le code à la base de l'erreur
public object TestError() {
throw new Exception("Une erreur est générée"); }
L'accès à cette action porvoquera l'affichage suivant
Le contrôleur
Les filtres d'action - HandleError
Il faudra modifier le contenu du fichier web.config pour y insérer la configuration
suivante
<system.web>
<customErrors mode="On"/>
</system.web>
Par défaut, le fichier d'erreur Error.aspx sera utilisé dans le même dossier associé
au contrôleur dont l'action a provoqué l'erreur. Si ce fichier est absent, c'est dans
le dossier Shared que la recherche se fera.
[HandleError (View="Error")]
public object TestError()
{
throw new Exception("Une erreur est générée");
}
Le contrôleur
Les filtres d'action - HandleError
Voici le contenu du fichier Error.aspx
<html>
<head><title>Page d'erreur</title></head>
<body>
<h2>Une erreur s'est produite lors de l'accès à la page</h2>
</body>
</html>
Le contrôleur
Les filtres d'action personnalisé
Nous pouvons créer nos propres filtres tels que par exemple d'enregistrer un
événement dans une base de données lorsque un utilisateur accède à une action.
Une autre idée de filtre serait de limiter l'accès à une action donnée en fonction de
l'adresse IP source du client (Cette dernière proposition sera proposée en exercice)
public class LogFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
WPFTutorialEntities1 WPFLogs = new WPFTutorialEntities1();
WPFLogs.InsertLog(
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName,
filterContext.HttpContext.Request.UserHostAddress,
filterContext.HttpContext.Timestamp);
}}
Le contrôleur
Les filtres d'action personnalisé
[LogFilter]
[Authorize]
public ActionResult Index()
{
ViewBag.Title = "Mon application MVC";
return View(MyLib.Select());
}
Envoi de données
Formulaire de login (post)
Nous allons envisager l'envoi des données en prenant un formulaire de login
public ActionResult Login(string ReturnUrl) {
ViewBag.ReturnUrl = ReturnUrl;
return View(); }
Envoi de données
Formulaire de login (post)
Nous allons reprendre quelques aspects du fichier Login.cshtml
@using (Html.BeginForm(new { ReturnUrl = Request.QueryString["ReturnUrl"] }))
{
@Html.AntiForgeryToken()
(...)
}
Html.BeginForm permet de générer le conteneur <form> html. Par défaut, c'est la
même action d'origine qui recevra les données, auxquelles sera ajoutée la donnée
ReturnUrl = Request.QueryString["ReturnUrl"]
Request.QueryString["ReturnUrl"] permet de récupérer le champ passé en url
quee nous retrouvons dans la barre URL du navigateur:
http://localhost:61775/Account/Login?ReturnUrl=%2f
Envoi de données
Formulaire de login (post)
@model MvcApplication3.User
@using (Html.BeginForm(new { ReturnUrl = Request.QueryString["ReturnUrl"] }))
{
(...)
@Html.Label("Nom",new { @class = "Label" })
@Html.TextBoxFor(m=>m.UserName, new { placeholder =
"Nom",@class="tb" })
@Html.ValidationMessageFor(m => m.UserName,"obligatoire") (...)
}
@model MvcApplication3.User Le modèle de données associé à la vue est
MvcApplication3.User, une classe comprenant des propriétés. Cette classe est dans
notre cas, générée automatiquement par le designer de modèle entity framework
pour l'accès à notre base de données
Envoie de données
Formulaire de login (post)
public partial class User {
public System.Guid UserID { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Passwd { get; set; } }
Chaque propriété peut être associée dans le formulaire à une zone d'édition nous
permettant ainsi de pouvoir récupérer le nom et le mot de passe
@Html.TextBoxFor(m=>m.UserName, new { placeholder = "Nom" })
Envoi de données
Formulaire de login (post)
public partial class User {
public System.Guid UserID { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Passwd { get; set; } }
Nous retrouvons l'attrubut [Required] qui a été ajouté par nos soins pour signaler
un critère de validation qui dans notre cas est la présence obligatoire de ce champ
@Html.ValidationMessageFor(m => m.UserName,"obligatoire")
Envoi de données
Formulaire de login (post)
Le bouton Submit permettra l'envoie du formulaire si les validations côté client
sont valides. Un message d'erreur sera affiché si le formulaire est réaffiché mais
avec une authentification qui a échoué
@{Html.EnableClientValidation(true);}
<input type="submit" value="Validation" />
(...)
@if (!ViewData.ModelState.IsValid) {
<p> id="message">Authentification erronée</p> }
Une autre façon d'afficcher un message d'erreur est de travailler avec
@Html.ValidationSummary ainsi que dans l'action du contrôleur
ModelState.AddModelError("", "Authentification erronée");
Envoi de données
Formulaire de login (post)
[HttpPost]
public ActionResult Login(User tempUser, string ReturnUrl)
{
(...)
else
{
ModelState.AddModelError("", "Authentification erronée");
}
}
<input id="saveButton" type="submit" value="Validation" />
</div>
@Html.ValidationSummary(false)
</div>
</div>
Envoi de données
Formulaire de login (post)
Une fois les champs remplis, le formulaire peut donc être envoyé vers l'action
Login. Nous recevrons donc deux arguments que sont l'utilisateur avec son nom et
mot de passe ainsi que le ReturnUrl.
[HttpPost]
public ActionResult Login(User tempUser, string ReturnUrl) {
WPFTutorialEntities1 WPFUsers = new WPFTutorialEntities1();
if (ModelState.IsValid) {
int result = WPFUsers.ValidateUser(tempUser.UserName,
tempUser.Passwd).FirstOrDefault() ?? 0;
if (result==1) {
Session["User"] = tempUser;
FormsAuthentication.SetAuthCookie(tempUser.UserName, false);
if (ReturnUrl != null) return Redirect(ReturnUrl);
else return Redirect(FormsAuthentication.DefaultUrl); }
else ModelState.AddModelError("", "Erreur d'authentification"); }
else ModelState.AddModelError("", "Erreur d'authentification");
return View(); }
Envoi de données
Formulaire de login (post)
ModelState.IsValid permet de vérifier que le contenu du formulaire est valide
int result = WPFUsers.ValidateUser(tempUser.UserName,
tempUser.Passwd).FirstOrDefault() ?? 0; est un appel à une procédure stockée
correspondant à une fonction importée dans entity framework. Elle vérifie qu'il y a
bien dans la db un utilisateur avec le nom et le mot de passe fournis
if (ReturnUrl != null) return Redirect(ReturnUrl);
else return Redirect(FormsAuthentication.DefaultUrl); } permet de
rediriger l'utilisateur vers la page d'origine qu'il tentait d'accéder ou la page par
défaut définie dans le fichier web.config si la page accédée était immédiatement la
page de login
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880"
defaultUrl="~/Home/Index"></forms>
</authentication>
Envoi de données
Formulaire de login (ajax)
Lorsque le formulaire est envoyé avec une authentification erronée, le serveur doit
renvoyer l'entierté de la page avec comme seule différence, l'information d'erreur
affichée. Nous pourrions, pour une question de "beauté", envoyer les informations
par Ajax vers une action.
Attention: il est indispensable d'inclure deux ensembles de script Jquery
<script src="~/Scripts/jquery.unobtrusive-ajax.js"
type="text/javascript"></script>
<script src="~/Scripts/jquery.form.js" type="text/javascript"></script>
jquery.unobtrusive-ajax.js permet d'obtenir toutes les fonctionnalités de
@Ajax.BeginForm
jquery.form.js permet de gérer notre formulaire et notamment d'en effacer le
contenu
Envoi de données
Formulaire de login (ajax)
@using (Ajax.BeginForm("Login2", "Account", new { ReturnUrl =
Request.QueryString["ReturnUrl"] }, new AjaxOptions
{
OnSuccess = "OnSuccess"
}))
{
@using (Ajax.BeginForm("Login2", "Account" permet de renseigner que le
formulaire doit être envoyé l'action Login2 faisant partie du conteneur Account
new AjaxOptions { OnSuccess = "OnSuccess"} permet de renseigner qu'en cas de
succes de l'appel de l'action, la méthode Javascript OnSuccess doit être appelée
Envoi de données
Formulaire de login (ajax)
<div><p style="display:none" id="message">Authentification erronée</p></div>
<script type="text/javascript">
function OnSuccess(response) {
if (response.success == true) {
$(location).attr('href', response.ReturnUrl); //redirection
} else {
$('form').clearForm();
$('#message').css('display', 'block'); }} // affichage message d'erreur
</script>
public JsonResult Login2(User tempUser, string ReturnUrl){
(...)
if (ReturnUrl != null) return Json(new { success = true, @ReturnUrl = ReturnUrl });
else return Json(new { success = true,
@ReturnUrl=FormsAuthentication.DefaultUrl}); (...)
return Json(new { success = false, @ReturnUrl = "" });}
Envoi de données
Formulaire - Validation server (ajax)
La validation de certains champs nécessite parfois l'accès à une base de données et
de ce fait, celle-ci doit donc se réaliser côté serveur.
On peut valider les informations lors de l'envoi du formulaire mais dans certains
cas, l'envoi par Ajax de la validation est plus agréable pour l'utilisateur.
Pour la bonne fonctionnalité au niveau de la page, il faut include le bundle Jquery-
val dans votre projet. Nous modifions notre modèle de sorte à ajouter la validation
public partial class User {
public System.Guid UserID { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Passwd { get; set; }
[Remote ("ValidateEmail","Home")]
public string Email { get; set; } }
Envoi de données
Formulaire - Validation server (ajax)
Nous allons envisager un formulaire d'édition de l'utilisateur en validant une
nouvelle adresse mail pour qu'elle soit unique dans la base de données
public JsonResult ValidateEmail(string Email)
{
using (var context = new WPFTutorialEntities1())
{
Guid UserGuid = Guid.Parse(Session["User"].ToString());
var UserQuery = from st in context.Users
where st.Email == Email && st.UserID != UserGuid
select st;
if (UserQuery.Count() != 0)
return Json("Adresse déjà utilisée", JsonRequestBehavior.AllowGet);
}
return Json(true, JsonRequestBehavior.AllowGet);
}
Envoi de données
Formulaire - Validation server (ajax)
Comme nous avons besoin du UserID dans plusieures actions, celui ci sera placé
dans une variable de session lors d'un login réussi
[HttpPost]
public ActionResult Login(User tempUser, string ReturnUrl) {
WPFTutorialEntities1 WPFUsers = new WPFTutorialEntities1();
if (ModelState.IsValid)
{
Guid ? result = WPFUsers.ValidateUser(tempUser.UserName,
tempUser.Passwd).FirstOrDefault() ?? null;
if (result != null)
{
Session["User"] = result.ToString(); (.....)
Envoi de données
Formulaire - Validation server (ajax)
Si la validation est activée lorsque la TextBox perd le focus, nous obtiendrons le
résultat suivant.
Envoi de données
Mise à jour dynamique
Dans le cadre des envois de requêtes ajax, nous pouvons en fonction
d'événements sur une page, mettre à jour les données dans la même page.
Soit une combobox comprenant la liste des départements. Lorsque un
département est sélectionné, la combobox des utilisateurs doit être mise à jour.
Envoi de données
Mise à jour dynamique
public ActionResult ComboTest() {
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
var items = WPF.Departement.ToList();
ViewBag.DepData = items;
return View();}
<div>
@Html.DropDownList("DepList", new SelectList(ViewBag.DepData,
"DepUID", "DepNom"))
<select id="UserList" name="UserList" style="width:150px;"></select>
</div>
Envoi de données
Mise à jour dynamique
<script>
$(document).ready(function () {
$('#DepList').change(function (e) {
$.ajax({
type: "POST",
dataType: "json",
url: '@Url.Action("GetUserFromDep","Home")',
data: {
"DepID": $('#DepList').val()},
success: function (data){
$('#UserList').empty();
$.each(data, function (i, el)
{ $('#UserList').append(new Option(el.UserName, el.UserID)); });}
});
});
$('#DepList').trigger('change');});
</script>
Envoi de données
Mise à jour dynamique
public JsonResult GetUserFromDep(string DepID)
{
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
var UserQuery = from st in WPF.Users
where st.DepUID.ToString()==DepID
select st;
return Json(UserQuery.ToList<User>());
}
MVC Web API
Introduction
ASP.NET Web API est un framework permettant au travers d'un service HTTP
l'exposition de données accessibles par une large gamme de clients. De base, les
types de données supportées sont XML, JSON et les données de formulaires.
Tout service Web API supporte les types de requêtes suivants (verbs):
• GET pour la lecture de données
• POST pour l'ajout d'une nouvelle donnée
• PUT pour la mise à jour d'une donnée
• DELETE pour l'effacement d'une donnée
D'autres verbes supportés mais moins fréquemment utilisés sont:
• HEAD comportement identique à GET mais uniquement pour récupérer les
entêtes
• OPTIONS permettant de demander par exemple la liste des verbes supportés
MVC Web API
Mise en place du service
Les WebAPI faisant partie de la technologie MVC, nous retrouverons une
similitude dans leur mise en place comparativement aux applications WEB MVC
Nous pouvons ajouter un service à une application MVC existance ou créer une
application MVC intégrant un service WebAPI
Dans le cas d'une nouvelle application, il suffira de choisir l'option Ajout d'un
nouveau projet - Application WEB ASP.NET MVC4
MVC Web API
Mise en place du service
Dans le cas d'une application existante, il suffira de choisir l'option Ajout d'un
nouveau controleur
MVC Web API
Routage - Controleur
Nous retrouvons un fichier de configuration des routes WebApiConfig.cs dont le
contenu de base sera le suivant
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}}
Si le controleur créé s'appelle ValuesController, l'accès au WebAPI se fera au
travers de l'URL suivante: http://xxxx/api/values
MVC Web API
Routage - Controleur
Nous retrouvons un fichier de configuration des routes WebApiConfig.cs dont le
contenu de base sera le suivant
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}}
Si le controleur créé s'appelle ValuesController, l'accès au WebAPI se fera au
travers de l'URL suivante: http://xxxx/api/values
MVC Web API
Routage - Controleur
Si nous choisissons la création du contrôleur non vide, certaines methodes seront
intégrées avec un exemple de code. Prenons comme exemple la méthode GET
associée au verb possédant le même nom
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Les données seront retournées au format JSON
MVC Web API
Association des méthodes aux verbes
Décoration des méthodes d'un attribut HttpGet, HttpPut, HttpPost, HttpDelete
[HttpGet]
public IEnumerable<string> Liste()
{
return new string[] { "value1", "value2" };
}
Décoration de l'attribut AcceptVerbs regroupant plusieurs verbes
[AcceptVerbs("GET", "HEAD")]
public IEnumerable<string> Liste()
{
return new string[] { "value1", "value2" };
}
MVC Web API
Association des méthodes aux verbes
Nommer les méthodes du contrôleur
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public IEnumerable<string> GetStudents()
{
return new string[] { "value1", "value2" };
}
MVC Web API
Retour des actions
Une action dans un contrôleur peut se voir affecter les types de retour suivant:
• void Web API retourne une réponse HTTP vide avec le code de status 204 (pas
de contenu).
• HttpResponseMessage Web API convertit la valeur retournée directement dans
le message HTTP en utilisant les propriétés de l'objet HttpResponseMessage pour
remplir la réponse. Cet objet est obtenu en utilisant la syntaxe suivante:
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK,
"value");
Si un modèle est utilisé dans la méthode CreateResponse, un media formatter
sera utilisé pour sérialiser ce modèle dans le corps de la réponse
• IHttpActionResult IHttpActionResult contient une méthode ExecuteAsync, qui
créera de façon asynchrone une instance HttpResponseMessage.
• Autres types Web API utilise un média formatter pour sérialiser la valeur
retournée dans le corps de la réponse. Le code de status est 200 (OK).
MVC Web API
Retour des actions - autres types
public IEnumerable<string> GetStudents() {
return new string[] { "value1", "value2" }; }
public IEnumerable<User> GetStudents() {
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
var UserQuery = from st in WPF.Users
select st;
return UserQuery.ToList<User>(); }
Le corps de la réponse HTTP contiendra les données suivantes:
[{"UserID":"f36b22f4-0231-4454-a33d-
4a988349c880","UserName":"Dupond","Passwd":"password","Email":"Dupond@h
elha.be","DepUID":"fa5d16f9-58e0-4fd4-93ff-20aa3e3dc96e"}]
MVC Web API
Retour des actions - autres types
Un désavantage de cette technique est de ne pas pouvoir retrourner un code de
retour. Nous pouvons soit générer une exception de type CLR ex: Exception
Le comportement par défaut sera de transmettre le code d'erreur 500.
Il est également posssible (et même coneillé) d'utiliser HttpResponseException
public IEnumerable<string> GetStudents()
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
MVC Web API
Retour des actions - HttpResponseMessage
public HttpResponseMessage GetStudents()
{
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
var UserQuery = from st in WPF.Users
select st;
return Request.CreateResponse(HttpStatusCode.OK, UserQuery.ToList());
}
MVC Web API
Passage des arguments aux actions
Web API utilise les règles suivantes pour lier les paramètres:
• Si le paramètres est un type simple, WebAPI essaie de récupérer la valeur à partir
de l'URI. Les types simples vont inclure les types primitifs du .NET(int, bool,
double ...) ainsi que TimeSpan, DateTime, Guid, decimal et string.
• Pour les types complexes, WebAPI essaiera de lire la valeur à partir du coprs du
message en utilisant un media type formatter.
Dans l'exemple suivant, nous utiliserons la méthode GET pour récupérer les
données et ensuite la méthode PUT pour les mettre à jour.
La méthode GET recevra l'identifiant unique de l'utilisateur et retournera ses
données sous forme d'un objet
MVC Web API
Passage des arguments aux actions
Nous ferons en sorte que, lorsque un étudiant est choisi dans la liste, une requête
soit envoyée vers le WebAPI (méthode GET).
Une première démarche est de gérer l'événement de changement d'item dans la
liste
$(document).ready(function () {
$('#UserList').change(function (e) {
var userid = $("#UserList").val();
if (userid != null) GetStudent(userid);
}); });
La fonction javascript GetStudent(userid); sera appelée lorsqu'il ya un chagement
de sélection d'item.
MVC Web API
Passage des arguments aux actions
function GetStudent(Userid) {
var uri = '@Url.Action("Values","api")';
uri += '/'+Userid;
$.getJSON(uri)
.done(function (data) {
$("#Nom").val(data[0].UserName);
$("#Mdp").val(data[0].Passwd);
$("#mail").val(data[0].Email);
}); }
Dans notre application, nous retrouvons la route définie pour ce WebAPI qui est
routeTemplate: "api/{controller}/{id}" ce qui correspond bien à notre URI utilisée
var uri = '@Url.Action("Values","api")';
uri += '/'+Userid;
Nous aurons par exemple api/Value/FA5D16F9-58E0-4FD4-93FF-20AA3E3DC96E
MVC Web API
Passage des arguments aux actions
public HttpResponseMessage GetStudents(string id)
{
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
var UserQuery = from st in WPF.Users
where st.UserID.ToString() == id
select st;
return Request.CreateResponse(HttpStatusCode.OK,UserQuery); }
La méthode Get retourne un objet de type Users qui sera sérialisé en JSON. Nous
retrouvons dans la fonction Javascript la récupération des données et
l'initialisation des différents champs
$.getJSON(uri)
.done(function (data) {
$("#Nom").val(data[0].UserName);
$("#Mdp").val(data[0].Passwd);
$("#mail").val(data[0].Email);
}); }
MVC Web API
Passage des arguments aux actions
Nous allons maintenant envisager l'envoi des données modifiées vers le WebAPI
avec la méthode PUT. Cet envoi sera réalisé en gérant l'événement du clic sur le
bouton d'enregistrement.
MVC Web API
Passage des arguments aux actions
$("#Save").click(function () {
var Student = new Object();
Student.UserName = $("#Nom").val();
Student.Passwd = $("#Mdp").val();
Student.Email = $("#mail").val();
Student.UserID = $("#UserList").val();
Student.DepUID = $('#DepList').val();
var uri = '@Url.Action("Values","api")';
$.ajax({
url: uri,
type: 'PUT',
data: JSON.stringify(Student),
contentType: 'application/json; charset=utf-8',
success: function (data) {
alert('Updated Successfully'); },
error: function (msg) { alert(msg); }
}); });
MVC Web API
Passage des arguments aux actions
Dans la fonction javascript associée au clic sur le bouton d'enregistrement, un
objet javascript est créé. Cet objet, si nous respectons les mêmes membres que
ceux de la classe CLR pourra être sérialisé et déssérialisé facilement
public void PutStudent([FromBody]User item)
{
WPFTutorialEntities1 WPF = new WPFTutorialEntities1();
User UserQuery = (from st in WPF.Users
where st.UserID == item.UserID
select st).First();
UserQuery.UserName = item.UserName;
UserQuery.Passwd = item.Passwd;
UserQuery.Email = item.Email;
WPF.SaveChanges(); }
Au moyen de LINQ, nous sélectionons dans le db l'étudiant associé au GUID édité
dans la page web. Il suffit alors de modifier cet objet (ex: UserQuery.Passwd =
item.Passwd;) et ensuite sauver les modifications dans la db (WPF.SaveChanges();)
MVC Comment ajouter Identity
Ajout des packages NUGET à un solution
Microsoft.AspNet.Identity.Owin
Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFramework
Pour intégrer maintenant Owin sous IIS, nous devrons installer les packages
suivant nous assurant ainsi la fonctionnalité minimale:
Microsoft.Owin.Host.SystemWeb ( ou Microsoft.Owin.Host.SelfHost si pas IIS )
Microsoft.Owin.Security.Cookies ( Utilisateur local )
MVC Comment ajouter Identity
Ajout de la classe de démarrage
namespace WebApplication30
{
public class Startup1
{
public void Configuration(IAppBuilder app)
{
}
}
}
MVC Comment ajouter Identity
Désactiver l'authentification par défaut
<system.web>
<authentication mode="None" />
<compilation debug="true" targetFramework="4.7" />
<httpRuntime targetFramework="4.7" />
</system.web>
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
</system.webServer>
MVC Comment ajouter Identity
Stockage des objets dans le contexte OWIN
Nous utiliserons la méthode CreatePerOwinContext de la classe AppBuilder.
L'instance de AppBuilder est fournie par l'hôte au moment de l'exécution.
Pour pouvoir authentifier un utilisateur, nous utiliserons aurons besoin des classes
personnalisées suivantes:
class ApplicationSignInManager : SignInManager<ApplicationUser,
string>
Une des méthodes de cette classe fait référence à la classe assurant la gestion des
utilisateurs. Nous devons donc implémenter la classe suivante
class ApplicationUserManager : UserManager<ApplicationUser>
Une des méthodes de cette classe fait référence à la classe assurant la gestion du contexte lié à la
base de données. Nous devrons donc implémenter la classe suivante:
class ApplicationDbContext : IdentityDbContext<ApplicationUser>
MVC Comment ajouter Identity
Stockage des objets dans le contexte OWIN
Il reste finalement à implémenter une classe que l'on retrouve dans les arguments des
classes génériques de base
class ApplicationUser : IdentityUser
Les trois premières classes implémentent une méthode create qui sera utiliser dans la
classe de démarrage OWIN
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserM
anager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSig
nInManager.Create);
La méthode CreatePerOwinContext a besoin en effet d'un délégué.
MVC Comment ajouter Identity
Ajouter une classe utilisateur
Cette classe doit hériter de l'interface IdentityUser. Vous ajouterez dans cette
classe les différentes propriétés liées aux champs personnalisés de la table des
utilisateurs
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity>
GenerateUserIdentityAsync(UserManager<ApplicationUser>
manager)
{
var userIdentity = await
manager.CreateIdentityAsync(this,
DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
public MyProperty {get; set;}
}
MVC Comment ajouter Identity
Ajouter une classe définissant le DB contexte
Cette classe doit hériter de l'interface IdentityDbContext<ApplicationUser>. Vous
ajouterez dans cette classe les différentes propriétés liées aux champs
personnalisés de la table des utilisateurs. ApplicationUser est la classe créée
précédemment
public class ApplicationDbContext :
IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MyConnectionString2", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
MVC Comment ajouter Identity
Ajouter une classe définissant le DB contexte
public ApplicationDbContext()
: base("MyConnectionString2", throwIfV1Schema:
false)
"MyConnectionString2" définit l'entrée dans le fichier Web.config
permettant de caractériser la chaîne de connexion d'accès à la base de données
<connectionStrings>
<add name="MyConnectionString2"
connectionString="Data Source=.\SQLEXPRESS;Initial
Catalog=IdentityTest;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
</connectionStrings>
MVC Comment ajouter Identity
Personnalisation de UserManager
public class ApplicationUserManager :
UserManager<ApplicationUser> {
public
ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store){}
public static ApplicationUserManager
Create(IdentityFactoryOptions<ApplicationUserManager>
options, IOwinContext context){
var manager = new ApplicationUserManager(new
UserStore<ApplicationUser>(context.Get<ApplicationDbContex
t>()));
//personalisation du manager
return manager; }
}
MVC Comment ajouter Identity
Personnalisation de RoleManager
public class ApplicationRoleManager :
RoleManager<IdentityRole>
{
public
ApplicationRoleManager(IRoleStore<IdentityRole, string>
roleStore) : base(roleStore) {}
public static ApplicationRoleManager
Create(IdentityFactoryOptions<ApplicationRoleManager>
options, IOwinContext context)
{
return new ApplicationRoleManager(new
RoleStore<IdentityRole>(new ApplicationDbContext()));
}
}
MVC Comment ajouter Identity
Personnalisation de SignInManager
public class ApplicationSignInManager :
SignInManager<ApplicationUser, string> {
public
ApplicationSignInManager(ApplicationUserManager
userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager) { }
public static ApplicationSignInManager
Create(IdentityFactoryOptions<ApplicationSignInManager>
options, IOwinContext context)
{
return new
ApplicationSignInManager(context.GetUserManager<Applicatio
nUserManager>(), context.Authentication);
}
}
MVC Comment ajouter Identity
Personnalisation de SignInManager
public class ApplicationSignInManager :
SignInManager<ApplicationUser, string> {
public
ApplicationSignInManager(ApplicationUserManager
userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager) { }
public static ApplicationSignInManager
Create(IdentityFactoryOptions<ApplicationSignInManager>
options, IOwinContext context)
{
return new
ApplicationSignInManager(context.GetUserManager<Applicatio
nUserManager>(), context.Authentication);
}
}
MVC Comment ajouter Identity
Personnaliser la classe de startup OWIN
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(Applicati
onUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(Applica
tionSignInManager.Create);
//....
}
MVC Comment ajouter Identity
Personnaliser la classe de startup OWIN
public void ConfigureAuth(IAppBuilder app){
//....
app.UseCookieAuthentication(new
CookieAuthenticationOptions
{
AuthenticationType =
DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new
PathString("/Account/Login")
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<ApplicationUserM
anager, Models.ApplicationUser>(
validateInterval:
TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) =>
user.GenerateUserIdentityAsync(manager))
MVC Comment ajouter Identity
Classe de startup OWIN – Web.config
<appSettings>
<add key="owin:appStartup" value="StartUpConfiguration" />
</appSettings>
Nous ajouterons juste au dessus de la classe
[assembly: OwinStartup("StartUpConfiguration",
typeof(WebApplication30.Startup1))]
MVC Comment ajouter Identity
Controller – Login action
public async Task<ActionResult> Login(LoginViewModel
model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationSignInManager signInManager =
HttpContext.GetOwinContext().Get<ApplicationSignInManager>
();
var result = await
signInManager.PasswordSignInAsync(model.Login,
model.Password, false, shouldLockout: false);
//....
MVC Comment ajouter Identity
Controller – Login action
public async Task<ActionResult> Login(LoginViewModel
model, string returnUrl) {
//....
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode",
new { ReturnUrl = returnUrl, RememberMe = model.RememberMe
});
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid
login attempt.");
return View(model); }}