asp.net mvc et web api -...
TRANSCRIPT
1
Asp.Net MVC et WEB API
I. MVC ......................................................................................................................................................... 4
1. « FROM SCRATCH » - DEMARRER AVEC UN PROJET ASP.NET VIDE................................................................................. 4
2. ROUTES ............................................................................................................................................................. 8
a. RouteConfig ................................................................................................................................................ 8
b. Attribut Route ............................................................................................................................................. 9
c. Area ........................................................................................................................................................... 10
d. Récupérer les informations de route ........................................................................................................ 11
3. CONTROLEURS ................................................................................................................................................... 12
a. « Action Result » ....................................................................................................................................... 12
b. Index ......................................................................................................................................................... 12
c. Détails ....................................................................................................................................................... 13
d. Create ....................................................................................................................................................... 13
e. Edit ............................................................................................................................................................ 14
f. Delete ........................................................................................................................................................ 14
g. Actions asynchrones ................................................................................................................................. 15
h. Actions Selectors ....................................................................................................................................... 15
i. Action filters ......................................................................................................................................... 16
j. Contrôleur Mvc comme Web Service ........................................................................................................ 17
4. VIEWS ............................................................................................................................................................. 20
a. _ViewStart ........................................................................................................................................... 22
b. _Layout (Master page/ Page de disposition) ....................................................................................... 22 @RenderSection() ...................................................................................................................................................... 23
c. Définir le modèle de la vue .................................................................................................................. 23
d. Html Helpers ........................................................................................................................................ 23
e. Code expressions pour insérer du C# dans le HTML .......................................................................... 25 Code blocks ................................................................................................................................................................ 25
f. Scripts .................................................................................................................................................. 25
g. SelectList .............................................................................................................................................. 26
h. Moteur de vue Aspx ............................................................................................................................. 26
i. Ajax Helpers ......................................................................................................................................... 26 Ajax Form ................................................................................................................................................................... 27 Ajax ActionLink........................................................................................................................................................... 28
5. VALIDATION ...................................................................................................................................................... 29
a. Data Annotations ................................................................................................................................. 29
b. IValidatableObject ............................................................................................................................... 31
c. Modifier le style des erreurs ................................................................................................................ 32
d. Validation côté « Client » avec jQuery Validation Plugin ................................................................... 32
6. SECURITE ..................................................................................................................................................... 33
a. Windows (Intranet) .............................................................................................................................. 33
b. « From scratch » - Sécurité « Forms » avec un projet Asp.Net vide .................................................... 34 Packages NuGet ......................................................................................................................................................... 34 Préparation ................................................................................................................................................................ 34
« IdentityConfig » (dossier « App_Start ») ............................................................................................................ 34 ApplicationUser..................................................................................................................................................... 36 Classe partielle « Startup » ................................................................................................................................... 37 Base de données ................................................................................................................................................... 38
2
2
Personnalisation des pages ................................................................................................................................... 38 Contrôleurs et ViewModels .................................................................................................................................. 39
Inscription (register) .................................................................................................................................................. 40 Connexion (Login) ...................................................................................................................................................... 43 Connexion «externe » avec Facebook, Google, Twitter ............................................................................................ 48
Facebook ............................................................................................................................................................... 48 Google ................................................................................................................................................................... 50 Twitter................................................................................................................................................................... 52 Code supplémentaire ............................................................................................................................................ 52
c. Autorisations (attribut « Authorize ») .................................................................................................. 55 ValidateAntiForgeryToken ......................................................................................................................................... 56 AntiXSS ....................................................................................................................................................................... 57
d. SSL ........................................................................................................................................................ 57
e. Projet Visual Studio 2012 ..................................................................................................................... 58 InitializeSimpleMembership et WebSecurity ............................................................................................................. 58 SimpleRoleProvider et SimpleMembershipProvider ................................................................................................. 59 Register ...................................................................................................................................................................... 60 Login .......................................................................................................................................................................... 60
7. LOCALISATION .............................................................................................................................................. 61
Dates et Monnaies........................................................................................................................................ 61
Resources ...................................................................................................................................................... 62
8. LESS ........................................................................................................................................................... 63
9. DEPENDENCY INJECTION (DI) .......................................................................................................................... 63 Avec Ninject ............................................................................................................................................................... 63
10. UPGRADE ................................................................................................................................................ 64
II. WEB API ................................................................................................................................................. 65
1. Web Api config ..................................................................................................................................... 65
2. Contrôleur Web Api ............................................................................................................................. 65 a. HTTP Status codes ............................................................................................................................................ 65 b. Ajout d’un contrôleur Web Api ........................................................................................................................ 65 c. Contrôleur Web API avec IhttpActionResult .................................................................................................... 66 d. Contrôleur Web API avec HttpResponseMessage ............................................................................................ 68
3. Attribut “Route” ................................................................................................................................... 71
4. Injection de dépendances .................................................................................................................... 71 Avec Ninject ............................................................................................................................................................... 71
5. Projet Client ......................................................................................................................................... 72 a. dataService JavaScript ...................................................................................................................................... 72 b. HttpClient ......................................................................................................................................................... 73
6. Cors ...................................................................................................................................................... 75
7. Formatters pour le « Mapping » .......................................................................................................... 76
8. OData................................................................................................................................................... 76
9. Authentification Web Api .................................................................................................................... 77
10. MongoDB ........................................................................................................................................ 82 a. Installation de MongoDB C# driver .................................................................................................................. 82 b. Service WEB ..................................................................................................................................................... 83
III. BOOTSTRAP ........................................................................................................................................... 86
1. INSTALLATION ET THEMES ............................................................................................................................... 86
2. GRID SYSTEM ............................................................................................................................................... 87
a. Cacher une colonne selon une résolution ............................................................................................ 87
b. Décalage de colonnes avec « offset » .................................................................................................. 88
3
3
c. Image flottante .................................................................................................................................... 88
3. BASES ......................................................................................................................................................... 88
a. Typographie ......................................................................................................................................... 88
b. Boutons, groupes de boutons et dropdowns ....................................................................................... 89
c. Icones ................................................................................................................................................... 90
d. Formulaires, listes, et tables ................................................................................................................ 90 « input-group » .......................................................................................................................................................... 90
e. Navbar desktop et mobile .................................................................................................................... 90
f. Header et breadcrumb ......................................................................................................................... 91
g. Pagination............................................................................................................................................ 91
h. Well ...................................................................................................................................................... 92
i. Panels .................................................................................................................................................. 92
PLUGINS .............................................................................................................................................................. 92
a. Collapse et accordéon .......................................................................................................................... 92
b. Boite de dialogue ................................................................................................................................. 93
c. Alert ..................................................................................................................................................... 93
d. Tab ....................................................................................................................................................... 94
e. Tooltip .................................................................................................................................................. 94
f. Caroussel.............................................................................................................................................. 95
4
4
I. MVC
1. « From scratch » - Démarrer avec un projet Asp.Net vide 1. Créer un projet Asp.Net vide
2. Installer « Asp.Net Mvc 5 » avec les packages NuGet
3. Ajouter « RouteConfig » dans un dossier « App_Start »
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace MvcFromScratch { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } }
Vue (Affiche les données)
Vue partielle
« content » (texte, xml)
JSON
Redirection
etc.
Contrôleur Modèles/
Données Va chercher
les données
Définition des routes de
l’application. Ici la route par
défaut dirigeant vers l’action
« Index » du contrôleur « Home »
Action
Result
ROUTES
« http://... / » « contrôleur »/« action »/« id »
5
5
4. Ajouter fichier de configuration de l’application « Global.asax»
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace MvcFromScratch { public class Global : System.Web.HttpApplication { protected void Application_Start() { RouteConfig.RegisterRoutes(RouteTable.Routes); } } }
5. Ajouter un contrôleur « Home » dans un dossier « Controllers »
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcFromScratch.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { return Content("Bonjour!"); } } }
Projet minimum
Enregistrement des routes
définies dans le fichier
« RouteConfig »
L’ « action result » ici est
une chaine de caractère.
Cela affichera juste ce
message dans une page
6
6
Avec Vue en « action result »
public ActionResult Index() { return View(); }
Vues :
« _ViewStart.cshtml » hérite de « StartPage » (System.Web.Mvc) sert à définir la master
page « par défaut » utilisée par toutes les pages de contenu.
Toutes les autres pages héritent de « WebViewPage » (_Lauout, Index, etc.)
2 possibilités :
Soit définir en haut de chaque vue la page dont elle hérite
Exemple « _ViewStart.cshtml »
@inherits System.Web.WebPages.StartPage @{ Layout = "~/Views/Shared/_Layout.cshtml"; }
« _Layout.cshtml » la master page (ou page de disposition)
@inherits System.Web.Mvc.WebViewPage <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> </head> <body style="background:red;"> <div> @RenderBody() </div> </body> </html>
7
7
Soit on crée un fichier de configuration « Web.config » pour le dossier des vues
« Views » pour ne pas avoir à définir sur chaque page <?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> </sectionGroup> </configSections> <system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="MvcFromScratch" /> </namespaces> </pages> </system.web.webPages.razor> </configuration> Ainsi on peut simplifier les pages… par exemple « _ViewStart.cshtml »
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }
Projet minium avec vues et master page
On supprime l’héritage en
haut des pages
_Layout.cshtml la master page
_ViewStart.cshtml permet de définir la master page à appliquer par
défaut aux pages de contenu
Les vues sont rangées dans un dossier portant le nom de leur
contrôleur
8
8
2. Routes
a. RouteConfig
Définir les routes de l’application dans « RouteConfig » (dossier « App_Start »)
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { action = "Index", controller="People", id = UrlParameter.Optional } ); } }
Créer une seconde route public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Ma route", url: "personal/{myparameter}", defaults: new { controller = "Home", action = "Personal", myparameter = "Bonjour" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
Dans le contrôleur « Home », on ajoute l’action pour cette route public class HomeController : Controller { // code retiré public ActionResult Personal(string myparameter) { return Content(myparameter); } }
On place la route au-dessus de
la route « par défaut »
La route
« http://.../personal/ »
9
9
Dans le cas où on aurait pas défini de paramètre de route routes.MapRoute( name: "Ma route", url: "personal", defaults: new { controller = "Home", action = "Personal"} );
… On pourrait quand même passer un paramètre à l’action (code inchangé) en le nommant
b. Attribut Route public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapMvcAttributeRoutes(); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
Utilisation : Dans le contrôleur « Home » [Route("personal")] public ActionResult Personal() { return Content("Route ok!"); }
Si on veut ajouter des paramètres, les mettre entre accolades [Route("personal/{message}")] public ActionResult Personal(string message) { return Content(message); }
Permettre l’utilisation de
l’attribut « route »
.Attention de bien mettre
avant la route par défaut
Paramètre passé
Renvoie par défaut la valeur définie dans
« RouteConfig »
10
10
c. Area
Il peut être intéressant de découper les gros projets en « Areas »
Exemple
public class MusicAreaRegistration : AreaRegistration { public override string AreaName { get { return "Music"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Music_default", "Music/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, namespaces: new[] { "AreaDemo.Areas.Music.Controllers" } ); } }
« Global.asax »
AreaRegistration.RegisterAllAreas();
11
11
d. Récupérer les informations de route
public class HomeController : Controller { // code retiré public ActionResult Personal() { var controller = RouteData.Values["controller"]; // var action = RouteData.Values["action"]; // var id = RouteData.Values["id"]; // var result = string.Format("{0}, {1}, {2}", controller, action, id); return Content(result); } }
Exemple depuis une vue on crée un lien
@Html.ActionLink("A envoyer!", "Personal", new { controller = "Home", id = 10 }) L’adresse du lien « http://localhost:7211/Home/Personal/10 »
Depuis une action du contrôleur
public ActionResult Index() { return RedirectToAction("Personal", "Home", new { id = 100 }); }
action contrôleu
r
paramètre
12
12
3. Contrôleurs
a. « Action Result »
ViewResult : une page complète
public ActionResult Index() { IEnumerable<Person> people = _peopleRepository.GetAll(); return View(people); } PartialViewResult : une section de page
return PartialView("_people", people);
ContentResult : texte, xml return Content("Bonjour!");
JsonResult : objet JSON return Json(people,JsonRequestBehavior.AllowGet);
RedirectToRouteResult : redirection vers une autre action return RedirectToRoute(new { controller = "Home", action = "index" }); return RedirectToAction("Index");
JavaScriptResult return JavaScript("alert('Bonjour!')"); + EmptyResult, FileResult, HttpUnauthorizedResult, etc.
b. Index public ActionResult Index() { IEnumerable<Person> people = _peopleRepository.GetAll(); return View(people); }
13
13
c. Détails public ActionResult Details(int id) { Person person = _peopleRepository.GetOne(id); if (person == null) return HttpNotFound("Personne non trouvée"); return View(person); }
d. Create public ActionResult Create() { Person newPerson = new Person(); return View(newPerson); } [HttpPost] public ActionResult Create(Person person) { if (ModelState.IsValid) { _peopleRepository.Add(person); return RedirectToAction("Index"); } return View(person); }
On crée dans un premier temps un
nouvel élément que l’on affiche
dans la vue « Create »
on ajoute le nouvel élément
(post). S’il y a des erreurs de
validation on redirige pour que
l’utilisateur corrige
14
14
e. Edit public ActionResult Edit(int id = 0) { Person person = _peopleRepository.GetOne(id); if (person == null) return HttpNotFound("Personne non trouvée"); return View(person); } [HttpPost] public ActionResult Edit(Person person) { if (ModelState.IsValid) { _peopleRepository.Update(person); return RedirectToAction("Index"); } return View(person); }
f. Delete public ActionResult Delete(int id = 0) { Person person = _peopleRepository.GetOne(id); if (person == null) return HttpNotFound("Personne non trouvée"); return View(person); } [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { _peopleRepository.Delete(id); return RedirectToAction("Index"); }
On récupère l’élément à
modifier que l’on affiche
dans la vue « Edit »
on sauve les modifications
apportées s’il n’y a pas
d’erreurs
On récupère l’élément à
supprimer que l’on affiche
Si l’utilisateur confirme la
suppression, on supprime
réellement l’élément.
15
15
g. Actions asynchrones public async Task<ActionResult> Index()
{
IEnumerable<Person> people = await _peopleRepository.GetAllAsync(); return View(people); }
h. Actions Selectors
Attribut « HttpGet » par défaut, inutile de l’indiquer
Attribut « HttpPost »
ViewBag permet d’afficher des messages passés depuis le contrôleur.
Exemple
Dans le contrôleur
public ActionResult Index() { ViewBag.Message1 = "Bonjour!"; ViewBag.Message2 = "Aurevoir :x"; return View(); }
Dans la vue
<h3>@ViewBag.Message1</h3> <h3>@ViewBag.Message2</h3>
16
16
i. Action filters
Ce sont des attributs que l’on place au-dessus d’une action ou du contrôleur.
Authorization filter (Attribut « Authorize »)
Autorize et ValidateAntiForgeryToken Voir Authentification
ValidateInput Accepte toutes les entrées (même s’il y a des balises, ce qui peut représenter un
code malveillant)
[HttpPost] [ValidateInput(true)] public ActionResult Edit(Person person) { // } Il est possible également d’utiliser l’attribut « AllowHtml » pour une propriété du modèle
[AllowHtml] public string LastName { get; set; }
Action filter (faire hériter de « ActionFilterAttribute » et override « OnActionExecuting »,
« OnActionExecuted »)
Result filter : exemple « OutputCache » (met en cache 60 secondes)
//[ChildActionOnly] [OutputCache(Duration=60)] public ActionResult Index() { IEnumerable<Person> people = _peopleRepository.GetAll(); return View(people); }
Exception fliter
Pour gérer une « unhandled exception ». Exemple redirige vers une vue
[HandleError(View="Errors")] public ActionResult Edit(Person person) { Pour voir la page erreur comme l’utilisateur (et non page d’erreur « coté serveur »)
17
17
j. Contrôleur Mvc comme Web Service
Même si utiliser un contrôleur Web Api est tout indiqué aujourd’hui, cela reste possible
d’utiliser un contrôleur Mvc comme web service.
public class PeopleWebServiceController : Controller { private IPeopleRepository _peopleRepository; public PeopleWebServiceController() : this(new FakePeopleRepository()) { } public PeopleWebServiceController(IPeopleRepository peopleRepository) { this._peopleRepository = peopleRepository; } public ActionResult Index() { IEnumerable<Person> people = _peopleRepository.GetAll(); return Json(people, JsonRequestBehavior.AllowGet); } public ActionResult Details(int id) { Person person = _peopleRepository.GetOne(id); if (person == null) return HttpNotFound(); return Json(person, JsonRequestBehavior.AllowGet); } [HttpPost] public void Create(Person person) { _peopleRepository.Add(person); } [HttpPost] public void Edit(Person person) { _peopleRepository.Update(person); } public void Delete(int id) { _peopleRepository.Delete(id); } }
Json.net (documentation) Sérialization d’un objet
string json = JsonConvert.SerializeObject(person);
Désérialization de JSon JsonConvert.DeserializeObject<Person>(resultContent)
18
18
Tests avec Fiddler
Index et détails
Ajout
Edition
Suppression
19
19
Projet Client
Utilisation de HttpClient(System.Net.Http) et de Json.Net (Projet WinRT, Wpf)
public class PeopleService { string urlBase = "http://localhost:15089/peoplewebservice/"; public async Task<Person[]> GetAll() { using (HttpClient client = new HttpClient()) { HttpResponseMessage response = await client.GetAsync(urlBase); string resultContent = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<Person[]>(resultContent); } } public async Task<Person> GetOne(int personID) { using (HttpClient client = new HttpClient()) { HttpResponseMessage result = await client.GetAsync(string.Format("{0}/details/{1}", urlBase, personID)); string resultContent = await result.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Add(Person person) { using (HttpClient client = new HttpClient()) { string json = JsonConvert.SerializeObject(person); HttpResponseMessage response = await client.PostAsync(string.Format("{0}/create", urlBase), new StringContent(json, Encoding.UTF8, "application/json")); string resultContent = await response.Content.ReadAsStringAsync(); person = JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Update(Person person) { using (HttpClient client = new HttpClient()) { string json = JsonConvert.SerializeObject(person); HttpResponseMessage response = await client.PutAsync(string.Format("{0}/edit", urlBase), new StringContent(json, Encoding.UTF8, "application/json")); string resultContent = await response.Content.ReadAsStringAsync(); person = JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Delete(int personID) { using (HttpClient client = new HttpClient()) { HttpResponseMessage response = await client.DeleteAsync(string.Format("{0}/delete/{1}", urlBase, personID)); response.EnsureSuccessStatusCode(); } } }
20
20
4. Views
Il existe 2 moteurs de vues : Razor (*.cshtml) et Aspx (*.aspx)
(Le code des contrôleurs ne diffère pas selon le moteur de vue utilisé)
On peut ajouter une vue :
- Depuis le menu contextuel sur une action d’un contrôleur - Sur le répertoire « Views »
Un dossier de vues par contrôleur
Vue Action Index Index Details Details Create Create Edit Edit Delete Delete …
Un dossier de vues par
contrôleur
Contrôleurs
21
21
Création d’une vue
Vue partielle
Utilisation : Dans la vue
@Html.Partial("_people", Model)
Contrôleur
return PartialView("_people", people);
Choix du modèle de la vue
Vue partielle. On a l’habitude
de nommer les vues
partielles avec « _ » pour les
différencier rapidement
22
22
a. _ViewStart
Définir la master page par défaut pour les pages de contenu @{ Layout = "~/Views/Shared/_Layout.cshtml"; }
Pour affecter une autre master page à une vue particulière. Définir la master page en haut de la
vue @{ Layout = "~/Views/Shared/_MyLayout.cshtml"; }
b. _Layout (Master page/ Page de disposition)
(Dossier « Shared »)
Contient tout le HTML commun pour les pages ainsi que RenderBody() pour afficher le contenu
des vues et RenderSection(). On peut créer plusieurs master pages.
Exemple de master page <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title</title> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") </head> <body> <header> <h1>Titre</h1> <nav> <ul> <li>@Html.ActionLink("Accueil", "Index")</li> <li>@Html.ActionLink("A propos", "Aboutus")</li> <li>@Html.ActionLink("Contact", "Contact")</li> </ul> </nav> </header> <div> @RenderBody() </div> <footer></footer> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) </body> </html>
23
23
@RenderSection()
Permet de définir ses propres sections
Exemple Dans la master page (_layout.cshtml) <div> @if (IsSectionDefined("header")) { @RenderSection("header", required: false); } else { <h3>Header par défaut</h3> } </div> <div class="container"> @RenderBody() </div> Dans une vue @section header { <h3>Ma section header</h3> }
c. Définir le modèle de la vue
Exemple
@model MvcOverview.Models.Person Ou pour une vue Liste @model IEnumerable<MvcOverview.Models.Person>
Avec @using @using MvcOverview.Models @model IEnumerable<Person>
d. Html Helpers
Documentation
Lien
(Avec paramètre ici)
@Html.ActionLink("Editer", "Edit", new { id = item.Id }) Action
Insère le résultat de l’action (ici la vue « Create »)
<p>@Html.Action("Create")</p> @url
<a href="@Url.Action("Index","Home")"><img src="~/Images/logo.png" /></a>
24
24
Formulaire
Html Fortement typé BeginForm EndForm TextArea TextBox Label CheckBox RadioButton ListBox
CheckBoxFor EditorFor ListBoxFor RadioButtonFor TextAreaFor ValidationMessage ValidationSummary
Vue en consultation (List, Details)
Label (DisplayNameFor) @Html.DisplayNameFor(model => model.FirstName) Affiche la valeur (DisplayFor) @Html.DisplayFor(model => model.FirstName)
Vue avec édition (Create, Edit)
Label @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label
col-md-2" }) … puis champ en edition avec valeur et Validation @Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.FirstName, "", new { @class = "text-danger"
})
Dans un formulaire @using (Html.BeginForm()) { @Html.ValidationSummary(true) @Html.AntiForgeryToken() @* code ici *@ } Exemple de formulaire @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>People</legend> <div class="editor-label"> @Html.LabelFor(model => model.FirstName) </div> <div class="editor-field"> @Html.EditorFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div> @* etc. *@ <p> <input type="submit" value="Valider" /> </p> </fieldset> } <div> @Html.ActionLink("Retour", "Index") </div>
25
25
e. Code expressions pour insérer du C# dans le HTML
Valeur <p>there are @Model.Count() people.</p>
@@ Permet de ne pas confondre avec du C#
Plusieurs instructions @(item.FirstName + ' ' + item.LastName)
Code blocks
Permet par exemple de créer ses variables et les réutiliser
@{ ViewBag.Title = "Index"; var count = Model.Count(); } <p>there are @count people.</p>
Dans les blocs @if ,@foreach … @foreach (var item in Model) { @: var fullName = string.Format("{0}, {1}", item.LastName, item.FirstName); <td> code block : @fullName </td> </tr> }
Insérer du texte dans un code block
<text>Mon texte</text> @:Mon texte Fonction
@DoSomething() @helper DoSomething() { <span>Bonjour depuis la fonction!</span> }
f. Scripts @section Scripts { <script> $(function () { }); </script> }
Exécute la fonction
26
26
g. SelectList
Pour afficher une liste avec sélection de l’élément.
…source, « champ de sélection », « champ affiché »,selectedValue
@Html.DropDownListFor(modelItem => item.CategoryID,new
SelectList(Model.Categories,"CategoryID","CategoryName",item.CategoryID))
public class ClientViewModel
{
public IList<ClientModel> Clients { get; set; }
public IList<CategoryModel> Categories { get; set; }
}
On peut aussi définir « SelectList » dans le modèle
h. Moteur de vue Aspx
Un mot sur le moteur de vue Aspx.Les pages ont pour extension *.aspx.
On utilise : <%= %> ou <%: %> pour encadrer une instruction Ex :<%= Html.ActionLink("Home","Index") %>
<% %> pour encadrer les blocs de code (exemple avec une variable ou if/foreach)
i. Ajax Helpers
Ajax options
Url url requested Confirm Message de confirmation affiché dans une
boite de dialogue OnBegin,OnComplete,OnSuccess,OnFailure LoadingElementId L’élément affiché pendant le « chargement »
(exemple un spinner) LoadingElementDuration Durée d’affichage de l’élément de chargement UpdateTargetId Element modifé InsertionMode Replace,InsertAfter,InsertBefore
Unobtrusive Ajax
27
27
Ajax Form
Le vue @model IEnumerable<string> @{ ViewBag.Title = "Home Page"; } @* Ajax form *@ @using (Ajax.BeginForm("Index", new AjaxOptions { HttpMethod = "get", InsertionMode = InsertionMode.Replace, UpdateTargetId = "cityList", LoadingElementDuration = 500, LoadingElementId = "progress" })) { <input type="search" name="searchTerm" /> <input type="submit" value="Submit" /> } @* spinner *@ <div id="progress" style="display:none"> <img src="~/Images/spinner.gif" /> </div> @* partial view *@ @Html.Partial("_cities", Model) @* unobstrusive Ajax *@ @section scripts { <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
} La vue partielle @model IEnumerable<string> <div id="cityList"> <table> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item) </td> </tr> } </table>
</div>
28
28
Le contrôleur public class HomeController : Controller { private static readonly string[] cities = { "Shanghai", "Moscou", "Seoul", "Beijing", "Tokyo", "Mexico", "New York", "Londres","Bangkok","Caire", "Rio de Janeiro", "Saint Petersburg", "Los Angeles", "Yokohama","Berlin", "Madrid", "Chicago", "Houston", "Philadelphie", "Phoenix","San Diego", "Dallas", "Indianapolis", "San Francisco", "Austin", "Columbus", "Fort Worth", "Charlotte", "Detroit","Boston", "Washington", "Denver","Portland", "Las Vegas","Atlanta", "Colorado Springs", "Omaha", "Miami", "Cleveland", "Minneapolis","Honolulu", "Buffalo", "Lincoln", "Orlando", "Chandler", "Laredo", "Madison", "Reno","Irving", "Toronto", "Montreal", "Vancouver", "Calgary", "Edmonton", "Quebec", }; public ActionResult Index(string searchTerm = null) { if (Request.IsAjaxRequest()) if (searchTerm != null) { var result = cities.Where(c => c.ToLower().StartsWith(searchTerm.ToLower())); return PartialView("_cities", result); } return View(cities); }
}
Ajax ActionLink
@Ajax.ActionLink("City List", "Index", new AjaxOptions { UpdateTargetId = "cityList",
HttpMethod = "get" })
29
29
5. Validation Pour bien faire il faut une validation côté client et une validation à la mise à jour côté serveur.
a. Data Annotations
Documentation
On distingue :
Les attributs servant à mettre en forme
Display
DisplayFormat
DisplayColumn pour la clé étrangère
DataType permet de mettre en forme (exemple password)
Les attributs de validation:
Required
StringLength
MaxLength
MinLength
Compare
Range
RegularExpression
Attribut Key
Namespace « Schema »
Table
Column
ComplexType
DatabaseGeneratded
InverseProperty
ForeignKey
NotMapped
30
30
Custom Attribute
Exemple création d’un attribut permettant de valider Twitter
public class TwitterAttribute : ValidationAttribute { private string pattern = "^@([A-Za-z0-9_]+)"; protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value != null) { var valueAsString = value.ToString(); if (!Regex.IsMatch(valueAsString, "^@([A-Za-z0-9_]+)")) { return new ValidationResult("Enter a valide twitter"); } } return ValidationResult.Success; } private readonly int _maxWords; }
31
31
Utilisation
[Display(Name = "Twitter : ")] [Twitter] public string Twitter { get; set; }
Contrôleur
[HttpPost] public ActionResult Create(Person person) { ValidatePerson(person); if (ModelState.IsValid) { _peopleRepository.Add(person); return RedirectToAction("Index"); } return View(person); } private void ValidatePerson(Person person) { string twitter = person.Twitter; if (string.IsNullOrWhiteSpace(twitter)) return; if (!Regex.IsMatch(twitter, "^@([A-Za-z0-9_]+)")) ModelState.AddModelError("Twitter", "Twitter invalide."); }
b. IValidatableObject
Le modèle peut implémenter IValidatableObject, la méthode « Validate » sera automatiquement
appelée …
public class Person : IValidatableObject { // … properties public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (!Regex.IsMatch(Twitter, "^@([A-Za-z0-9_]+)")) { yield return new ValidationResult("Enter a valide twitter", new[] { "Twitter" }); } } }
Vérifie si le formulaire
ne contient pas
d’erreurs
on ajoute une erreur au
modèle
32
32
c. Modifier le style des erreurs .input-validation-error { border: 1px solid #e64343; box-shadow: 0 0 2px 0 rgba(230, 67, 67, 0.4); } .input-validation-error:focus { background: #fcecec; border: 1px solid #e64343; box-shadow: 0 0 2px 0 rgba(230, 67, 67, 0.4); } .validationMessage { color: Red; }
d. Validation côté « Client » avec jQuery Validation Plugin
http://jqueryvalidation.org/
Référence <script src="~/Scripts/jquery.validate.min.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
Exemple pour afficher un message d’erreur <div> @Html.LabelFor(model => model.FirstName) @Html.TextBoxFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div>
33
33
6. Sécurité Articles sur la sécurité Asp.Net
a. Windows (Intranet)
<p>Bonjour, @User.Identity.Name!</p>
<configuration> <system.web> <compilation debug="true" targetFramework="4.5.1" /> <httpRuntime targetFramework="4.5.1" /> <authentication mode="Windows" /> </system.web> </configuration>
34
34
b. « From scratch » - Sécurité « Forms » avec un projet Asp.Net vide
Packages NuGet
Besoin de :
EntityFramework
AspNet.Identity.Core
Identity.EntityFramework
Identity.Owin
Microsoft.Owin.Host.SystemWeb (méthodes d’extension)
Depuis la console du gestionnaire de package PM> Install-Package EntityFramework PM> Install-Package Microsoft.AspNet.Identity.Core PM> Install-Package Microsoft.AspNet.Identity.EntityFramework PM> Install-Package Microsoft.AspNet.Identity.Owin PM> Install-Package Microsoft.Owin.Host.SystemWeb
+ Si on utilise Facebook, Google, Twitter, etc. pour une connexion externe.
PM> install-package Microsoft.Owin.Security.Facebook PM> install-package Microsoft.Owin.Security.Google PM> install-package Microsoft.Owin.Security.Twitter
Préparation
« IdentityConfig » (dossier « App_Start ») using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security; using MvcFromScratch.Models; using System.Net.Mail; namespace MvcFromScratch { public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { var client = new SmtpClient("smtp.xyz.fr"); client.Send("[email protected]", message.Destination, message.Subject, message.Body); return Task.FromResult(0); } } public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Connectez votre service SMS ici pour envoyer un message texte.
35
35
return Task.FromResult(0); } } 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<ApplicationDbContext>())); manager.UserValidator = new UserValidator<ApplicationUser>(manager) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; manager.UserLockoutEnabledByDefault = true; manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); manager.MaxFailedAccessAttemptsBeforeLockout = 5; manager.RegisterTwoFactorProvider("Code téléphonique ", new PhoneNumberTokenProvider<ApplicationUser> { MessageFormat = "Votre code de sécurité est {0}" }); manager.RegisterTwoFactorProvider("Code d'e-mail", new EmailTokenProvider<ApplicationUser> { Subject = "Code de sécurité", BodyFormat = "Votre code de sécurité est {0}" }); manager.EmailService = new EmailService(); manager.SmsService = new SmsService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity")); } return manager; } } // Configurer le gestionnaire de connexion d'application
Configuration du nom d’utilisateur
(autoriser les chiffres dans les
noms ? email uniques ?)
Configuration du mot de passe
(longueur min. ? lettre ou digit
requis ? upper case requis ? )
Verrouillage après 5 erreurs
d’authentification
Configuration de ApplicationUserManager
36
36
public class ApplicationSignInManager : SignInManager<ApplicationUser, string> { public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user) { return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); } public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context) { return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication); } } }
ApplicationUser
Dans le dossier « Models »
using System.Data.Entity; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; namespace MvcFromScratch.Models { // consultez http://go.microsoft.com/fwlink/?LinkID=317594 public class ApplicationUser : IdentityUser { public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) { var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); return userIdentity; } } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } } }
37
37
Classe partielle « Startup »
Dans le dossier « App_Start »
using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Google; using Owin; using MvcFromScratch.Models; namespace MvcFromScratch { public partial class Startup { // rendez-vous sur http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); // Autoriser l’application à utiliser un cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Permet à l'application de valider le timbre de sécurité OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); app.UseFacebookAuthentication( appId: "475813752569575", appSecret: "123456789abcdefg "); app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() { ClientId = "903536450598-ps3vsah291ef6iekkqtqflc2d0g746hj.apps.googleusercontent.com", ClientSecret = "123456789abcdefg" }); // Supprimer les commentaires //app.UseTwitterAuthentication( // consumerKey: "", // consumerSecret: ""); } } }
Connexions
« externes »
38
38
A la racine du projet
using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(MvcFromScratch.Startup))] namespace MvcFromScratch { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
Base de données
Créer le dossier spécial « App_Data » dans lequel la base de données sera créée
Chaine de connexion dans « Web.config »
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\Identity.mdf;Initial Catalog=Identity;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings>
Personnalisation des pages
Dans une vue partielle(_LoginPartial) ou la master page (_Layout)
@using Microsoft.AspNet.Identity @if (Request.IsAuthenticated) { <h2>Bonjour @User.Identity.GetUserName() !</h2> @Html.ActionLink("Se déconnecter", "Logoff", "Account") } else { <li>@Html.ActionLink("S’inscrire", "Register", "Account")</li> <li>@Html.ActionLink("Se connecter", "Login", "Account")</li> }
On nomme la base qui sera créée « Identity »
par exemple
« AspNetUsers » Contient tous les utilisateurs
(email, userName, passwordhash,etc.).
Pas de mot de passe pour les utilisateurs
connectés en « externe » (facebook, etc.) , mais
ajout de l’Id dans « AspNetUserLogins »
On pourrait également vérifier si
l’utilisateur appartient à un rôle,
pour donner accès à la gestion du
site par exemple
39
39
Contrôleurs et ViewModels
Créer « AccountController » dans le dossier « Controllers »
using MvcFromScratch.Models; using System; using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; namespace MvcFromScratch.Controllers { [Authorize] public class AccountController : Controller { private ApplicationSignInManager _signInManager; private ApplicationUserManager _userManager; public ApplicationSignInManager SignInManager { get { return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>(); } private set { _signInManager = value; } } public ApplicationUserManager UserManager { get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); } private set { _userManager = value; } } private IAuthenticationManager AuthenticationManager { get { return HttpContext.GetOwinContext().Authentication; } } private IAuthenticationManager AuthenticationManager { Get { return HttpContext.GetOwinContext().Authentication; } } private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) ModelState.AddModelError("", error); } } }
Créer « AccountViewModels » qui contiendra tous les ViewModels dans le dossier
« Models »
40
40
Inscription (register)
« AccountController »
// GET: /Account/Register [AllowAnonymous] public ActionResult Register() { return View(); } // POST: /Account/Register [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { // Avec confirmation string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); await UserManager.SendEmailAsync(user.Id, "Confirmez votre compte", "Confirmez votre compte en cliquant <a href=\"" + callbackUrl + "\">ici</a>"); ViewBag.Message = "Un email vous a été envoyé afin de confirmer votre compte."; return View("Info"); // Sans confirmation // await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); //return RedirectToAction("Index", "Home"); } AddErrors(result); } return View(model); } // GET: /Account/ConfirmEmail [AllowAnonymous] public async Task<ActionResult> ConfirmEmail(string userId, string code) { if (userId == null || code == null) { return View("Error"); } var result = await UserManager.ConfirmEmailAsync(userId, code); return View(result.Succeeded ? "ConfirmEmail" : "Error"); }
on envoie un email de demande
de confirmation
Sans demande de confirmation, on connecte
directement l’utilisateur après l’inscription et le
redirige vers la page d’accueil
Réaffiche le formulaire en cas d’erreurs de validation
41
41
ViewModels
public class RegisterViewModel { [Required] [EmailAddress] [Display(Name = "Courrier électronique")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Mot de passe")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirmer le mot de passe ")] [Compare("Password", ErrorMessage = "Le mot de passe et le mot de passe de confirmation ne correspondent pas.")] public string ConfirmPassword { get; set; } } Vues:
- Register (pour s’inscrire), ConfirmEmail (affichée après que l’utilisateur ait cliqué sur le lien
de confirmation de l’email envoyé) …Vues dans le dossier « Account » des vues « Views »
- Info, Error (dossier « Shared »)
Register
@model MvcFromScratch.Models.RegisterViewModel @{ ViewBag.Title = "S’inscrire"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("Register", "Account", FormMethod.Post)) { @Html.AntiForgeryToken() <h4>Créer un nouveau compte.</h4> <hr /> @Html.ValidationSummary() <div> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) </div> <div> @Html.LabelFor(m => m.Password) @Html.PasswordFor(m => m.Password) </div> <div> @Html.LabelFor(m => m.ConfirmPassword) @Html.PasswordFor(m => m.ConfirmPassword) </div> <div> <input type="submit" class="btn btn-default" value="Inscription" /> </div> }
42
42
ConfirmEmail
@{ ViewBag.Title = "Confirmation de l'e-mail"; } <h2>@ViewBag.Title.</h2> <div> <p> Merci d'avoir confirmé votre e-mail. Veuillez @Html.ActionLink("Cliquer ici pour vous connecter", "Login", "Account") </p> </div> Info
@{ ViewBag.Title = "Info"; } <h2>@ViewBag.Title.</h2> <h3>@ViewBag.Message</h3> Error
@{ ViewBag.Title = "Erreur"; } <h1>Erreur</h1> <p>@ViewBag.ErrorMessage</p>
43
43
Connexion (Login)
« AccountController »
// GET: /Account/Login [AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); } // POST: /Account/Login [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } var user = await UserManager.FindByNameAsync(model.Email); if (user != null) { if (!await UserManager.IsEmailConfirmedAsync(user.Id)) { ViewBag.ErrorMessage = "Vous devez confirmer votre email."; return View("Error"); } } var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true); 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("", "Tentative de connexion non valide."); return View(model); } } private ActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); } // POST: /Account/LogOff [HttpPost] [ValidateAntiForgeryToken] public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
Vérifie que l’utilisateur a
bien validé son compte avec
l’email
Vérifie que le formulaire ne
comporte pas d’erreurs
Déconnexion
44
44
Mot de passe oublié
// GET: /Account/ForgotPassword [AllowAnonymous] public ActionResult ForgotPassword() { return View(); } // POST: /Account/ForgotPassword [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model) { if (ModelState.IsValid) { var user = await UserManager.FindByNameAsync(model.Email); if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id))) { return View("ForgotPasswordConfirmation"); } var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id); var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); await UserManager.SendEmailAsync(user.Id, "Reset Password", "Cliquez sur ce lien pour réinitaliser votre mot de passe : " + callbackUrl); ViewBag.Link = callbackUrl; return View("ForgotPasswordConfirmation"); } return View(model); } // GET: /Account/ResetPassword [AllowAnonymous] public ActionResult ResetPassword(string code) { var id = Request.QueryString["userid"]; var model = new ResetPasswordViewModel() { Id = id, Code = code }; return code == null ? View("Error") : View(model); } // POST: /Account/ResetPassword [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model) { if (!ModelState.IsValid) { return View(model); } var user = await UserManager.FindByIdAsync(model.Id); if (user == null) { return RedirectToAction("ResetPasswordConfirmation", "Account"); } var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password); if (result.Succeeded) { return RedirectToAction("ResetPasswordConfirmation", "Account"); } AddErrors(result);
45
45
return View(); } // GET: /Account/ResetPasswordConfirmation [AllowAnonymous] public ActionResult ResetPasswordConfirmation() { return View(); } ViewModels
public class LoginViewModel { [Required] [Display(Name = "Courrier électronique")] [EmailAddress] public string Email { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "Mot de passe")] public string Password { get; set; } [Display(Name = "Mémoriser le mot de passe ?")] public bool RememberMe { get; set; } } public class ForgotPasswordViewModel { [Required] [EmailAddress] [Display(Name = "E-mail")] public string Email { get; set; } } public class ResetPasswordViewModel { [Required] public string Id { get; set; } [Required] [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Mot de passe")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirmer le mot de passe")] [Compare("Password", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] public string ConfirmPassword { get; set; } public string Code { get; set; } }
46
46
Vues:
- Login (pour se connecter) …Vue dans le dossier « Account » des vues « Views »
- Pour la réinitalisalisation de mot de passe : ForgotPassword, ForgotPasswordConfirmation,
ResetPassword, ResetPasswordConfirmation
- Lockout (affichée si l’utilsateur se trompe trop de fois dans ses identifiants), Error (dossier
« Shared »)
Login
@using MvcFromScratch.Models @model LoginViewModel @{ ViewBag.Title = "Connexion"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post)) { @Html.AntiForgeryToken() <h4>Utilisez un compte local pour vous connecter.</h4> <hr /> @Html.ValidationSummary(true) <div> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" }) </div> <div> @Html.LabelFor(m => m.Password) @Html.PasswordFor(m => m.Password) @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" }) </div> <div> @Html.CheckBoxFor(m => m.RememberMe) @Html.LabelFor(m => m.RememberMe) </div> <div> <input type="submit" value="Connexion" /> </div> <p> @Html.ActionLink("Mot de passe oublié?", "ForgotPassword") </p> <p> @Html.ActionLink("S'inscrire comme nouvel utilisateur", "Register") </p> } <div> @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl }) </div>
47
47
ForgotPasword
@model MvcFromScratch.Models.ForgotPasswordViewModel @{ ViewBag.Title = "Vous avez oublié votre mot de passe ?"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("ForgotPassword", "Account", FormMethod.Post)) { @Html.AntiForgeryToken() <h4>Entrez une adresse de messagerie.</h4> <hr /> @Html.ValidationSummary() <div> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) </div> <div> <input type="submit" value="Lien de courrier électronique" /> </div> } ForgotPasswordConfirmation
@{ ViewBag.Title = "Confirmation du mot de passe oublié"; } <hgroup class="title"> <h1>@ViewBag.Title.</h1> </hgroup> <div> <p> Vérifiez votre adresse de messagerie pour réinitialiser votre mot de passe. </p> </div>
ResetPasword
@model MvcFromScratch.Models.ResetPasswordViewModel @{ ViewBag.Title = "Réinitialiser le mot de passe"; } <h2>@ViewBag.Title.</h2> @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post)) { @Html.AntiForgeryToken() <h4>Réinitialisez votre mot de passe.</h4> <hr /> @Html.ValidationSummary() @Html.HiddenFor(model => model.Id) @Html.HiddenFor(model => model.Code) <div> @Html.LabelFor(m => m.Password) @Html.PasswordFor(m => m.Password) </div> <div> @Html.LabelFor(m => m.ConfirmPassword) @Html.PasswordFor(m => m.ConfirmPassword) </div> <div> <input type="submit" value="Réinitialiser" /> </div> }
48
48
ResetPasswordConfirmation
@{ ViewBag.Title = "Mot de passe réinitalisé"; } <hgroup class="title"> <h1>@ViewBag.Title.</h1> </hgroup> <div> <p> Votre mot de passe a été réinitialisé. Veuillez @Html.ActionLink("cliquer ici pour vous connecter", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) </p> </div>
Connexion «externe » avec Facebook, Google, Twitter
Développeurs Facebook
1. Créer une nouvelle application : Menu « My Apps » « add a new app » … web
2. Donner un nom à l’application puis « Create App Id »
3. Donner l’url du site « http://localhost:7211/ » par exemple et un email
Cliquer sur show
(demande le mot de
passe facebook) pour
afficher « app secret »
49
49
4. Passer le statut de l’application en « public » (on peut créer aussi une application de test)
5. Si ce n’est fait installer le package … PM> install-package Microsoft.Owin.Security.Facebook
6. Rendez-vous dans « Startup.Auth.cs » dossier « App_Start », dé-commenter et entrer l’app id
et l’app secret
app.UseFacebookAuthentication( appId: "475813752569575", appSecret: "123456789abcdefg");
L’utilisateur est ajouté dans la table « Asp.NetUsers » sans mot de passe ainsi que dans la table
« AspNetUserLogins » avec son « UserId »
50
50
Développeurs Google
1. Créer un projet (donner un nom)
2. Créer un identifiant client
Toujours indiquer « signin-google » en url
de redirection
51
51
3. Activer « Google+ Api »
4. Si ce n’est fait installer le package …
PM> install-package Microsoft.Owin.Security.Google 5. Rendez-vous dans « Startup.Auth.cs » dossier « App_Start » et dé-commenter et entrer le
« Client id » et le « Client secret »
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() { ClientId = "903536450598-ps3vsah291ef6iekkqtqflc2d0g746hj.apps.googleusercontent.com", ClientSecret = "123456789abcdefg" });
52
52
Développeurs Twitter
Code supplémentaire // POST: /Account/ExternalLogin [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult ExternalLogin(string provider, string returnUrl) { // Demandez une redirection vers le fournisseur de connexions externe return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); } // GET: /Account/ExternalLoginCallback [AllowAnonymous] public async Task<ActionResult> ExternalLoginCallback(string returnUrl) { var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); if (loginInfo == null) { return RedirectToAction("Login"); } var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); switch (result) { case SignInStatus.Success: return RedirectToLocal(returnUrl); case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); case SignInStatus.Failure: default: // Si l'utilisateur n'a pas de compte ViewBag.ReturnUrl = returnUrl; ViewBag.LoginProvider = loginInfo.Login.LoginProvider; return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email }); } } // POST: /Account/ExternalLoginConfirmation [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) { if (User.Identity.IsAuthenticated) { return RedirectToAction("Index", "Manage"); } if (ModelState.IsValid) { // Obtenez des informations sur l’utilisateur auprès du fournisseur var info = await AuthenticationManager.GetExternalLoginInfoAsync(); if (info == null)
53
53
{ return View("ExternalLoginFailure"); } var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await UserManager.CreateAsync(user); if (result.Succeeded) { result = await UserManager.AddLoginAsync(user.Id, info.Login); if (result.Succeeded) { await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); return RedirectToLocal(returnUrl); } } AddErrors(result); } ViewBag.ReturnUrl = returnUrl; return View(model); } internal class ChallengeResult : HttpUnauthorizedResult { private const string XsrfKey = "XsrfId"; public ChallengeResult(string provider, string redirectUri) : this(provider, redirectUri, null) { } public ChallengeResult(string provider, string redirectUri, string userId) { LoginProvider = provider; RedirectUri = redirectUri; UserId = userId; } public string LoginProvider { get; set; } public string RedirectUri { get; set; } public string UserId { get; set; } public override void ExecuteResult(ControllerContext context) { var properties = new AuthenticationProperties { RedirectUri = RedirectUri }; if (UserId != null) { properties.Dictionary[XsrfKey] = UserId; } context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); } }
54
54
ViewModels
public class ExternalLoginListViewModel { public string ReturnUrl { get; set; } } public class ExternalLoginConfirmationViewModel { [Required] [Display(Name = "Courrier électronique")] public string Email { get; set; } }
Vues
_ExternalLoginsListPartial
@model MvcFromScratch.Models.ExternalLoginListViewModel @using Microsoft.Owin.Security <h4>Utilisez un autre service pour vous connecter.</h4> <hr /> @{ var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes(); if (loginProviders.Count() == 0) { <div> <p> Il n'existe aucun service d'authentification externe configuré. Consultez <a href="http://go.microsoft.com/fwlink/?LinkId=403804">cet article</a> pour des détails sur la configuration de cette application ASP.NET en vue de la prise en charge de la connexion via des services externes. </p> </div> } else { using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) { @Html.AntiForgeryToken() <div id="socialLoginList"> <p> @foreach (AuthenticationDescription p in loginProviders) { <button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Connexion avec votre compte @p.Caption">@p.AuthenticationType</button> } </p> </div> } } }
55
55
ExternalLoginConfirmation
@model MvcFromScratch.Models.ExternalLoginConfirmationViewModel @{ ViewBag.Title = "S’inscrire"; } <h2>@ViewBag.Title.</h2> <h3>Associer votre compte @ViewBag.LoginProvider.</h3> @using (Html.BeginForm("ExternalLoginConfirmation", "Account", FormMethod.Post)) { @Html.AntiForgeryToken() <h4>Formulaire d'association</h4> <hr /> @Html.ValidationSummary(true) <p> Vous avez été authentifié avec succès avec <strong>@ViewBag.LoginProvider</strong>. Veuillez entrer ci-dessous un nom d'utilisateur pour ce site et cliquer sur le bouton S'inscrire pour valider la connexion. </p> <div> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" }) </div> <div> <input type="submit" value="Inscription" /> </div> } ExternalLoginFailure
@{ ViewBag.Title = "Échec de la connexion"; } <hgroup> <h2>@ViewBag.Title.</h2> <h3 class="text-danger">Échec de la connexion auprès du service.</h3> </hgroup>
Exemples : Tutorial, et ici
PM> Install-Package Microsoft.AspNet.Identity.Samples –Pre
c. Autorisations (attribut « Authorize »)
On peut l’appliquer à une action d’un contrôleur
public class PeopleController : Controller { [Authorize] public ActionResult Index() { return View(); } }
56
56
… ou à tout le contrôleur.
[Authorize] public class PeopleController : Controller { [AllowAnonymous] public ActionResult Index() { return View(); } }
… Autoriser l’accès à un rôle, utilisateur, etc. [Authorize(Roles="admin, member")]
ReturnUrl
Si on essaie d’accéder à une vue nécessitant d’être authentifié on est redirigé vers la page de
login.
Une fois authentifié on est redirigé …
ValidateAntiForgeryToken
Pour contrer les attaques malveillantes
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Person person) { if (ModelState.IsValid) { _peopleRepository.Add(person); return RedirectToAction("Index"); } return View(person); }
…Dans la vue
@using (Html.BeginForm()) { @Html.ValidationSummary(true) @Html.AntiForgeryToken()
Permet un accès anonyme à
une action
57
57
AntiXSS
[HttpPost] public ActionResult Create(Person person) { if (ModelState.IsValid) { person.LastName = Sanitizer.GetSafeHtmlFragment(person.LastName); _peopleRepository.Add(person); return RedirectToAction("Index"); } return View(person); }
d. SSL // GET: /Account/Login [RequireHttps] [AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); }
58
58
e. Projet Visual Studio 2012
InitializeSimpleMembership et WebSecurity
Spécification de la chaine de connexion utilisée(DefaultConnection) WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
La base de données correspondant à la chaine de connexion est créée lors de l’enregistrement du
premier membre si elle n’existe pas.
Il est possible d’ajouter ses champs personnalisés et modifier la table « UserProfile » de la base
avec la commande (console du gestionnaire de packages)
PM> Update-Database -Verbose + enable-migrations
On peut également supprimer « InitializeSimpleMembership » et utiliser sa propre connexion
.Ajouter « WebSecurity.InitializeDatabaseConnection » dans global.asax et remplacer
« UsersContext » par son propre DbContext.
59
59
[Table("UserProfile")] public class UserProfile { [Key] [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int UserId { get; set; } public string UserName { get; set; } }
// persist cookie = true bool isLogged = WebSecurity.Login("username", "password",true); WebSecurity.CreateUserAndAccount("username", "password");
Le password est haché automatiquement Si l’utilisateur coche « mémoriser le mot de passe » lorsqu’il se entre ses
informations de connexion dans la page de login, un cookie est créé. OAuth et OpenID avec projet Visual Studio 2012
SimpleRoleProvider et SimpleMembershipProvider
Web.config
<system.web> <!-- etc. --> <roleManager enabled="true" defaultProvider="simple"> <providers> <clear/> <add name="simple" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/> </providers> </roleManager> <membership defaultProvider="simple"> <providers> <clear/> <add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/> </providers> </membership> </system.web>
var roles = (SimpleRoleProvider)Roles.Provider; var membership = (SimpleMembershipProvider)Membership.Provider; if (!roles.RoleExists("Admin")) roles.CreateRole("Admin"); if (membership.GetUser("Marie", false) == null) membership.CreateUserAndAccount("Marie", "passwword"); if (!roles.GetRolesForUser("Marie").Contains("Admin")) roles.AddUsersToRoles(new[] { "Marie" }, new[] { "Admin" });
60
60
Register [AllowAnonymous] public ActionResult Register() { return View(); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.Email, Email = model.Email }; IdentityResult result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } return View(model); }
Login [AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (ModelState.IsValid) { var user = await UserManager.FindAsync(model.Email, model.Password); if (user != null) { await SignInAsync(user, model.RememberMe); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError("", "Nom d'utilisateur ou mot de passe non valide."); } } return View(model); }
61
61
7. Localisation
Dates et Monnaies
Dépendent de la culture courante
Exemple : dans la vue on affiche une monnaie et la date, leur formatage change selon la culture.
@{ var money = 9.99m; var today = DateTime.Now.ToShortDateString(); } <div>@money.ToString("c")</div> <div>@today</div>
Web.config
<system.web> <!----> <globalization culture="auto" uiCulture="auto"/> </system.web>
Changer les préférences linguistiques d’IE
… En montant English
…En montant Français
62
62
Resources
Ajouter un fichier de ressources
Ajouter un fichier de ressources pour la langue par défaut (exemple : Resources.resx) et un
fichier de ressources pour chaque langue supportée par le site (exemple : Resources.en.resx)
Indiquer l’outil personnalisé pour uniquement Resources par défaut.
Exemple en changeant la langue dans les préférences linguistiques d’IE
(LocalizationDemo est le nom du projet)
<div>@LocalizationDemo.Views.Home.Resources.Greeting</div>
On peut également récupérer les ressources dans les contrôleurs var greeting = Resources.Greeting;
Et utiliser les ressources avec les data annotations [Display(ResourceType=typeof(Resources),Name="Greeting")] public string FullName { get; set; }
63
63
8. Less Installer l’ extension pour Visual Studio « Web Essentials » (aperçu CSS, génération de la feuille
de Styles, minimisation, etc.)
9. Dependency Injection (DI)
Avec Ninject
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); RegisterServices(); } private void RegisterServices() { var kernel = new StandardKernel(); DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); kernel.Bind<IPeopleRepository>().To<FakePeopleRepository>(); } } public class NinjectDependencyResolver :IDependencyResolver { private readonly IKernel _kernel; public NinjectDependencyResolver(IKernel kernel) { this._kernel = kernel; } public object GetService(Type serviceType) { return _kernel.TryGet(serviceType, new IParameter[0]); } public IEnumerable<object> GetServices(Type serviceType) { return _kernel.GetAll(serviceType, new IParameter[0]); } }
64
64
10. Upgrade Visual Studio 2013 ne prend en charge MVC qu’à partir de la version 4.
Upgrade de MVC2 vers MVC3 avec ASP.NET MVC 3 Application Upgrader
Upgrade de MVC3 vers MVC4
Ouvrir la solution (dont le projet Mvc ne peut être chargé) dans Visual Studio 2013
… chercher « Auto upgrade mvc 3 to mvc4 » avec le gestionnaire de package NuGet
Il se peut qu’il faille ajouter une référence à « Microsoft.AspNet.Web.Optimization » (package NuGet) ensuite.
Cibler le Framework 4.0 peut aussi éviter des problèmes de versions de fichiers.
65
65
II. Web Api
1. Web Api config
La route par défaut pour un contrôleur Web Api
2. Contrôleur Web Api
a. HTTP Status codes
GET : 200 (ok), 404 (not found), 400, 500
POST : 201 (created), 400 (bad request), 500 (internal server error)
PUT : 200, 404, 400, 500
PATCH : 200,404,400,500
DELETE : 204 (no content), 404, 400, 500
Général : 401 (unauthorized), 403 (forbidden), 405 (method not allowed)
HttpStatusCode
b. Ajout d’un contrôleur Web Api
Menu contextuel sur le dossier « Controllers » du projet
66
66
c. Contrôleur Web API avec IhttpActionResult
Permet de simplifier l’écriture (et réduire le code) de réponses HTTP en utilisant des méthodes :
Classe implémentant de IhttpResult Méthode OkResult Ok NotFoundResult NotFound ExceptionResult UnauthorizedResult Unauthorized BadRequestResult BadRequest ConflictResult Conflict RedirectResult Redirect InvalidModelStateResult
public class PeopleController : ApiController { private IPeopleRepository peopleRepository; public PeopleController() : this(new FakePeopleRepository()) { } public PeopleController(IPeopleRepository peopleRepository) { this.peopleRepository = peopleRepository; } [HttpGet] public IHttpActionResult Get() { try { var people = peopleRepository.GetAll(); return Ok(people); } catch (Exception ex) { return InternalServerError(ex); } } [HttpGet] public IHttpActionResult Get(int id) { try { var person = peopleRepository.GetOne(id); if (person != null) { return Ok(person); } else { return NotFound(); } } catch (Exception ex) { return InternalServerError(ex); } } [HttpPost] public IHttpActionResult Post([FromBody]Person person) {
Injection de
dépendances
67
67
try { var result = peopleRepository.Add(person); return Created(Request.RequestUri + "/" + result.Id.ToString(), result); } catch (Exception ex) { return InternalServerError(ex); } } [HttpPut] public IHttpActionResult Put(int id, [FromBody]Person person) { try { if (person == null) return BadRequest(); var personToUpdate = peopleRepository.GetOne(id); if (personToUpdate == null) { return NotFound(); } var result = peopleRepository.Update(person); return Ok(result); } catch (Exception ex) { return InternalServerError(ex); } } [HttpDelete] public IHttpActionResult Delete(int id) { try { var personToDelete = peopleRepository.GetOne(id); if (personToDelete == null) { return NotFound(); } peopleRepository.Delete(id); return StatusCode(HttpStatusCode.NoContent); } catch (Exception ex) { return InternalServerError(ex); } } }
68
68
d. Contrôleur Web API avec HttpResponseMessage public class PeopleController : ApiController { private IPeopleRepository peopleRepository; public PeopleController() : this(new FakePeopleRepository()) { } public PeopleController(IPeopleRepository peopleRepository) { this.peopleRepository = peopleRepository; } [HttpGet] public HttpResponseMessage Get() { try { var people = peopleRepository.GetAll(); return Request.CreateResponse(HttpStatusCode.OK,people); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError,ex); } } [HttpGet] public HttpResponseMessage GetOne(int id) { try { var person = peopleRepository.GetOne(id); if (person != null) { return Request.CreateResponse(HttpStatusCode.OK,person); } else { return Request.CreateResponse(HttpStatusCode.NotFound); } } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } } [HttpPost] public HttpResponseMessage Post([FromBody]Person person) { try { var result = peopleRepository.Add(person); return Request.CreateResponse(HttpStatusCode.Created, result); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } } [HttpPut] public HttpResponseMessage Put(int id,[FromBody]Person person) { try
On pourrait également retourner
une liste générique en résultat par
exemple
On pourrait également retourner
une instance de classe (exemple
« Person »)
69
69
{ if (person == null) return Request.CreateResponse(HttpStatusCode.BadRequest); var personToUpdate = peopleRepository.GetOne(id); if (personToUpdate == null) { return Request.CreateResponse(HttpStatusCode.NotFound); } var result = peopleRepository.Update(person); return Request.CreateResponse(HttpStatusCode.OK, result); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } } [HttpDelete] public HttpResponseMessage Delete(int id) { try { var personToDelete = peopleRepository.GetOne(id); if (personToDelete == null) { return Request.CreateResponse(HttpStatusCode.NotFound); } peopleRepository.Delete(id); return Request.CreateResponse(HttpStatusCode.NoContent); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } } }
Retour au format Json par défaut. Si on désire un retour au format Xml…DataMember(membres
à intégrer dans la réponse) et DataContract (System.Runtime.Serialization) [Serializable] [DataContract] public class Person { [DataMember] public int Id { get; set; } [DataMember] public string Name { get; set; } [DataMember] public string Twitter { get; set; } } …Et dans le header accept :text/xml
70
70
Test du Service avec Fiddler
GET
POST
Retour au format JSON de l’élément créé
PUT
DELETE
Ajout du type de contenu
Ajout du paramètre « id » à
l’url et passage de l’élément
modifié
« get one »
71
71
3. Attribut “Route”
Permet de personnaliser ses routes et d’éviter les conflits entre routes.
[HttpGet] [Route("api/people/{id}/twitter")] public HttpResponseMessage GetTwitter(int id) { // etc. }
Route Description [Route("api/people/{id}/twitter")] Route personnalisée [Route("api/people/{id:int:min(1)}/twitter")] Contraintes [Route("api/people/{id:int}")] [Route("api/people/{name:alpha}")]
Multiples routes
[Route("api/people/{id?}")] Appliquer à tout le contrôleur [RoutePrefix("api/people")] Prefix pour toutes les routes du contrôleur [RouteArea("admin")] Mvc Areas
4. Injection de dépendances
Avec Ninject
Ajout du package NuGet « NInject »
… ainsi que (pour Web Api)
Global.asax
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); RegisterServices(); } private void RegisterServices() { var kernel = new StandardKernel(); GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel); kernel.Bind<IPeopleRepository>().To<PeopleRepository>(); } }
72
72
Le contrôleur n’a plus besoin de constructeur par défaut
5. Projet Client
a. dataService JavaScript
(Pour projet SPA, Angular, Durandal,…). Avec Knockout par exemple : var dataService = function () { 'use strict' var urlBase = 'http://localhost:12107/api/People', getPeople = function () { return $.getJSON(urlBase); }, searchPeople = function (search) { return $.getJSON(urlBase + '/find/' + search); }, getPerson = function (id) { return $.getJSON(urlBase + '/' + id); }, addPerson = function (person) { return $.ajax({ url: urlBase, type: 'POST', dataType: 'json', contentType: 'application/json; charset=utf-8', data: ko.toJSON(person) }); }, updatePerson = function (person) { return $.ajax({ url: urlBase, type: 'PUT', dataType: 'json', contentType: 'application/json; charset=utf-8', data: ko.toJSON(person) }); }, deletePerson = function (id) { return $.ajax({ url: urlBase + '/' + id, type: 'DELETE' }); }; return { getPeople: getPeople, searchPeople: searchPeople, getPerson: getPerson, updatePerson: updatePerson, addPerson: addPerson, deletePerson: deletePerson }; }();
73
73
b. HttpClient
(projets WinRT, Wpf,etc.)
public class PeopleService { string urlBase ="http://localhost:12107/api/People"; public async Task<Person[]> GetAll() { using (HttpClient client = new HttpClient()) { HttpResponseMessage response = await client.GetAsync(urlBase); string resultContent = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<Person[]>(resultContent); } } public async Task<Person> GetOne(int personID) { using (HttpClient client = new HttpClient()) { HttpResponseMessage result = await client.GetAsync(string.Format("{0}/{1}", urlBase, personID)); string resultContent = await result.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Add(Person person) { using (HttpClient client = new HttpClient()) { string json = JsonConvert.SerializeObject(person); HttpResponseMessage response = await client.PostAsync(urlBase, new StringContent(json, Encoding.UTF8, "application/json")); string resultContent = await response.Content.ReadAsStringAsync(); person = JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Update(Person person) { using (HttpClient client = new HttpClient()) { string json = JsonConvert.SerializeObject(person); HttpResponseMessage response = await client.PutAsync(urlBase, new StringContent(json, Encoding.UTF8, "application/json")); string resultContent = await response.Content.ReadAsStringAsync(); person = JsonConvert.DeserializeObject<Person>(resultContent); } } public async void Delete(int personID) { using (HttpClient client = new HttpClient()) { HttpResponseMessage response = await client.DeleteAsync(string.Format("{0}/{1}", urlBase, personID)); response.EnsureSuccessStatusCode(); } } }
74
74
Il existe également des librairies facilitant encore l’utilisation de HtppClient comme EasyHttp
public class EasyHttpPeopleService { string urlBase = "http://localhost:12107/api/People"; public Person[] GetAll() { HttpClient client = new HttpClient(); HttpResponse response = client.Get(urlBase); Person[] result = response.StaticBody<Person[]>(); return result; } public Person GetOne(int personID) { HttpClient client = new HttpClient(); HttpResponse response = client.Get(string.Format("{0}/{1}", urlBase, personID)); Person result = response.StaticBody<Person>(); return result; } public void Add(Person person) { HttpClient client = new HttpClient(); client.Post(urlBase, person, "application/json"); } public void Update(Person person) { HttpClient client = new HttpClient(); client.Put(urlBase, person, "application/json"); } public void Delete(int personID) { HttpClient client = new HttpClient(); client.Delete(string.Format("{0}/{1}", urlBase, personID)); } }
75
75
6. Cors
Installer avec les packages NuGet
PM> Install-Package Microsoft.AspNet.WebApi.Cors
… Dans WebApiConfig
… Sur le contrôleur Web API
[EnableCorsAttribute("http://localhost:63679", "*","*")] public class PeopleController : ApiController { public IHttpActionResult Get() { var file = HostingEnvironment.MapPath(@"~/App_Data/people.json"); var json = File.ReadAllText(file); var people = JsonConvert.DeserializeObject<List<Person>>(json); return Ok(people); } }
Les ports ne correspondant pas,
on a une erreur
Ajouter l’attribut « EnableCorsAttribute ». Le premier
argument correspondant à l’URL du client (on
pourrait également mettre « * »), le second aux
headers puis les méthodes.
76
76
7. Formatters pour le « Mapping »
Dans le Fichier Json les noms des propriétés sont en camelCase, alors que les propriétés des modèles
eux sont en PascalCase.
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Configuration et services de l'API Web // Configurer l'API Web pour utiliser uniquement l'authentification de jeton du porteur. config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Itinéraires de l'API Web config.MapHttpAttributeRoutes(); config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); config.EnableCors(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
8. OData
Installer avec les packages NuGet
PM> Install-Package Microsoft.AspNet.WebApi.OData
Puis pour les méthodes des contrôleurs
[EnableQuery()] [ResponseType(typeof(Person))] public IHttpActionResult Get() { try { var result = peopleRepository.GetAll(); return Ok(result.AsQueryable()); } catch (Exception ex) { return InternalServerError(ex); } }
Site, Documentation
Résultat retourné AsQueryable.
Ajouter l’attribut
« EnableQuery »
Si on n’utilise pas
« IHttpActionResult » retourner
« IQueryable<Person> » dans
l’exemple
77
77
9. Authentification Web Api
Server
« PeopleController » réclame d’être authentifié .
[Authorize] public class PeopleController : ApiController { private static List<Person> people = new List<Person>() { new Person(1,"John","Papa","@john_papa"), new Person(2, "Ryan", "Niemeyer", "@rpniemeyer"), new Person(3, "Steve", "Sanderson", "@stevensanderson"), new Person(4, "Ward", "Bell", "@wardbell"), new Person(5, "Scott", "Allen","@OdeToCode"), new Person(6,"Yacine","Khammal","@YacineKhammal") }; [HttpGet] public IEnumerable<Person> Get() { return people; } } Startup.Auth et AccountController différent de la version
« Mvc ».L’ApplicationUserManager(IdentityConfig.cs) change peu.
AccountController Startup.Auth.cs
78
78
Client : On n’est autorisé à afficher la liste des personnes qu’une fois authentifié. <body> <header> <!--connexion --> <div id="login"> <form id="loginForm"> <input type="text" name="userName" placeholder="Nom d'utilisateur" value="[email protected]" /> <input type="password" name="password" placeholder="Mot de passe" value="Abc123_" /> <input type="submit" id="loginInput" value="Valider" /> <input type="button" id="showRegisterForm" value="Créer un compte?" /> </form> </div> <!-- enregistrement --> <div id="register"> <form id="registerForm"> <input type="text" id="userName" name="userName" value="[email protected]" /> <input type="text" name="email" value="[email protected]" /> <input type="password" id="password" name="password" value="Abc123_" /> <input type="password" name="confirmPassword" value="Abc123_" /> <input type="submit" id="registerInput" value="Valider" /> <input type="button" id="showLoginForm" value="Connexion" /> </form> </div> <!--connecté --> <div id="loggedIn"></div> </header> <section> <button id="getPeopleListButton">Lister les personnes</button> <div id="container"></div> </section> <script src="Scripts/jquery-2.1.3.js"></script> <script> $(function () { // Show / hide $("#register").hide(); $("#getPeopleListButton").hide(); $("#loggedIn").hide(); $("#showLoginForm").on("click", function () { $("#register").hide(); }); $("#showRegisterForm").on("click", function () { $("#register").show(); }); var accessToken = ""; var loggedIn = false; var register = function () { var url = "api/account/register"; var data = $("#registerForm").serialize(); // récupère les infos saisies dans le formulaire $.post(url, data) .done(function () { alert("Bienvenue !"); login(); // connexion
79
79
}) .fail(function () { alert("Echec lors de la création du compte."); }); return false; } // login var login = function () { var url = "Token"; var data = $("#loginForm").serialize(); //userName=abc%40laposte.net&email=abc%40laposte.net&password=Abc123_&confirmPassword=Abc123_ data = data + "&grant_type=password"; $.post(url, data) .success(function (data) { accessToken = data.access_token; loggedIn = true; $("#login").hide(); $("#register").hide(); var welcomeMessage = "<h2>Bonjour " + data.userName + "!</h2>"; $("#loggedIn").html(welcomeMessage); $("#loggedIn").show(); $("#getPeopleListButton").show(); }) .fail(function () { alert("Impossible de se connecter. vérifiez vos identifiants."); }); return false; } var getAll = function () { var url = "api/people"; if (loggedIn) { $.ajax(url, { type: "GET", headers: { "Authorization": "Bearer " + accessToken, "Content-Type": "application/x-www-form-urlencoded" } }).success(function (data) { var tableHTML = "<table><tr><th>Nom</th><th>Twitter</th></tr>"; data.forEach(function (person) { tableHTML += "<tr><td>" + person.Name + "</td><td>" + person.Twitter + "</td></tr>"; }); tableHTML += "</table>"; $("#container").html(tableHTML); }) .fail(function (error) { alert(JSON.stringify(error, null, 4)); }); }
80
80
else { alert("Veuillez vous authentifier svp."); } } $("#loginInput").on("click", login); $("#registerInput").on("click", register); $("#getPeopleListButton").on("click", getAll); }); </script> </body> Login…on récupère le token …
Récupération de la liste de personnes …
81
81
Activer SSL
Depuis les propriétés du projet
… Puis dans l’onglet Web changer l’URL
URL SSL
82
82
10. MongoDB
Connexion à une base de données NoSQL MongoDB avec un service WEB MVC.
Drivers :
- MongoDB C# Driver
- Simple-mongodb
- FluentMongo
a. Installation de MongoDB C# driver
Documentation
Deux références sont ajoutées :
Serveur
peopledb
…
people
…
MongoClient
MongoServer
MongoDatabase
MongoCollection
83
83
b. Service WEB
Création d’une classe permettant de se connecter à la base de données.
using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; using MongoDBWithMVC.Properties; namespace MongoDBWithMVC.Models { public class MongoDBContext { public MongoDatabase database; public MongoDBContext() { //mongodb://localhost:27017/ var client = new MongoClient(Settings.Default.MongoDBConnectionString); var server = client.GetServer(); database = server.GetDatabase(Settings.Default.DatabaseName); //peopledb } public MongoCollection<Person> People { get { return database.GetCollection<Person>("people"); } } } }
Le modèle utilisé pour la définition des documents
public class Person { [BsonRepresentation(BsonType.ObjectId)] public string Id { get; set; } [BsonElementAttribute("name")] public string Name { get; set; } [BsonElementAttribute("twitter")] public string Twitter { get; set; } }
Pour l’identifiant (_id) attribué
à l’insertion d’un document
Pour la sérialisation lorsque le
nom des éléments est différent
Accès à la collection « people » de la
base
84
84
Service WEB API
public class PeopleController : ApiController { MongoDBContext context; public PeopleController() { context = new MongoDBContext(); } public IHttpActionResult Get() { try { var result = context.People.FindAll(); return Ok(result); } catch (Exception ex) { return InternalServerError(ex); } } public IHttpActionResult Get(string id) { try { var person = context.People.FindOneById(new ObjectId(id)); if (person != null) { return Ok(person); } else { return NotFound(); } } catch (Exception ex) { return InternalServerError(ex); } } [HttpPost] public IHttpActionResult Post([FromBody]Person person) { try { var result = context.People.Insert(person); return Created(Request.RequestUri + "/" + person.Id.ToString(), person); } catch (Exception ex) { return InternalServerError(ex); } } [HttpPut] public IHttpActionResult Put(string id, [FromBody]Person person) { try {
On utilise la classe de contexte créée
pour se connecter et accéder à la
collection « people »
Méthode « FindAll » pour obtenir
tous les documents
Obtention du document par l’id
Ajout d’un document à la collection
« people »
85
85
var personToUpdate = context.People.FindOneById(new ObjectId(id)); if (personToUpdate == null) { return NotFound(); } personToUpdate.Name = person.Name; personToUpdate.Twitter = person.Twitter; context.People.Save(personToUpdate); return Ok(personToUpdate); } catch (Exception ex) { return InternalServerError(ex); } } [HttpDelete] public IHttpActionResult Delete(string id) { try { var personToDelete = context.People.FindOneById(new ObjectId(id)); if (personToDelete == null) { return NotFound(); } context.People.Remove(Query.EQ("_id", new ObjectId(id))); return StatusCode(HttpStatusCode.NoContent); } catch (Exception ex) { return InternalServerError(ex); } } }
Modification puis sauvegarde du
document.
On pourrait aussi utiliser la méthode
update+ updatebuilder
Suppression du document par
l’id
86
86
III. Bootstrap
1. Installation et thèmes On peut « customiser » en sélectionnant seulement les éléments à inclure
Ajout au projet : Télécharger depuis le site ou avec packages NuGet (ou sur Github pour avoir les
fichiers LESS)
Thèmes pour bootstrap
Thème à télécharger sur boostwatch. On renomme le thème par exemple « flatly-bootstrap.css »
que l’on ajoute au projet et on remplace le thème par défaut par celui-ci
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, inital-scale=1" /> <title>Bootstrap demo</title> <link href="css/bootstrap.css" rel="stylesheet" /> <link href="css/bootstrap-theme.css" rel="stylesheet" /> <!--<link href="css/themes/flatly-bootstrap.css" rel="stylesheet" />--> <link href="css/default.css" rel="stylesheet" /> </head> <body> <script src="js/jquery-2.1.3.js"></script> <script src="js/bootstrap.js"></script> </body> </html>
Thème « par défaut » et thème
téléchargé sur boostwatch
87
87
2. Grid system Grid system sur 12 colonnes : exemple de col-md-1 à col-md-12 occupant toute la largeur.
Taille Classe >=1200px col-lg-xx de 992 à 1200px col-md-xx De 768 à 991px col-sm-xx <768px col-xs-xx
<div class="row"> <div id="main" class="col-md-8 col-sm-10 col-xs-6"><p>Lorem ipsum ... </p></div> <div id="sidebar" class="col-md-4 col-sm-2 col-xs-6">Lorem ... </div> </div>
a. Cacher une colonne selon une résolution
<div id="sidebar" class="col-md-4 hidden-xs">Lorem ipsum ... </div>
col-md-8 et col-md-4 pour la
« sidebar » sur taille « middle »
Cachera la colonne sur mobile
La taille des colonnes s’adapte selon les résolutions
Sur mobile 2 colonnes égales (col-xs-
6)
88
88
b. Décalage de colonnes avec « offset »
De col-xx-offset-1 à col-xx-offset-12
Ex
<div class="row"> <div class="col-md-8 col-md-offset-4">Lorem ipsum …</div> </div>
c. Image flottante
Thumbnails
<div class="row"> <div class="col-md-12"> <p><img src="images/foodies.jpg" width="300" class="pull-left thumbnail"/>Lorem …</p> </div> </div>
3. Bases
a. Typographie
Documentation
Classes commençant par « text-xx »
<p class="text-muted">Lorem ipsum dolor sit amet</p>
Colonne « col-md-8 » avec
décalage de 4 colonnes « col-md-
offset-4 »
« pull-left » place l’image à gauche (« pull-right »
dispo)
« thumbnail » ajoute une bordure et effet à la
photo
89
89
b. Boutons, groupes de boutons et dropdowns
Buttons
<button class="btn btn-danger">En savoir plus ...</button>
Dropdowns
<div class="form-group"> <div class="btn-group"> <button class="btn btn-default active">Un</button> <button class="btn btn-default">Deux</button> <div class="btn-group"> <button id="option" class="btn btn-default">Choisir ...</button> <button class="btn btn-default" data-toggle="dropdown"><span class="caret"></span></button> <ul id="options" class="dropdown-menu"> <li><a href="#">Option un</a></li> <li><a href="#">Option deux</a></li> <li class="divider"></li> <li class="disabled"><a href="#">Option trois</a></li> </ul> </div> </div> </div> Script pour changer le texte du bouton <script> $(document).ready(function () { $("#options li a").on("click", function () { var option = $(this).text(); $("#option").text(option); }) }); </script>
On pourrait utiliser des
boutons radio avec label
90
90
c. Icones
Glyphicons
<button class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"><span class="glyphicon glyphicon-chevron-down"></span></button>
d. Formulaires, listes, et tables
Forms, nav, tables
« input-group »
Documentation
<div class="form-group"> <div class="col-md-10"> <div class="input-group"> <span class="input-group-addon">@</span> <input type="email" class="form-control" placeholder="Email"/> </div> </div> </div>
e. Navbar desktop et mobile
Documentation
<header class="container"> <div id="menu" class="nav navbar-default"> <div class="navbar-header"> <button class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"><span class="glyphicon glyphicon-chevron-down"></span></button> <h4><a href="#">Boostrap demo</a></h4> </div> <nav class="navbar navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li class="nav active"><a href="#">Accueil</a></li> <li class="nav"><a href="#">A propos</a></li> <li class="nav"><a href="#">Contact</a></li> </ul> </nav> </div> </header>
Version desktop
91
91
Avec thème « flatly »
Version mobile
Avec thème « flatly »
f. Header et breadcrumb
Header, breadcrumb
<div id="body" class="container"> <ol class="breadcrumb"> <li><a href="index.html">Accueil</a></li> <li class="active">A propos</li> </ol> <div class="page-header"> <h1>Bootstrap demo</h1> <p>Une démonstration du Framework</p> </div>
g. Pagination
Documentation
« breadcrumb »
« header » mis en
forme
92
92
<ul class="pagination"> <li class="disabled"><a href="#">«</a></li> <li class="active"><a href="#">1</a></li> <li><a href="#">2</a></li> <li><a href="#">3</a></li> <li><a href="#">»</a></li> </ul>
h. Well
<p class="well"> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lacinia metus quis vulputate semper. </p>
i. Panels
Documentation
Plugins
a. Collapse et accordéon
Documentation
On pourrait ajouter la classe « lead » pour mettre plus en avant
93
93
b. Boite de dialogue
Documentation
<a href="#mydialogbox" class="btn btn-danger" data-toggle="modal">Ouvrir la boite de dialogue ...</a> <div id="mydialogbox" class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <a href="#" class="close" data-dissmiss="modal">×</a> <h3>Une boite de dialogue</h3> </div> <div class="modal-body"> Le contenu de la boite! </div> <div class="modal-footer"> le pied </div> </div> </div> </div>
Depuis jQuery
$("#mydialogbox").modal("show");
c. Alert
Documentation
On place une alerte au-dessus d’un formulaire
« collapse » par défaut que l’on affiche si le
formulaire comporte des erreurs par exemple
94
94
<div class="alert alert-warning collapse" id="formmessage"> <a href="#" class="close" data-dissmiss="alert">×</a> <p>Vous devez remplir tous les champs!</p> </div> <form> <!-- ... -->
$("form").on("submit", function (e) { if($("#email").text()=="") { e.preventDefault(); $("#formmessage").show(); } })
d. Tab
Documentation
<ul class="nav nav-tabs nav-justified"> <li class="active"><a href="#formtab" data-toggle="tab">Contact</a></li> <li><a href="#maptab" data-toggle="tab">Map</a></li> </ul> <div class="tab-content"> <div class="well tab-pane active" id="formtab"> <form> <!-- ... --> </form> </div> <div class="well tab-pane" id="maptab"> <!-- ... --> </div> </div>
e. Tooltip
Documentation
95
95
Exemple toolptip sur les images (affiche la propriété title)
$("img").tooltip();
On pourrait définir du « html » dans l’attribut « title »
<img id="foodies" src="images/foodies.jpg" width="300" class="pull-left thumbnail" data-html="true" title="<h3>Un petit café?</h3><img src='images/foodies.jpg' class='img-responsive'/>" />
f. Caroussel
Documentation
« data-html »
96
96
<div class="container"> <div class="row visible-lg visible-md"> <div class="col-lg-8 col-lg-offset-2 col-md-8 col-md-offset-2"> <div id="carousel" class="carousel slide" data-ride="carousel" data-interval="2000"> <!-- indicateurs --> <ol class="carousel-indicators"> <li data-target="#carousel" data-slide-to="0" class="active"></li> <li data-target="#carousel" data-slide-to="1"></li> <li data-target="#carousel" data-slide-to="2"></li> </ol> <!-- slides --> <div class="carousel-inner"> <div class="item active"> <img src="images/image_1.jpg" class="img-responsive" alt="" /> <div class="carousel-caption"><h4>Un petit café?</h4></div> </div> <div class="item"> <img src="images/image_2.jpg" class="img-responsive" alt="" /> <div class="carousel-caption"><h4>Un poussin</h4></div> </div> <div class="item"> <img src="images/image_3.jpg" class="img-responsive" alt="" /> <div class="carousel-caption"><h4>La grande roue</h4></div> </div> </div> <!-- Controls --> <a class="left carousel-control" href="#carousel" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> </a> <a class="right carousel-control" href="#carousel" data-slide="next"> <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> </a> </div> </div> </div> </div>
jQuery
Permet de lancer auto.
(data-interval : intervalle
entre chaque slide en ms)
Contrôles
Indicateurs
Caption
97
97
$("#carousel").carousel();